Home | History | Annotate | Download | only in site_utils
      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 utility functions to help managing servers in server
      6 database (defined in global config section AUTOTEST_SERVER_DB).
      7 
      8 """
      9 
     10 import socket
     11 import subprocess
     12 import sys
     13 
     14 import common
     15 
     16 import django.core.exceptions
     17 from autotest_lib.client.common_lib import base_utils as utils
     18 from autotest_lib.client.common_lib.global_config import global_config
     19 from autotest_lib.frontend.server import models as server_models
     20 from autotest_lib.site_utils.lib import infra
     21 
     22 
     23 class ServerActionError(Exception):
     24     """Exception raised when action on server failed.
     25     """
     26 
     27 
     28 def use_server_db():
     29     """Check if use_server_db is enabled in configuration.
     30 
     31     @return: True if use_server_db is set to True in global config.
     32     """
     33     return global_config.get_config_value(
     34             'SERVER', 'use_server_db', default=False, type=bool)
     35 
     36 
     37 def warn_missing_role(role, exclude_server):
     38     """Post a warning if Autotest instance has no other primary server with
     39     given role.
     40 
     41     @param role: Name of the role.
     42     @param exclude_server: Server to be excluded from search for role.
     43     """
     44     servers = server_models.Server.objects.filter(
     45             roles__role=role,
     46             status=server_models.Server.STATUS.PRIMARY).exclude(
     47                     hostname=exclude_server.hostname)
     48     if not servers:
     49         message = ('WARNING! There will be no server with role %s after it\'s '
     50                    'removed from server %s. Autotest will not function '
     51                    'normally without any server in role %s.' %
     52                    (role, exclude_server.hostname, role))
     53         print >> sys.stderr, message
     54 
     55 
     56 def get_servers(hostname=None, role=None, status=None):
     57     """Find servers with given role and status.
     58 
     59     @param hostname: hostname of the server.
     60     @param role: Role of server, default to None.
     61     @param status: Status of server, default to None.
     62 
     63     @return: A list of server objects with given role and status.
     64     """
     65     filters = {}
     66     if hostname:
     67         filters['hostname'] = hostname
     68     if role:
     69         filters['roles__role'] = role
     70     if status:
     71         filters['status'] = status
     72     return list(server_models.Server.objects.filter(**filters))
     73 
     74 
     75 def get_server_details(servers, table=False, summary=False):
     76     """Get a string of given servers' details.
     77 
     78     The method can return a string of server information in 3 different formats:
     79     A detail view:
     80         Hostname     : server2
     81         Status       : primary
     82         Roles        : drone
     83         Attributes   : {'max_processes':300}
     84         Date Created : 2014-11-25 12:00:00
     85         Date Modified: None
     86         Note         : Drone in lab1
     87     A table view:
     88         Hostname | Status  | Roles     | Date Created    | Date Modified | Note
     89         server1  | backup  | scheduler | 2014-11-25 23:45:19 |           |
     90         server2  | primary | drone     | 2014-11-25 12:00:00 |           | Drone
     91     A summary view:
     92         scheduler      : server1(backup), server3(primary),
     93         host_scheduler :
     94         drone          : server2(primary),
     95         devserver      :
     96         database       :
     97         suite_scheduler:
     98         crash_server   :
     99         No Role        :
    100 
    101     The method returns detail view of each server and a summary view by default.
    102     If `table` is set to True, only table view will be returned.
    103     If `summary` is set to True, only summary view will be returned.
    104 
    105     @param servers: A list of servers to get details.
    106     @param table: True to return a table view instead of a detail view,
    107                   default is set to False.
    108     @param summary: True to only show the summary of roles and status of
    109                     given servers.
    110 
    111     @return: A string of the information of given servers.
    112     """
    113     # Format string to display a table view.
    114     # Hostname, Status, Roles, Date Created, Date Modified, Note
    115     TABLEVIEW_FORMAT = ('%(hostname)-30s | %(status)-7s | %(roles)-20s | '
    116                         '%(date_created)-19s | %(date_modified)-19s | %(note)s')
    117 
    118     result = ''
    119     if not table and not summary:
    120         for server in servers:
    121             result += '\n' + str(server)
    122     elif table:
    123         result += (TABLEVIEW_FORMAT %
    124                    {'hostname':'Hostname', 'status':'Status',
    125                     'roles':'Roles', 'date_created':'Date Created',
    126                     'date_modified':'Date Modified', 'note':'Note'})
    127         for server in servers:
    128             roles = ','.join(server.get_role_names())
    129             result += '\n' + (TABLEVIEW_FORMAT %
    130                               {'hostname':server.hostname,
    131                                'status': server.status or '',
    132                                'roles': roles,
    133                                'date_created': server.date_created,
    134                                'date_modified': server.date_modified or '',
    135                                'note': server.note or ''})
    136     elif summary:
    137         result += 'Roles and status of servers:\n\n'
    138         for role, _ in server_models.ServerRole.ROLE.choices():
    139             servers_of_role = [s for s in servers if role in
    140                                [r.role for r in s.roles.all()]]
    141             result += '%-15s: ' % role
    142             for server in servers_of_role:
    143                 result += '%s(%s), ' % (server.hostname, server.status)
    144             result += '\n'
    145         servers_without_role = [s.hostname for s in servers
    146                                 if not s.roles.all()]
    147         result += '%-15s: %s' % ('No Role', ', '.join(servers_without_role))
    148 
    149     return result
    150 
    151 
    152 def check_server(hostname, role):
    153     """Confirm server with given hostname is ready to be primary of given role.
    154 
    155     If the server is a backup and failed to be verified for the role, remove
    156     the role from its roles list. If it has no other role, set its status to
    157     repair_required.
    158 
    159     @param hostname: hostname of the server.
    160     @param role: Role to be checked.
    161     @return: True if server can be verified for the given role, otherwise
    162              return False.
    163     """
    164     # TODO(dshi): Add more logic to confirm server is ready for the role.
    165     # For now, the function just checks if server is ssh-able.
    166     try:
    167         infra.execute_command(hostname, 'true')
    168         return True
    169     except subprocess.CalledProcessError as e:
    170         print >> sys.stderr, ('Failed to check server %s, error: %s' %
    171                               (hostname, e))
    172         return False
    173 
    174 
    175 def verify_server(exist=True):
    176     """Decorator to check if server with given hostname exists in the database.
    177 
    178     @param exist: Set to True to confirm server exists in the database, raise
    179                   exception if not. If it's set to False, raise exception if
    180                   server exists in database. Default is True.
    181 
    182     @raise ServerActionError: If `exist` is True and server does not exist in
    183                               the database, or `exist` is False and server exists
    184                               in the database.
    185     """
    186     def deco_verify(func):
    187         """Wrapper for the decorator.
    188 
    189         @param func: Function to be called.
    190         """
    191         def func_verify(*args, **kwargs):
    192             """Decorator to check if server exists.
    193 
    194             If exist is set to True, raise ServerActionError is server with
    195             given hostname is not found in server database.
    196             If exist is set to False, raise ServerActionError is server with
    197             given hostname is found in server database.
    198 
    199             @param func: function to be called.
    200             @param args: arguments for function to be called.
    201             @param kwargs: keyword arguments for function to be called.
    202             """
    203             hostname = kwargs['hostname']
    204             try:
    205                 server = server_models.Server.objects.get(hostname=hostname)
    206             except django.core.exceptions.ObjectDoesNotExist:
    207                 server = None
    208 
    209             if not exist and server:
    210                 raise ServerActionError('Server %s already exists.' %
    211                                         hostname)
    212             if exist and not server:
    213                 raise ServerActionError('Server %s does not exist in the '
    214                                         'database.' % hostname)
    215             if server:
    216                 kwargs['server'] = server
    217             return func(*args, **kwargs)
    218         return func_verify
    219     return deco_verify
    220 
    221 
    222 def get_drones():
    223     """Get a list of drones in status primary.
    224 
    225     @return: A list of drones in status primary.
    226     """
    227     servers = get_servers(role=server_models.ServerRole.ROLE.DRONE,
    228                           status=server_models.Server.STATUS.PRIMARY)
    229     return [s.hostname for s in servers]
    230 
    231 
    232 def delete_attribute(server, attribute):
    233     """Delete the attribute from the host.
    234 
    235     @param server: An object of server_models.Server.
    236     @param attribute: Name of an attribute of the server.
    237     """
    238     attributes = server.attributes.filter(attribute=attribute)
    239     if not attributes:
    240         raise ServerActionError('Server %s does not have attribute %s' %
    241                                 (server.hostname, attribute))
    242     attributes[0].delete()
    243     print 'Attribute %s is deleted from server %s.' % (attribute,
    244                                                        server.hostname)
    245 
    246 
    247 def change_attribute(server, attribute, value):
    248     """Change the value of an attribute of the server.
    249 
    250     @param server: An object of server_models.Server.
    251     @param attribute: Name of an attribute of the server.
    252     @param value: Value of the attribute of the server.
    253 
    254     @raise ServerActionError: If the attribute already exists and has the
    255                               given value.
    256     """
    257     attributes = server_models.ServerAttribute.objects.filter(
    258             server=server, attribute=attribute)
    259     if attributes and attributes[0].value == value:
    260         raise ServerActionError('Attribute %s for Server %s already has '
    261                                 'value of %s.' %
    262                                 (attribute, server.hostname, value))
    263     if attributes:
    264         old_value = attributes[0].value
    265         attributes[0].value = value
    266         attributes[0].save()
    267         print ('Attribute `%s` of server %s is changed from %s to %s.' %
    268                      (attribute, server.hostname, old_value, value))
    269     else:
    270         server_models.ServerAttribute.objects.create(
    271                 server=server, attribute=attribute, value=value)
    272         print ('Attribute `%s` of server %s is set to %s.' %
    273                (attribute, server.hostname, value))
    274 
    275 
    276 def get_shards():
    277     """Get a list of shards in status primary.
    278 
    279     @return: A list of shards in status primary.
    280     """
    281     servers = get_servers(role=server_models.ServerRole.ROLE.SHARD,
    282                           status=server_models.Server.STATUS.PRIMARY)
    283     return [s.hostname for s in servers]
    284 
    285 
    286 def confirm_server_has_role(hostname, role):
    287     """Confirm a given server has the given role, and its status is primary.
    288 
    289     @param hostname: hostname of the server.
    290     @param role: Name of the role to be checked.
    291     @raise ServerActionError: If localhost does not have given role or it's
    292                               not in primary status.
    293     """
    294     if hostname.lower() in ['localhost', '127.0.0.1']:
    295         hostname = socket.gethostname()
    296     hostname = utils.normalize_hostname(hostname)
    297 
    298     servers = get_servers(role=role, status=server_models.Server.STATUS.PRIMARY)
    299     for server in servers:
    300         if hostname == utils.normalize_hostname(server.hostname):
    301             return True
    302     raise ServerActionError('Server %s does not have role of %s running in '
    303                             'status primary.' % (hostname, role))
    304