# Copyright 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This module provides functions to manage servers in server database (defined in global config section AUTOTEST_SERVER_DB). create(hostname, role=None, note=None) Create a server with given role, with status backup. delete(hostname) Delete a server from the database. If the server is in primary status, its roles will be replaced by a backup server first. modify(hostname, role=None, status=None, note=None, delete=False, attribute=None, value=None) Modify a server's role, status, note, or attribute: 1. Add role to a server. If the server is in primary status, proper actions like service restart will be executed to enable the role. 2. Delete a role from a server. If the server is in primary status, proper actions like service restart will be executed to disable the role. 3. Change status of a server. If the server is changed from or to primary status, proper actions like service restart will be executed to enable or disable each role of the server. 4. Change note of a server. Note is a field you can add description about the server. 5. Change/delete attribute of a server. Attribute can be used to store information about a server. For example, the max_processes count for a drone. """ import datetime import common from autotest_lib.frontend.server import models as server_models from autotest_lib.site_utils import server_manager_actions from autotest_lib.site_utils import server_manager_utils def _add_role(server, role, action): """Add a role to the server. @param server: An object of server_models.Server. @param role: Role to be added to the server. @param action: Execute actions after role or status is changed. Default to False. @raise ServerActionError: If role is failed to be added. """ server_models.validate(role=role) if role in server.get_role_names(): raise server_manager_utils.ServerActionError( 'Server %s already has role %s.' % (server.hostname, role)) # Verify server if not server_manager_utils.check_server(server.hostname, role): raise server_manager_utils.ServerActionError( 'Server %s is not ready for role %s.' % (server.hostname, role)) if (role in server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE and server.status == server_models.Server.STATUS.PRIMARY): servers = server_models.Server.objects.filter( roles__role=role, status=server_models.Server.STATUS.PRIMARY) if len(servers) >= 1: raise server_manager_utils.ServerActionError( 'Role %s must be unique. Server %s already has role %s.' % (role, servers[0].hostname, role)) server_models.ServerRole.objects.create(server=server, role=role) # If needed, apply actions to enable the role for the server. server_manager_actions.try_execute(server, [role], enable=True, post_change=True, do_action=action) print 'Role %s is added to server %s.' % (role, server.hostname) def _delete_role(server, role, action=False): """Delete a role from the server. @param server: An object of server_models.Server. @param role: Role to be deleted from the server. @param action: Execute actions after role or status is changed. Default to False. @raise ServerActionError: If role is failed to be deleted. """ server_models.validate(role=role) if role not in server.get_role_names(): raise server_manager_utils.ServerActionError( 'Server %s does not have role %s.' % (server.hostname, role)) if server.status == server_models.Server.STATUS.PRIMARY: server_manager_utils.warn_missing_role(role, server) # Apply actions to disable the role for the server before the role is # removed from the server. server_manager_actions.try_execute(server, [role], enable=False, post_change=False, do_action=action) print 'Deleting role %s from server %s...' % (role, server.hostname) server.roles.get(role=role).delete() # Apply actions to disable the role for the server after the role is # removed from the server. server_manager_actions.try_execute(server, [role], enable=False, post_change=True, do_action=action) # If the server is in status primary and has no role, change its status to # backup. if (not server.get_role_names() and server.status == server_models.Server.STATUS.PRIMARY): print ('Server %s has no role, change its status from primary to backup' % server.hostname) server.status = server_models.Server.STATUS.BACKUP server.save() print 'Role %s is deleted from server %s.' % (role, server.hostname) def _change_status(server, status, action): """Change the status of the server. @param server: An object of server_models.Server. @param status: New status of the server. @param action: Execute actions after role or status is changed. Default to False. @raise ServerActionError: If status is failed to be changed. """ server_models.validate(status=status) if server.status == status: raise server_manager_utils.ServerActionError( 'Server %s already has status of %s.' % (server.hostname, status)) if (not server.roles.all() and status == server_models.Server.STATUS.PRIMARY): raise server_manager_utils.ServerActionError( 'Server %s has no role associated. Server must have a role to ' 'be in status primary.' % server.hostname) # Abort the action if the server's status will be changed to primary and # the Autotest instance already has another server running an unique role. # For example, a scheduler server is already running, and a backup server # with role scheduler should not be changed to status primary. unique_roles = server.roles.filter( role__in=server_models.ServerRole.ROLES_REQUIRE_UNIQUE_INSTANCE) if unique_roles and status == server_models.Server.STATUS.PRIMARY: for role in unique_roles: servers = server_models.Server.objects.filter( roles__role=role.role, status=server_models.Server.STATUS.PRIMARY) if len(servers) == 1: raise server_manager_utils.ServerActionError( 'Role %s must be unique. Server %s already has the ' 'role.' % (role.role, servers[0].hostname)) # Post a warning if the server's status will be changed from primary to # other value and the server is running a unique role across database, e.g. # scheduler. if server.status == server_models.Server.STATUS.PRIMARY: for role in server.get_role_names(): server_manager_utils.warn_missing_role(role, server) enable = status == server_models.Server.STATUS.PRIMARY server_manager_actions.try_execute(server, server.get_role_names(), enable=enable, post_change=False, do_action=action) prev_status = server.status server.status = status server.save() # Apply actions to enable/disable roles of the server after the status is # changed. server_manager_actions.try_execute(server, server.get_role_names(), enable=enable, post_change=True, prev_status=prev_status, do_action=action) print ('Status of server %s is changed from %s to %s. Affected roles: %s' % (server.hostname, prev_status, status, ', '.join(server.get_role_names()))) @server_manager_utils.verify_server(exist=False) def create(hostname, role=None, note=None): """Create a new server. The status of new server will always be backup, user need to call atest server modify hostname --status primary to set the server's status to primary. @param hostname: hostname of the server. @param role: role of the new server, default to None. @param note: notes about the server, default to None. @return: A Server object that contains the server information. """ server_models.validate(hostname=hostname, role=role) server = server_models.Server.objects.create( hostname=hostname, status=server_models.Server.STATUS.BACKUP, note=note, date_created=datetime.datetime.now()) server_models.ServerRole.objects.create(server=server, role=role) return server @server_manager_utils.verify_server() def delete(hostname, server=None): """Delete given server from server database. @param hostname: hostname of the server to be deleted. @param server: Server object from database query, this argument should be injected by the verify_server_exists decorator. @raise ServerActionError: If delete server action failed, e.g., server is not found in database or server is primary but no backup is found. """ print 'Deleting server %s from server database.' % hostname if (server_manager_utils.use_server_db() and server.status == server_models.Server.STATUS.PRIMARY): print ('Server %s is in status primary, need to disable its ' 'current roles first.' % hostname) for role in server.roles.all(): _delete_role(server, role.role) server.delete() print 'Server %s is deleted from server database.' % hostname @server_manager_utils.verify_server() def modify(hostname, role=None, status=None, delete=False, note=None, attribute=None, value=None, action=False, server=None): """Modify given server with specified actions. @param hostname: hostname of the server to be modified. @param role: Role to be added to the server. @param status: Modify server status. @param delete: True to delete given role from the server, default to False. @param note: Note of the server. @param attribute: Name of an attribute of the server. @param value: Value of an attribute of the server. @param action: Execute actions after role or status is changed. Default to False. @param server: Server object from database query, this argument should be injected by the verify_server_exists decorator. @raise InvalidDataError: If the operation failed with any wrong value of the arguments. @raise ServerActionError: If any operation failed. """ if role: if not delete: _add_role(server, role, action) else: _delete_role(server, role, action) if status: _change_status(server, status, action) if note is not None: server.note = note server.save() if attribute and value: server_manager_utils.change_attribute(server, attribute, value) elif attribute and delete: server_manager_utils.delete_attribute(server, attribute) return server