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