1# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This module provides functions to manage servers in server database
6(defined in global config section AUTOTEST_SERVER_DB).
7
8create(hostname, role=None, note=None)
9    Create a server with given role, with status primary.
10
11delete(hostname)
12    Delete a server from the database.
13
14modify(hostname, role=None, status=None, note=None, delete=False,
15       attribute=None, value=None)
16    Modify a server's role, status, note, or attribute:
17    1. Add role to a server. If the server is in primary status, proper actions
18       like service restart will be executed to enable the role.
19    2. Delete a role from a server. If the server is in primary status, proper
20       actions like service restart will be executed to disable the role.
21    3. Change status of a server. If the server is changed from or to primary
22       status, proper actions like service restart will be executed to enable
23       or disable each role of the server.
24    4. Change note of a server. Note is a field you can add description about
25       the server.
26    5. Change/delete attribute of a server. Attribute can be used to store
27       information about a server. For example, the max_processes count for a
28       drone.
29
30"""
31
32
33import datetime
34
35import common
36
37from autotest_lib.frontend.server import models as server_models
38from autotest_lib.site_utils import server_manager_actions
39from autotest_lib.site_utils import server_manager_utils
40
41
42def _add_role(server, role, action):
43    """Add a role to the server.
44
45    @param server: An object of server_models.Server.
46    @param role: Role to be added to the server.
47    @param action: Execute actions after role or status is changed. Default to
48                   False.
49
50    @raise ServerActionError: If role is failed to be added.
51    """
52    server_models.validate(role=role)
53    if role in server.get_role_names():
54        raise server_manager_utils.ServerActionError(
55                'Server %s already has role %s.' % (server.hostname, role))
56
57    # Verify server
58    if not server_manager_utils.check_server(server.hostname, role):
59        raise server_manager_utils.ServerActionError(
60                'Server %s is not ready for role %s.' % (server.hostname, role))
61
62    if (role in server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE and
63        server.status == server_models.Server.STATUS.PRIMARY):
64        servers = server_models.Server.objects.filter(
65                roles__role=role, status=server_models.Server.STATUS.PRIMARY)
66        if len(servers) >= 1:
67            raise server_manager_utils.ServerActionError(
68                'Role %s must be unique. Server %s already has role %s.' %
69                (role, servers[0].hostname, role))
70
71    server_models.ServerRole.objects.create(server=server, role=role)
72
73    # If needed, apply actions to enable the role for the server.
74    server_manager_actions.try_execute(server, [role], enable=True,
75                                       post_change=True, do_action=action)
76
77    print 'Role %s is added to server %s.' % (role, server.hostname)
78
79
80def _delete_role(server, role, action=False):
81    """Delete a role from the server.
82
83    @param server: An object of server_models.Server.
84    @param role: Role to be deleted from the server.
85    @param action: Execute actions after role or status is changed. Default to
86                   False.
87
88    @raise ServerActionError: If role is failed to be deleted.
89    """
90    server_models.validate(role=role)
91    if role not in server.get_role_names():
92        raise server_manager_utils.ServerActionError(
93                'Server %s does not have role %s.' % (server.hostname, role))
94
95    if server.status == server_models.Server.STATUS.PRIMARY:
96        server_manager_utils.warn_missing_role(role, server)
97
98    # Apply actions to disable the role for the server before the role is
99    # removed from the server.
100    server_manager_actions.try_execute(server, [role], enable=False,
101                                       post_change=False, do_action=action)
102
103    print 'Deleting role %s from server %s...' % (role, server.hostname)
104    server.roles.get(role=role).delete()
105
106    # Apply actions to disable the role for the server after the role is
107    # removed from the server.
108    server_manager_actions.try_execute(server, [role], enable=False,
109                                       post_change=True, do_action=action)
110
111    if (not server.get_role_names() and
112        server.status == server_models.Server.STATUS.PRIMARY):
113        print ('Server %s has no role.')
114
115    print 'Role %s is deleted from server %s.' % (role, server.hostname)
116
117
118def _change_status(server, status, action):
119    """Change the status of the server.
120
121    @param server: An object of server_models.Server.
122    @param status: New status of the server.
123    @param action: Execute actions after role or status is changed. Default to
124                   False.
125
126    @raise ServerActionError: If status is failed to be changed.
127    """
128    server_models.validate(status=status)
129    if server.status == status:
130        raise server_manager_utils.ServerActionError(
131                'Server %s already has status of %s.' %
132                (server.hostname, status))
133    if (not server.roles.all() and
134        status == server_models.Server.STATUS.PRIMARY):
135        raise server_manager_utils.ServerActionError(
136                'Server %s has no role associated. Server must have a role to '
137                'be in status primary.' % server.hostname)
138
139    # Abort the action if the server's status will be changed to primary and
140    # the Autotest instance already has another server running an unique role.
141    # For example, a scheduler server is already running, and a repair_required
142    # server with role scheduler should not be changed to status primary.
143    unique_roles = server.roles.filter(
144            role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE)
145    if unique_roles and status == server_models.Server.STATUS.PRIMARY:
146        for role in unique_roles:
147            servers = server_models.Server.objects.filter(
148                    roles__role=role.role,
149                    status=server_models.Server.STATUS.PRIMARY)
150            if len(servers) == 1:
151                raise server_manager_utils.ServerActionError(
152                        'Role %s must be unique. Server %s already has the '
153                        'role.' % (role.role, servers[0].hostname))
154
155    # Post a warning if the server's status will be changed from primary to
156    # other value and the server is running a unique role across database, e.g.
157    # scheduler.
158    if server.status == server_models.Server.STATUS.PRIMARY:
159        for role in server.get_role_names():
160            server_manager_utils.warn_missing_role(role, server)
161
162    enable = status == server_models.Server.STATUS.PRIMARY
163    server_manager_actions.try_execute(server, server.get_role_names(),
164                                       enable=enable, post_change=False,
165                                       do_action=action)
166
167    prev_status = server.status
168    server.status = status
169    server.save()
170
171    # Apply actions to enable/disable roles of the server after the status is
172    # changed.
173    server_manager_actions.try_execute(server, server.get_role_names(),
174                                       enable=enable, post_change=True,
175                                       prev_status=prev_status,
176                                       do_action=action)
177
178    print ('Status of server %s is changed from %s to %s. Affected roles: %s' %
179           (server.hostname, prev_status, status,
180            ', '.join(server.get_role_names())))
181
182
183@server_manager_utils.verify_server(exist=False)
184def create(hostname, role=None, note=None):
185    """Create a new server.
186
187    The status of new server will always be primary.
188
189    @param hostname: hostname of the server.
190    @param role: role of the new server, default to None.
191    @param note: notes about the server, default to None.
192
193    @return: A Server object that contains the server information.
194    """
195    server_models.validate(hostname=hostname, role=role)
196    server = server_models.Server.objects.create(
197            hostname=hostname, status=server_models.Server.STATUS.PRIMARY,
198            note=note, date_created=datetime.datetime.now())
199    server_models.ServerRole.objects.create(server=server, role=role)
200    return server
201
202
203@server_manager_utils.verify_server()
204def delete(hostname, server=None):
205    """Delete given server from server database.
206
207    @param hostname: hostname of the server to be deleted.
208    @param server: Server object from database query, this argument should be
209                   injected by the verify_server_exists decorator.
210
211    @raise ServerActionError: If delete server action failed, e.g., server is
212            not found in database.
213    """
214    print 'Deleting server %s from server database.' % hostname
215
216    if (server_manager_utils.use_server_db() and
217        server.status == server_models.Server.STATUS.PRIMARY):
218        print ('Server %s is in status primary, need to disable its '
219               'current roles first.' % hostname)
220        for role in server.roles.all():
221            _delete_role(server, role.role)
222
223    server.delete()
224    print 'Server %s is deleted from server database.' % hostname
225
226
227@server_manager_utils.verify_server()
228def modify(hostname, role=None, status=None, delete=False, note=None,
229           attribute=None, value=None, action=False, server=None):
230    """Modify given server with specified actions.
231
232    @param hostname: hostname of the server to be modified.
233    @param role: Role to be added to the server.
234    @param status: Modify server status.
235    @param delete: True to delete given role from the server, default to False.
236    @param note: Note of the server.
237    @param attribute: Name of an attribute of the server.
238    @param value: Value of an attribute of the server.
239    @param action: Execute actions after role or status is changed. Default to
240                   False.
241    @param server: Server object from database query, this argument should be
242                   injected by the verify_server_exists decorator.
243
244    @raise InvalidDataError: If the operation failed with any wrong value of
245                             the arguments.
246    @raise ServerActionError: If any operation failed.
247    """
248    if role:
249        if not delete:
250            _add_role(server, role, action)
251        else:
252            _delete_role(server, role, action)
253
254    if status:
255        _change_status(server, status, action)
256
257    if note is not None:
258        server.note = note
259        server.save()
260
261    if attribute and value:
262        server_manager_utils.change_attribute(server, attribute, value)
263    elif attribute and delete:
264        server_manager_utils.delete_attribute(server, attribute)
265
266    return server
267