1*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Li""" 6*9c5db199SXin LiThe server module contains the objects and methods used to manage servers in 7*9c5db199SXin LiAutotest. 8*9c5db199SXin Li 9*9c5db199SXin LiThe valid actions are: 10*9c5db199SXin Lilist: list all servers in the database 11*9c5db199SXin Licreate: create a server 12*9c5db199SXin Lidelete: deletes a server 13*9c5db199SXin Limodify: modify a server's role or status. 14*9c5db199SXin Li 15*9c5db199SXin LiThe common options are: 16*9c5db199SXin Li--role / -r: role that's related to server actions. 17*9c5db199SXin Li 18*9c5db199SXin LiSee topic_common.py for a High Level Design and Algorithm. 19*9c5db199SXin Li""" 20*9c5db199SXin Li 21*9c5db199SXin Lifrom __future__ import print_function 22*9c5db199SXin Li 23*9c5db199SXin Liimport common 24*9c5db199SXin Li 25*9c5db199SXin Lifrom autotest_lib.cli import action_common 26*9c5db199SXin Lifrom autotest_lib.cli import skylab_utils 27*9c5db199SXin Lifrom autotest_lib.cli import topic_common 28*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 29*9c5db199SXin Lifrom autotest_lib.client.common_lib import revision_control 30*9c5db199SXin Li# The django setup is moved here as test_that uses sqlite setup. If this line 31*9c5db199SXin Li# is in server_manager, test_that unittest will fail. 32*9c5db199SXin Lifrom autotest_lib.frontend import setup_django_environment 33*9c5db199SXin Li 34*9c5db199SXin Litry: 35*9c5db199SXin Li from skylab_inventory import text_manager 36*9c5db199SXin Li from skylab_inventory import translation_utils 37*9c5db199SXin Li from skylab_inventory.lib import server as skylab_server 38*9c5db199SXin Liexcept ImportError: 39*9c5db199SXin Li pass 40*9c5db199SXin Li 41*9c5db199SXin Li 42*9c5db199SXin LiATEST_DISABLE_MSG = ('Updating server_db via atest server command has been ' 43*9c5db199SXin Li 'disabled. Please use use go/cros-infra-inventory-tool ' 44*9c5db199SXin Li 'to update it in skylab inventory service.') 45*9c5db199SXin Li 46*9c5db199SXin Li 47*9c5db199SXin Liclass server(topic_common.atest): 48*9c5db199SXin Li """Server class 49*9c5db199SXin Li 50*9c5db199SXin Li atest server [list|create|delete|modify] <options> 51*9c5db199SXin Li """ 52*9c5db199SXin Li usage_action = '[list|create|delete|modify]' 53*9c5db199SXin Li topic = msg_topic = 'server' 54*9c5db199SXin Li msg_items = '<server>' 55*9c5db199SXin Li 56*9c5db199SXin Li def __init__(self, hostname_required=True, allow_multiple_hostname=False): 57*9c5db199SXin Li """Add to the parser the options common to all the server actions. 58*9c5db199SXin Li 59*9c5db199SXin Li @param hostname_required: True to require the command has hostname 60*9c5db199SXin Li specified. Default is True. 61*9c5db199SXin Li """ 62*9c5db199SXin Li super(server, self).__init__() 63*9c5db199SXin Li 64*9c5db199SXin Li self.parser.add_option('-r', '--role', 65*9c5db199SXin Li help='Name of a role', 66*9c5db199SXin Li type='string', 67*9c5db199SXin Li default=None, 68*9c5db199SXin Li metavar='ROLE') 69*9c5db199SXin Li self.parser.add_option('-x', '--action', 70*9c5db199SXin Li help=('Set to True to apply actions when role ' 71*9c5db199SXin Li 'or status is changed, e.g., restart ' 72*9c5db199SXin Li 'scheduler when a drone is removed. %s' % 73*9c5db199SXin Li skylab_utils.MSG_INVALID_IN_SKYLAB), 74*9c5db199SXin Li action='store_true', 75*9c5db199SXin Li default=False, 76*9c5db199SXin Li metavar='ACTION') 77*9c5db199SXin Li 78*9c5db199SXin Li self.add_skylab_options(enforce_skylab=True) 79*9c5db199SXin Li 80*9c5db199SXin Li self.topic_parse_info = topic_common.item_parse_info( 81*9c5db199SXin Li attribute_name='hostname', use_leftover=True) 82*9c5db199SXin Li 83*9c5db199SXin Li self.hostname_required = hostname_required 84*9c5db199SXin Li self.allow_multiple_hostname = allow_multiple_hostname 85*9c5db199SXin Li 86*9c5db199SXin Li 87*9c5db199SXin Li def parse(self): 88*9c5db199SXin Li """Parse command arguments. 89*9c5db199SXin Li """ 90*9c5db199SXin Li role_info = topic_common.item_parse_info(attribute_name='role') 91*9c5db199SXin Li kwargs = {} 92*9c5db199SXin Li if self.hostname_required: 93*9c5db199SXin Li kwargs['req_items'] = 'hostname' 94*9c5db199SXin Li (options, leftover) = super(server, self).parse([role_info], **kwargs) 95*9c5db199SXin Li if options.web_server: 96*9c5db199SXin Li self.invalid_syntax('Server actions will access server database ' 97*9c5db199SXin Li 'defined in your local global config. It does ' 98*9c5db199SXin Li 'not rely on RPC, no autotest server needs to ' 99*9c5db199SXin Li 'be specified.') 100*9c5db199SXin Li 101*9c5db199SXin Li # self.hostname is a list. Action on server only needs one hostname at 102*9c5db199SXin Li # most. 103*9c5db199SXin Li if (not self.hostname and self.hostname_required): 104*9c5db199SXin Li self.invalid_syntax('`server` topic requires hostname. ' 105*9c5db199SXin Li 'Use -h to see available options.') 106*9c5db199SXin Li 107*9c5db199SXin Li if (self.hostname_required and not self.allow_multiple_hostname and 108*9c5db199SXin Li len(self.hostname) > 1): 109*9c5db199SXin Li self.invalid_syntax('`server` topic can only manipulate 1 server. ' 110*9c5db199SXin Li 'Use -h to see available options.') 111*9c5db199SXin Li 112*9c5db199SXin Li if self.hostname: 113*9c5db199SXin Li if not self.allow_multiple_hostname or not self.skylab: 114*9c5db199SXin Li # Only support create multiple servers in skylab. 115*9c5db199SXin Li # Override self.hostname with the first hostname in the list. 116*9c5db199SXin Li self.hostname = self.hostname[0] 117*9c5db199SXin Li 118*9c5db199SXin Li self.role = options.role 119*9c5db199SXin Li 120*9c5db199SXin Li if self.skylab and self.role: 121*9c5db199SXin Li translation_utils.validate_server_role(self.role) 122*9c5db199SXin Li 123*9c5db199SXin Li return (options, leftover) 124*9c5db199SXin Li 125*9c5db199SXin Li 126*9c5db199SXin Li def output(self, results): 127*9c5db199SXin Li """Display output. 128*9c5db199SXin Li 129*9c5db199SXin Li For most actions, the return is a string message, no formating needed. 130*9c5db199SXin Li 131*9c5db199SXin Li @param results: return of the execute call. 132*9c5db199SXin Li """ 133*9c5db199SXin Li print(results) 134*9c5db199SXin Li 135*9c5db199SXin Li 136*9c5db199SXin Liclass server_help(server): 137*9c5db199SXin Li """Just here to get the atest logic working. Usage is set by its parent. 138*9c5db199SXin Li """ 139*9c5db199SXin Li pass 140*9c5db199SXin Li 141*9c5db199SXin Li 142*9c5db199SXin Liclass server_list(action_common.atest_list, server): 143*9c5db199SXin Li """atest server list [--role <role>]""" 144*9c5db199SXin Li 145*9c5db199SXin Li def __init__(self): 146*9c5db199SXin Li """Initializer. 147*9c5db199SXin Li """ 148*9c5db199SXin Li super(server_list, self).__init__(hostname_required=False) 149*9c5db199SXin Li 150*9c5db199SXin Li self.parser.add_option('-s', '--status', 151*9c5db199SXin Li help='Only show servers with given status.', 152*9c5db199SXin Li type='string', 153*9c5db199SXin Li default=None, 154*9c5db199SXin Li metavar='STATUS') 155*9c5db199SXin Li self.parser.add_option('--json', 156*9c5db199SXin Li help=('Format output as JSON.'), 157*9c5db199SXin Li action='store_true', 158*9c5db199SXin Li default=False) 159*9c5db199SXin Li self.parser.add_option('-N', '--hostnames-only', 160*9c5db199SXin Li help=('Only return hostnames.'), 161*9c5db199SXin Li action='store_true', 162*9c5db199SXin Li default=False) 163*9c5db199SXin Li # TODO(crbug.com/850344): support '--table' and '--summary' formats. 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Li def parse(self): 167*9c5db199SXin Li """Parse command arguments. 168*9c5db199SXin Li """ 169*9c5db199SXin Li (options, leftover) = super(server_list, self).parse() 170*9c5db199SXin Li self.json = options.json 171*9c5db199SXin Li self.status = options.status 172*9c5db199SXin Li self.namesonly = options.hostnames_only 173*9c5db199SXin Li 174*9c5db199SXin Li if sum([self.json, self.namesonly]) > 1: 175*9c5db199SXin Li self.invalid_syntax('May only specify up to 1 output-format flag.') 176*9c5db199SXin Li return (options, leftover) 177*9c5db199SXin Li 178*9c5db199SXin Li 179*9c5db199SXin Li def execute_skylab(self): 180*9c5db199SXin Li """Execute 'atest server list --skylab' 181*9c5db199SXin Li 182*9c5db199SXin Li @return: A list of servers matched the given hostname and role. 183*9c5db199SXin Li """ 184*9c5db199SXin Li inventory_repo = skylab_utils.InventoryRepo( 185*9c5db199SXin Li self.inventory_repo_dir) 186*9c5db199SXin Li inventory_repo.initialize() 187*9c5db199SXin Li infrastructure = text_manager.load_infrastructure( 188*9c5db199SXin Li inventory_repo.get_data_dir()) 189*9c5db199SXin Li 190*9c5db199SXin Li return skylab_server.get_servers( 191*9c5db199SXin Li infrastructure, 192*9c5db199SXin Li self.environment, 193*9c5db199SXin Li hostname=self.hostname, 194*9c5db199SXin Li role=self.role, 195*9c5db199SXin Li status=self.status) 196*9c5db199SXin Li 197*9c5db199SXin Li 198*9c5db199SXin Li def execute(self): 199*9c5db199SXin Li """Execute the command. 200*9c5db199SXin Li 201*9c5db199SXin Li @return: A list of servers matched given hostname and role. 202*9c5db199SXin Li """ 203*9c5db199SXin Li if self.skylab: 204*9c5db199SXin Li try: 205*9c5db199SXin Li return self.execute_skylab() 206*9c5db199SXin Li except (skylab_server.SkylabServerActionError, 207*9c5db199SXin Li revision_control.GitError, 208*9c5db199SXin Li skylab_utils.InventoryRepoDirNotClean) as e: 209*9c5db199SXin Li self.failure(e, what_failed='Failed to list servers from skylab' 210*9c5db199SXin Li ' inventory.', item=self.hostname, fatal=True) 211*9c5db199SXin Li else: 212*9c5db199SXin Li return None 213*9c5db199SXin Li 214*9c5db199SXin Li 215*9c5db199SXin Li def output(self, results): 216*9c5db199SXin Li """Display output. 217*9c5db199SXin Li 218*9c5db199SXin Li @param results: return of the execute call, a list of server object that 219*9c5db199SXin Li contains server information. 220*9c5db199SXin Li """ 221*9c5db199SXin Li if results: 222*9c5db199SXin Li if self.json: 223*9c5db199SXin Li if self.skylab: 224*9c5db199SXin Li formatter = skylab_server.format_servers_json 225*9c5db199SXin Li else: 226*9c5db199SXin Li return None 227*9c5db199SXin Li elif self.namesonly: 228*9c5db199SXin Li return None 229*9c5db199SXin Li else: 230*9c5db199SXin Li return None 231*9c5db199SXin Li print(formatter(results)) 232*9c5db199SXin Li else: 233*9c5db199SXin Li self.failure('No server is found.', 234*9c5db199SXin Li what_failed='Failed to find servers', 235*9c5db199SXin Li item=self.hostname, fatal=True) 236*9c5db199SXin Li 237*9c5db199SXin Li 238*9c5db199SXin Liclass server_create(server): 239*9c5db199SXin Li """atest server create hostname --role <role> --note <note> 240*9c5db199SXin Li """ 241*9c5db199SXin Li 242*9c5db199SXin Li def __init__(self): 243*9c5db199SXin Li """Initializer. 244*9c5db199SXin Li """ 245*9c5db199SXin Li super(server_create, self).__init__(allow_multiple_hostname=True) 246*9c5db199SXin Li self.parser.add_option('-n', '--note', 247*9c5db199SXin Li help='note of the server', 248*9c5db199SXin Li type='string', 249*9c5db199SXin Li default=None, 250*9c5db199SXin Li metavar='NOTE') 251*9c5db199SXin Li 252*9c5db199SXin Li 253*9c5db199SXin Li def parse(self): 254*9c5db199SXin Li """Parse command arguments. 255*9c5db199SXin Li """ 256*9c5db199SXin Li (options, leftover) = super(server_create, self).parse() 257*9c5db199SXin Li self.note = options.note 258*9c5db199SXin Li 259*9c5db199SXin Li if not self.role: 260*9c5db199SXin Li self.invalid_syntax('--role is required to create a server.') 261*9c5db199SXin Li 262*9c5db199SXin Li return (options, leftover) 263*9c5db199SXin Li 264*9c5db199SXin Li 265*9c5db199SXin Li def execute_skylab(self): 266*9c5db199SXin Li """Execute the command for skylab inventory changes.""" 267*9c5db199SXin Li inventory_repo = skylab_utils.InventoryRepo( 268*9c5db199SXin Li self.inventory_repo_dir) 269*9c5db199SXin Li inventory_repo.initialize() 270*9c5db199SXin Li data_dir = inventory_repo.get_data_dir() 271*9c5db199SXin Li infrastructure = text_manager.load_infrastructure(data_dir) 272*9c5db199SXin Li 273*9c5db199SXin Li new_servers = [] 274*9c5db199SXin Li for hostname in self.hostname: 275*9c5db199SXin Li new_servers.append(skylab_server.create( 276*9c5db199SXin Li infrastructure, 277*9c5db199SXin Li hostname, 278*9c5db199SXin Li self.environment, 279*9c5db199SXin Li role=self.role, 280*9c5db199SXin Li note=self.note)) 281*9c5db199SXin Li text_manager.dump_infrastructure(data_dir, infrastructure) 282*9c5db199SXin Li 283*9c5db199SXin Li message = skylab_utils.construct_commit_message( 284*9c5db199SXin Li 'Add new server: %s' % self.hostname) 285*9c5db199SXin Li self.change_number = inventory_repo.upload_change( 286*9c5db199SXin Li message, draft=self.draft, dryrun=self.dryrun, 287*9c5db199SXin Li submit=self.submit) 288*9c5db199SXin Li 289*9c5db199SXin Li return new_servers 290*9c5db199SXin Li 291*9c5db199SXin Li 292*9c5db199SXin Li def execute(self): 293*9c5db199SXin Li """Execute the command. 294*9c5db199SXin Li 295*9c5db199SXin Li @return: A Server object if it is created successfully. 296*9c5db199SXin Li """ 297*9c5db199SXin Li self.failure(ATEST_DISABLE_MSG, 298*9c5db199SXin Li what_failed='Failed to create server', 299*9c5db199SXin Li item=self.hostname, 300*9c5db199SXin Li fatal=True) 301*9c5db199SXin Li 302*9c5db199SXin Li 303*9c5db199SXin Li def output(self, results): 304*9c5db199SXin Li """Display output. 305*9c5db199SXin Li 306*9c5db199SXin Li @param results: return of the execute call, a server object that 307*9c5db199SXin Li contains server information. 308*9c5db199SXin Li """ 309*9c5db199SXin Li if results: 310*9c5db199SXin Li print('Server %s is added.\n' % self.hostname) 311*9c5db199SXin Li print(results) 312*9c5db199SXin Li 313*9c5db199SXin Li if self.skylab and not self.dryrun and not self.submit: 314*9c5db199SXin Li print(skylab_utils.get_cl_message(self.change_number)) 315*9c5db199SXin Li 316*9c5db199SXin Li 317*9c5db199SXin Li 318*9c5db199SXin Liclass server_delete(server): 319*9c5db199SXin Li """atest server delete hostname""" 320*9c5db199SXin Li 321*9c5db199SXin Li def execute_skylab(self): 322*9c5db199SXin Li """Execute the command for skylab inventory changes.""" 323*9c5db199SXin Li inventory_repo = skylab_utils.InventoryRepo( 324*9c5db199SXin Li self.inventory_repo_dir) 325*9c5db199SXin Li inventory_repo.initialize() 326*9c5db199SXin Li data_dir = inventory_repo.get_data_dir() 327*9c5db199SXin Li infrastructure = text_manager.load_infrastructure(data_dir) 328*9c5db199SXin Li 329*9c5db199SXin Li skylab_server.delete(infrastructure, self.hostname, self.environment) 330*9c5db199SXin Li text_manager.dump_infrastructure(data_dir, infrastructure) 331*9c5db199SXin Li 332*9c5db199SXin Li message = skylab_utils.construct_commit_message( 333*9c5db199SXin Li 'Delete server: %s' % self.hostname) 334*9c5db199SXin Li self.change_number = inventory_repo.upload_change( 335*9c5db199SXin Li message, draft=self.draft, dryrun=self.dryrun, 336*9c5db199SXin Li submit=self.submit) 337*9c5db199SXin Li 338*9c5db199SXin Li 339*9c5db199SXin Li def execute(self): 340*9c5db199SXin Li """Execute the command. 341*9c5db199SXin Li 342*9c5db199SXin Li @return: True if server is deleted successfully. 343*9c5db199SXin Li """ 344*9c5db199SXin Li self.failure(ATEST_DISABLE_MSG, 345*9c5db199SXin Li what_failed='Failed to delete server', 346*9c5db199SXin Li item=self.hostname, 347*9c5db199SXin Li fatal=True) 348*9c5db199SXin Li 349*9c5db199SXin Li 350*9c5db199SXin Li def output(self, results): 351*9c5db199SXin Li """Display output. 352*9c5db199SXin Li 353*9c5db199SXin Li @param results: return of the execute call. 354*9c5db199SXin Li """ 355*9c5db199SXin Li if results: 356*9c5db199SXin Li print('Server %s is deleted.\n' % 357*9c5db199SXin Li self.hostname) 358*9c5db199SXin Li 359*9c5db199SXin Li if self.skylab and not self.dryrun and not self.submit: 360*9c5db199SXin Li print(skylab_utils.get_cl_message(self.change_number)) 361*9c5db199SXin Li 362*9c5db199SXin Li 363*9c5db199SXin Li 364*9c5db199SXin Liclass server_modify(server): 365*9c5db199SXin Li """atest server modify hostname 366*9c5db199SXin Li 367*9c5db199SXin Li modify action can only change one input at a time. Available inputs are: 368*9c5db199SXin Li --status: Status of the server. 369*9c5db199SXin Li --note: Note of the server. 370*9c5db199SXin Li --role: New role to be added to the server. 371*9c5db199SXin Li --delete_role: Existing role to be deleted from the server. 372*9c5db199SXin Li """ 373*9c5db199SXin Li 374*9c5db199SXin Li def __init__(self): 375*9c5db199SXin Li """Initializer. 376*9c5db199SXin Li """ 377*9c5db199SXin Li super(server_modify, self).__init__() 378*9c5db199SXin Li self.parser.add_option('-s', '--status', 379*9c5db199SXin Li help='Status of the server', 380*9c5db199SXin Li type='string', 381*9c5db199SXin Li metavar='STATUS') 382*9c5db199SXin Li self.parser.add_option('-n', '--note', 383*9c5db199SXin Li help='Note of the server', 384*9c5db199SXin Li type='string', 385*9c5db199SXin Li default=None, 386*9c5db199SXin Li metavar='NOTE') 387*9c5db199SXin Li self.parser.add_option('-d', '--delete', 388*9c5db199SXin Li help=('Set to True to delete given role.'), 389*9c5db199SXin Li action='store_true', 390*9c5db199SXin Li default=False, 391*9c5db199SXin Li metavar='DELETE') 392*9c5db199SXin Li self.parser.add_option('-a', '--attribute', 393*9c5db199SXin Li help='Name of the attribute of the server', 394*9c5db199SXin Li type='string', 395*9c5db199SXin Li default=None, 396*9c5db199SXin Li metavar='ATTRIBUTE') 397*9c5db199SXin Li self.parser.add_option('-e', '--value', 398*9c5db199SXin Li help='Value for the attribute of the server', 399*9c5db199SXin Li type='string', 400*9c5db199SXin Li default=None, 401*9c5db199SXin Li metavar='VALUE') 402*9c5db199SXin Li 403*9c5db199SXin Li 404*9c5db199SXin Li def parse(self): 405*9c5db199SXin Li """Parse command arguments. 406*9c5db199SXin Li """ 407*9c5db199SXin Li (options, leftover) = super(server_modify, self).parse() 408*9c5db199SXin Li self.status = options.status 409*9c5db199SXin Li self.note = options.note 410*9c5db199SXin Li self.delete = options.delete 411*9c5db199SXin Li self.attribute = options.attribute 412*9c5db199SXin Li self.value = options.value 413*9c5db199SXin Li self.action = options.action 414*9c5db199SXin Li 415*9c5db199SXin Li # modify supports various options. However, it's safer to limit one 416*9c5db199SXin Li # option at a time so no complicated role-dependent logic is needed 417*9c5db199SXin Li # to handle scenario that both role and status are changed. 418*9c5db199SXin Li # self.parser is optparse, which does not have function in argparse like 419*9c5db199SXin Li # add_mutually_exclusive_group. That's why the count is used here. 420*9c5db199SXin Li flags = [self.status is not None, self.role is not None, 421*9c5db199SXin Li self.attribute is not None, self.note is not None] 422*9c5db199SXin Li if flags.count(True) != 1: 423*9c5db199SXin Li msg = ('Action modify only support one option at a time. You can ' 424*9c5db199SXin Li 'try one of following 5 options:\n' 425*9c5db199SXin Li '1. --status: Change server\'s status.\n' 426*9c5db199SXin Li '2. --note: Change server\'s note.\n' 427*9c5db199SXin Li '3. --role with optional -d: Add/delete role from server.\n' 428*9c5db199SXin Li '4. --attribute --value: Set/change the value of a ' 429*9c5db199SXin Li 'server\'s attribute.\n' 430*9c5db199SXin Li '5. --attribute -d: Delete the attribute from the ' 431*9c5db199SXin Li 'server.\n' 432*9c5db199SXin Li '\nUse option -h to see a complete list of options.') 433*9c5db199SXin Li self.invalid_syntax(msg) 434*9c5db199SXin Li if (self.status != None or self.note != None) and self.delete: 435*9c5db199SXin Li self.invalid_syntax('--delete does not apply to status or note.') 436*9c5db199SXin Li if self.attribute != None and not self.delete and self.value == None: 437*9c5db199SXin Li self.invalid_syntax('--attribute must be used with option --value ' 438*9c5db199SXin Li 'or --delete.') 439*9c5db199SXin Li 440*9c5db199SXin Li # TODO(nxia): crbug.com/832964 support --action with --skylab 441*9c5db199SXin Li if self.skylab and self.action: 442*9c5db199SXin Li self.invalid_syntax('--action is currently not supported with' 443*9c5db199SXin Li ' --skylab.') 444*9c5db199SXin Li 445*9c5db199SXin Li return (options, leftover) 446*9c5db199SXin Li 447*9c5db199SXin Li 448*9c5db199SXin Li def execute_skylab(self): 449*9c5db199SXin Li """Execute the command for skylab inventory changes.""" 450*9c5db199SXin Li inventory_repo = skylab_utils.InventoryRepo( 451*9c5db199SXin Li self.inventory_repo_dir) 452*9c5db199SXin Li inventory_repo.initialize() 453*9c5db199SXin Li data_dir = inventory_repo.get_data_dir() 454*9c5db199SXin Li infrastructure = text_manager.load_infrastructure(data_dir) 455*9c5db199SXin Li 456*9c5db199SXin Li target_server = skylab_server.modify( 457*9c5db199SXin Li infrastructure, 458*9c5db199SXin Li self.hostname, 459*9c5db199SXin Li self.environment, 460*9c5db199SXin Li role=self.role, 461*9c5db199SXin Li status=self.status, 462*9c5db199SXin Li delete_role=self.delete, 463*9c5db199SXin Li note=self.note, 464*9c5db199SXin Li attribute=self.attribute, 465*9c5db199SXin Li value=self.value, 466*9c5db199SXin Li delete_attribute=self.delete) 467*9c5db199SXin Li text_manager.dump_infrastructure(data_dir, infrastructure) 468*9c5db199SXin Li 469*9c5db199SXin Li status = inventory_repo.git_repo.status() 470*9c5db199SXin Li if not status: 471*9c5db199SXin Li print('Nothing is changed for server %s.' % self.hostname) 472*9c5db199SXin Li return 473*9c5db199SXin Li 474*9c5db199SXin Li message = skylab_utils.construct_commit_message( 475*9c5db199SXin Li 'Modify server: %s' % self.hostname) 476*9c5db199SXin Li self.change_number = inventory_repo.upload_change( 477*9c5db199SXin Li message, draft=self.draft, dryrun=self.dryrun, 478*9c5db199SXin Li submit=self.submit) 479*9c5db199SXin Li 480*9c5db199SXin Li return target_server 481*9c5db199SXin Li 482*9c5db199SXin Li 483*9c5db199SXin Li def execute(self): 484*9c5db199SXin Li """Execute the command. 485*9c5db199SXin Li 486*9c5db199SXin Li @return: The updated server object if it is modified successfully. 487*9c5db199SXin Li """ 488*9c5db199SXin Li self.failure(ATEST_DISABLE_MSG, 489*9c5db199SXin Li what_failed='Failed to modify server', 490*9c5db199SXin Li item=self.hostname, 491*9c5db199SXin Li fatal=True) 492*9c5db199SXin Li 493*9c5db199SXin Li 494*9c5db199SXin Li def output(self, results): 495*9c5db199SXin Li """Display output. 496*9c5db199SXin Li 497*9c5db199SXin Li @param results: return of the execute call, which is the updated server 498*9c5db199SXin Li object. 499*9c5db199SXin Li """ 500*9c5db199SXin Li if results: 501*9c5db199SXin Li print('Server %s is modified.\n' % self.hostname) 502*9c5db199SXin Li print(results) 503*9c5db199SXin Li 504*9c5db199SXin Li if self.skylab and not self.dryrun and not self.submit: 505*9c5db199SXin Li print(skylab_utils.get_cl_message(self.change_number)) 506