Home | History | Annotate | Download | only in cli
      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 """
      6 The server module contains the objects and methods used to manage servers in
      7 Autotest.
      8 
      9 The valid actions are:
     10 list:      list all servers in the database
     11 create:    create a server
     12 delete:    deletes a server
     13 modify:    modify a server's role or status.
     14 
     15 The common options are:
     16 --role / -r:     role that's related to server actions.
     17 
     18 See topic_common.py for a High Level Design and Algorithm.
     19 """
     20 
     21 import common
     22 
     23 from autotest_lib.cli import action_common
     24 from autotest_lib.cli import topic_common
     25 from autotest_lib.client.common_lib import error
     26 # The django setup is moved here as test_that uses sqlite setup. If this line
     27 # is in server_manager, test_that unittest will fail.
     28 from autotest_lib.frontend import setup_django_environment
     29 from autotest_lib.site_utils import server_manager
     30 from autotest_lib.site_utils import server_manager_utils
     31 
     32 
     33 class server(topic_common.atest):
     34     """Server class
     35 
     36     atest server [list|create|delete|modify] <options>
     37     """
     38     usage_action = '[list|create|delete|modify]'
     39     topic = msg_topic = 'server'
     40     msg_items = '<server>'
     41 
     42     def __init__(self, hostname_required=True):
     43         """Add to the parser the options common to all the server actions.
     44 
     45         @param hostname_required: True to require the command has hostname
     46                                   specified. Default is True.
     47         """
     48         super(server, self).__init__()
     49 
     50         self.parser.add_option('-r', '--role',
     51                                help='Name of a role',
     52                                type='string',
     53                                default=None,
     54                                metavar='ROLE')
     55         self.parser.add_option('-x', '--action',
     56                                help=('Set to True to apply actions when role '
     57                                      'or status is changed, e.g., restart '
     58                                      'scheduler when a drone is removed.'),
     59                                action='store_true',
     60                                default=False,
     61                                metavar='ACTION')
     62 
     63         self.topic_parse_info = topic_common.item_parse_info(
     64                 attribute_name='hostname', use_leftover=True)
     65 
     66         self.hostname_required = hostname_required
     67 
     68 
     69     def parse(self):
     70         """Parse command arguments.
     71         """
     72         role_info = topic_common.item_parse_info(attribute_name='role')
     73         kwargs = {}
     74         if self.hostname_required:
     75             kwargs['req_items'] = 'hostname'
     76         (options, leftover) = super(server, self).parse([role_info], **kwargs)
     77         if options.web_server:
     78             self.invalid_syntax('Server actions will access server database '
     79                                 'defined in your local global config. It does '
     80                                 'not rely on RPC, no autotest server needs to '
     81                                 'be specified.')
     82 
     83         # self.hostname is a list. Action on server only needs one hostname at
     84         # most.
     85         if ((not self.hostname and self.hostname_required) or
     86             len(self.hostname) > 1):
     87             self.invalid_syntax('`server` topic can only manipulate 1 server. '
     88                                 'Use -h to see available options.')
     89         if self.hostname:
     90             # Override self.hostname with the first hostname in the list.
     91             self.hostname = self.hostname[0]
     92         self.role = options.role
     93         return (options, leftover)
     94 
     95 
     96     def output(self, results):
     97         """Display output.
     98 
     99         For most actions, the return is a string message, no formating needed.
    100 
    101         @param results: return of the execute call.
    102         """
    103         print results
    104 
    105 
    106 class server_help(server):
    107     """Just here to get the atest logic working. Usage is set by its parent.
    108     """
    109     pass
    110 
    111 
    112 class server_list(action_common.atest_list, server):
    113     """atest server list [--role <role>]"""
    114 
    115     def __init__(self):
    116         """Initializer.
    117         """
    118         super(server_list, self).__init__(hostname_required=False)
    119         self.parser.add_option('-t', '--table',
    120                                help=('List details of all servers in a table, '
    121                                      'e.g., \tHostname | Status  | Roles     | '
    122                                      'note\t\tserver1  | primary | scheduler | '
    123                                      'lab'),
    124                                action='store_true',
    125                                default=False)
    126         self.parser.add_option('-s', '--status',
    127                                help='Only show servers with given status',
    128                                type='string',
    129                                default=None,
    130                                metavar='STATUS')
    131         self.parser.add_option('-u', '--summary',
    132                                help=('Show the summary of roles and status '
    133                                      'only, e.g.,\tscheduler: server1(primary) '
    134                                      'server2(backup)\t\tdrone: server3(primary'
    135                                      ') server4(backup)'),
    136                                action='store_true',
    137                                default=False)
    138         self.parser.add_option('--json',
    139                                help='Format output as JSON.',
    140                                action='store_true',
    141                                default=False)
    142 
    143 
    144     def parse(self):
    145         """Parse command arguments.
    146         """
    147         (options, leftover) = super(server_list, self).parse()
    148         self.json = options.json
    149         self.table = options.table
    150         self.status = options.status
    151         self.summary = options.summary
    152         if self.table and self.summary:
    153             self.invalid_syntax('Option --table and --summary cannot be both '
    154                                 'specified.')
    155         return (options, leftover)
    156 
    157 
    158     def execute(self):
    159         """Execute the command.
    160 
    161         @return: A list of servers matched given hostname and role.
    162         """
    163         try:
    164             return server_manager_utils.get_servers(hostname=self.hostname,
    165                                                     role=self.role,
    166                                                     status=self.status)
    167         except (server_manager_utils.ServerActionError,
    168                 error.InvalidDataError) as e:
    169             self.failure(e, what_failed='Failed to find servers',
    170                          item=self.hostname, fatal=True)
    171 
    172 
    173     def output(self, results):
    174         """Display output.
    175 
    176         @param results: return of the execute call, a list of server object that
    177                         contains server information.
    178         """
    179         if results:
    180             if self.json:
    181                 formatter = server_manager_utils.format_servers_json
    182             elif self.table:
    183                 formatter = server_manager_utils.format_servers_table
    184             elif self.summary:
    185                 formatter = server_manager_utils.format_servers_summary
    186             else:
    187                 formatter = server_manager_utils.format_servers
    188             print formatter(results)
    189         else:
    190             self.failure('No server is found.',
    191                          what_failed='Failed to find servers',
    192                          item=self.hostname, fatal=True)
    193 
    194 
    195 class server_create(server):
    196     """atest server create hostname --role <role> --note <note>
    197     """
    198 
    199     def __init__(self):
    200         """Initializer.
    201         """
    202         super(server_create, self).__init__()
    203         self.parser.add_option('-n', '--note',
    204                                help='note of the server',
    205                                type='string',
    206                                default=None,
    207                                metavar='NOTE')
    208 
    209 
    210     def parse(self):
    211         """Parse command arguments.
    212         """
    213         (options, leftover) = super(server_create, self).parse()
    214         self.note = options.note
    215 
    216         if not self.role:
    217             self.invalid_syntax('--role is required to create a server.')
    218 
    219         return (options, leftover)
    220 
    221 
    222     def execute(self):
    223         """Execute the command.
    224 
    225         @return: A Server object if it is created successfully.
    226         """
    227         try:
    228             return server_manager.create(hostname=self.hostname, role=self.role,
    229                                          note=self.note)
    230         except (server_manager_utils.ServerActionError,
    231                 error.InvalidDataError) as e:
    232             self.failure(e, what_failed='Failed to create server',
    233                          item=self.hostname, fatal=True)
    234 
    235 
    236     def output(self, results):
    237         """Display output.
    238 
    239         @param results: return of the execute call, a server object that
    240                         contains server information.
    241         """
    242         if results:
    243             print 'Server %s is added to server database:\n' % self.hostname
    244             print results
    245 
    246 
    247 class server_delete(server):
    248     """atest server delete hostname"""
    249 
    250     def execute(self):
    251         """Execute the command.
    252 
    253         @return: True if server is deleted successfully.
    254         """
    255         try:
    256             server_manager.delete(hostname=self.hostname)
    257             return True
    258         except (server_manager_utils.ServerActionError,
    259                 error.InvalidDataError) as e:
    260             self.failure(e, what_failed='Failed to delete server',
    261                          item=self.hostname, fatal=True)
    262 
    263 
    264     def output(self, results):
    265         """Display output.
    266 
    267         @param results: return of the execute call.
    268         """
    269         if results:
    270             print ('Server %s is deleted from server database successfully.' %
    271                    self.hostname)
    272 
    273 
    274 class server_modify(server):
    275     """atest server modify hostname
    276 
    277     modify action can only change one input at a time. Available inputs are:
    278     --status:       Status of the server.
    279     --note:         Note of the server.
    280     --role:         New role to be added to the server.
    281     --delete_role:  Existing role to be deleted from the server.
    282     """
    283 
    284     def __init__(self):
    285         """Initializer.
    286         """
    287         super(server_modify, self).__init__()
    288         self.parser.add_option('-s', '--status',
    289                                help='Status of the server',
    290                                type='string',
    291                                metavar='STATUS')
    292         self.parser.add_option('-n', '--note',
    293                                help='Note of the server',
    294                                type='string',
    295                                default=None,
    296                                metavar='NOTE')
    297         self.parser.add_option('-d', '--delete',
    298                                help=('Set to True to delete given role.'),
    299                                action='store_true',
    300                                default=False,
    301                                metavar='DELETE')
    302         self.parser.add_option('-a', '--attribute',
    303                                help='Name of the attribute of the server',
    304                                type='string',
    305                                default=None,
    306                                metavar='ATTRIBUTE')
    307         self.parser.add_option('-e', '--value',
    308                                help='Value for the attribute of the server',
    309                                type='string',
    310                                default=None,
    311                                metavar='VALUE')
    312 
    313 
    314     def parse(self):
    315         """Parse command arguments.
    316         """
    317         (options, leftover) = super(server_modify, self).parse()
    318         self.status = options.status
    319         self.note = options.note
    320         self.delete = options.delete
    321         self.attribute = options.attribute
    322         self.value = options.value
    323         self.action = options.action
    324 
    325         # modify supports various options. However, it's safer to limit one
    326         # option at a time so no complicated role-dependent logic is needed
    327         # to handle scenario that both role and status are changed.
    328         # self.parser is optparse, which does not have function in argparse like
    329         # add_mutually_exclusive_group. That's why the count is used here.
    330         flags = [self.status is not None, self.role is not None,
    331                  self.attribute is not None, self.note is not None]
    332         if flags.count(True) != 1:
    333             msg = ('Action modify only support one option at a time. You can '
    334                    'try one of following 5 options:\n'
    335                    '1. --status:                Change server\'s status.\n'
    336                    '2. --note:                  Change server\'s note.\n'
    337                    '3. --role with optional -d: Add/delete role from server.\n'
    338                    '4. --attribute --value:     Set/change the value of a '
    339                    'server\'s attribute.\n'
    340                    '5. --attribute -d:          Delete the attribute from the '
    341                    'server.\n'
    342                    '\nUse option -h to see a complete list of options.')
    343             self.invalid_syntax(msg)
    344         if (self.status != None or self.note != None) and self.delete:
    345             self.invalid_syntax('--delete does not apply to status or note.')
    346         if self.attribute != None and not self.delete and self.value == None:
    347             self.invalid_syntax('--attribute must be used with option --value '
    348                                 'or --delete.')
    349         return (options, leftover)
    350 
    351 
    352     def execute(self):
    353         """Execute the command.
    354 
    355         @return: The updated server object if it is modified successfully.
    356         """
    357         try:
    358             return server_manager.modify(hostname=self.hostname, role=self.role,
    359                                          status=self.status, delete=self.delete,
    360                                          note=self.note,
    361                                          attribute=self.attribute,
    362                                          value=self.value, action=self.action)
    363         except (server_manager_utils.ServerActionError,
    364                 error.InvalidDataError) as e:
    365             self.failure(e, what_failed='Failed to modify server',
    366                          item=self.hostname, fatal=True)
    367 
    368 
    369     def output(self, results):
    370         """Display output.
    371 
    372         @param results: return of the execute call, which is the updated server
    373                         object.
    374         """
    375         if results:
    376             print 'Server %s is modified successfully.' % self.hostname
    377             print results
    378