# 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