blob: 9a1d4e337f42722124fbd488952bc63c0e9efc95 [file] [log] [blame]
# 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 utility functions to help managing servers in server
database (defined in global config section AUTOTEST_SERVER_DB).
"""
import socket
import subprocess
import sys
import common
import django.core.exceptions
from autotest_lib.client.common_lib import base_utils as utils
from autotest_lib.client.common_lib.global_config import global_config
from autotest_lib.frontend.server import models as server_models
from autotest_lib.site_utils.lib import infra
class ServerActionError(Exception):
"""Exception raised when action on server failed.
"""
def use_server_db():
"""Check if use_server_db is enabled in configuration.
@return: True if use_server_db is set to True in global config.
"""
return global_config.get_config_value(
'SERVER', 'use_server_db', default=False, type=bool)
def warn_missing_role(role, exclude_server):
"""Post a warning if Autotest instance has no other primary server with
given role.
@param role: Name of the role.
@param exclude_server: Server to be excluded from search for role.
"""
servers = server_models.Server.objects.filter(
roles__role=role,
status=server_models.Server.STATUS.PRIMARY).exclude(
hostname=exclude_server.hostname)
if not servers:
message = ('WARNING! There will be no server with role %s after it\'s '
'removed from server %s. Autotest will not function '
'normally without any server in role %s.' %
(role, exclude_server.hostname, role))
print >> sys.stderr, message
def get_servers(hostname=None, role=None, status=None):
"""Find servers with given role and status.
@param hostname: hostname of the server.
@param role: Role of server, default to None.
@param status: Status of server, default to None.
@return: A list of server objects with given role and status.
"""
filters = {}
if hostname:
filters['hostname'] = hostname
if role:
filters['roles__role'] = role
if status:
filters['status'] = status
return list(server_models.Server.objects.filter(**filters))
def get_server_details(servers, table=False, summary=False):
"""Get a string of given servers' details.
The method can return a string of server information in 3 different formats:
A detail view:
Hostname : server2
Status : primary
Roles : drone
Attributes : {'max_processes':300}
Date Created : 2014-11-25 12:00:00
Date Modified: None
Note : Drone in lab1
A table view:
Hostname | Status | Roles | Date Created | Date Modified | Note
server1 | backup | scheduler | 2014-11-25 23:45:19 | |
server2 | primary | drone | 2014-11-25 12:00:00 | | Drone
A summary view:
scheduler : server1(backup), server3(primary),
host_scheduler :
drone : server2(primary),
devserver :
database :
suite_scheduler:
crash_server :
No Role :
The method returns detail view of each server and a summary view by default.
If `table` is set to True, only table view will be returned.
If `summary` is set to True, only summary view will be returned.
@param servers: A list of servers to get details.
@param table: True to return a table view instead of a detail view,
default is set to False.
@param summary: True to only show the summary of roles and status of
given servers.
@return: A string of the information of given servers.
"""
# Format string to display a table view.
# Hostname, Status, Roles, Date Created, Date Modified, Note
TABLEVIEW_FORMAT = ('%(hostname)-30s | %(status)-7s | %(roles)-20s | '
'%(date_created)-19s | %(date_modified)-19s | %(note)s')
result = ''
if not table and not summary:
for server in servers:
result += '\n' + str(server)
elif table:
result += (TABLEVIEW_FORMAT %
{'hostname':'Hostname', 'status':'Status',
'roles':'Roles', 'date_created':'Date Created',
'date_modified':'Date Modified', 'note':'Note'})
for server in servers:
roles = ','.join(server.get_role_names())
result += '\n' + (TABLEVIEW_FORMAT %
{'hostname':server.hostname,
'status': server.status or '',
'roles': roles,
'date_created': server.date_created,
'date_modified': server.date_modified or '',
'note': server.note or ''})
elif summary:
result += 'Roles and status of servers:\n\n'
for role, _ in server_models.ServerRole.ROLE.choices():
servers_of_role = [s for s in servers if role in
[r.role for r in s.roles.all()]]
result += '%-15s: ' % role
for server in servers_of_role:
result += '%s(%s), ' % (server.hostname, server.status)
result += '\n'
servers_without_role = [s.hostname for s in servers
if not s.roles.all()]
result += '%-15s: %s' % ('No Role', ', '.join(servers_without_role))
return result
def check_server(hostname, role):
"""Confirm server with given hostname is ready to be primary of given role.
If the server is a backup and failed to be verified for the role, remove
the role from its roles list. If it has no other role, set its status to
repair_required.
@param hostname: hostname of the server.
@param role: Role to be checked.
@return: True if server can be verified for the given role, otherwise
return False.
"""
# TODO(dshi): Add more logic to confirm server is ready for the role.
# For now, the function just checks if server is ssh-able.
try:
infra.execute_command(hostname, 'true')
return True
except subprocess.CalledProcessError as e:
print >> sys.stderr, ('Failed to check server %s, error: %s' %
(hostname, e))
return False
def verify_server(exist=True):
"""Decorator to check if server with given hostname exists in the database.
@param exist: Set to True to confirm server exists in the database, raise
exception if not. If it's set to False, raise exception if
server exists in database. Default is True.
@raise ServerActionError: If `exist` is True and server does not exist in
the database, or `exist` is False and server exists
in the database.
"""
def deco_verify(func):
"""Wrapper for the decorator.
@param func: Function to be called.
"""
def func_verify(*args, **kwargs):
"""Decorator to check if server exists.
If exist is set to True, raise ServerActionError is server with
given hostname is not found in server database.
If exist is set to False, raise ServerActionError is server with
given hostname is found in server database.
@param func: function to be called.
@param args: arguments for function to be called.
@param kwargs: keyword arguments for function to be called.
"""
hostname = kwargs['hostname']
try:
server = server_models.Server.objects.get(hostname=hostname)
except django.core.exceptions.ObjectDoesNotExist:
server = None
if not exist and server:
raise ServerActionError('Server %s already exists.' %
hostname)
if exist and not server:
raise ServerActionError('Server %s does not exist in the '
'database.' % hostname)
if server:
kwargs['server'] = server
return func(*args, **kwargs)
return func_verify
return deco_verify
def get_drones():
"""Get a list of drones in status primary.
@return: A list of drones in status primary.
"""
servers = get_servers(role=server_models.ServerRole.ROLE.DRONE,
status=server_models.Server.STATUS.PRIMARY)
return [s.hostname for s in servers]
def delete_attribute(server, attribute):
"""Delete the attribute from the host.
@param server: An object of server_models.Server.
@param attribute: Name of an attribute of the server.
"""
attributes = server.attributes.filter(attribute=attribute)
if not attributes:
raise ServerActionError('Server %s does not have attribute %s' %
(server.hostname, attribute))
attributes[0].delete()
print 'Attribute %s is deleted from server %s.' % (attribute,
server.hostname)
def change_attribute(server, attribute, value):
"""Change the value of an attribute of the server.
@param server: An object of server_models.Server.
@param attribute: Name of an attribute of the server.
@param value: Value of the attribute of the server.
@raise ServerActionError: If the attribute already exists and has the
given value.
"""
attributes = server_models.ServerAttribute.objects.filter(
server=server, attribute=attribute)
if attributes and attributes[0].value == value:
raise ServerActionError('Attribute %s for Server %s already has '
'value of %s.' %
(attribute, server.hostname, value))
if attributes:
old_value = attributes[0].value
attributes[0].value = value
attributes[0].save()
print ('Attribute `%s` of server %s is changed from %s to %s.' %
(attribute, server.hostname, old_value, value))
else:
server_models.ServerAttribute.objects.create(
server=server, attribute=attribute, value=value)
print ('Attribute `%s` of server %s is set to %s.' %
(attribute, server.hostname, value))
def get_drones():
"""Get a list of drones in status primary.
@return: A list of drones in status primary.
"""
servers = get_servers(role=server_models.ServerRole.ROLE.DRONE,
status=server_models.Server.STATUS.PRIMARY)
return [s.hostname for s in servers]
def get_shards():
"""Get a list of shards in status primary.
@return: A list of shards in status primary.
"""
servers = get_servers(role=server_models.ServerRole.ROLE.SHARD,
status=server_models.Server.STATUS.PRIMARY)
return [s.hostname for s in servers]
def confirm_server_has_role(hostname, role):
"""Confirm a given server has the given role, and its status is primary.
@param hostname: hostname of the server.
@param role: Name of the role to be checked.
@raise ServerActionError: If localhost does not have given role or it's
not in primary status.
"""
if hostname.lower() in ['localhost', '127.0.0.1']:
hostname = socket.gethostname()
hostname = utils.normalize_hostname(hostname)
servers = get_servers(role=role, status=server_models.Server.STATUS.PRIMARY)
for server in servers:
if hostname == utils.normalize_hostname(server.hostname):
return True
raise ServerActionError('Server %s does not have role of %s running in '
'status primary.' % (hostname, role))