blob: caa57919cb372389a4dd34d4f94ab65b6bd8971a [file] [log] [blame]
Dan Shi784df0c2014-11-26 10:11:15 -08001# 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"""
6The server module contains the objects and methods used to manage servers in
7Autotest.
8
9The valid actions are:
10list: list all servers in the database
11create: create a server
12delete: deletes a server
13modify: modify a server's role or status.
14
15The common options are:
16--role / -r: role that's related to server actions.
17
18See topic_common.py for a High Level Design and Algorithm.
19"""
20
21import common
22
23from autotest_lib.cli import action_common
Ningning Xia84190b82018-04-16 15:01:40 -070024from autotest_lib.cli import skylab_utils
Dan Shi784df0c2014-11-26 10:11:15 -080025from autotest_lib.cli import topic_common
26from autotest_lib.client.common_lib import error
Shuqian Zhaodb205af2018-02-28 15:13:03 -080027from autotest_lib.client.common_lib import global_config
Ningning Xia84190b82018-04-16 15:01:40 -070028from autotest_lib.client.common_lib import revision_control
Dan Shib9144a42014-12-01 16:09:32 -080029# The django setup is moved here as test_that uses sqlite setup. If this line
30# is in server_manager, test_that unittest will fail.
Dan Shi56f1ba72014-12-03 19:16:53 -080031from autotest_lib.frontend import setup_django_environment
Dan Shi784df0c2014-11-26 10:11:15 -080032from autotest_lib.site_utils import server_manager
Dan Shi56f1ba72014-12-03 19:16:53 -080033from autotest_lib.site_utils import server_manager_utils
Ningning Xiaa043aad2018-04-23 15:07:09 -070034from chromite.lib import gob_util
Ningning Xia9c188b92018-04-27 15:34:23 -070035
36try:
37 from skylab_inventory import text_manager
38 from skylab_inventory import translation_utils
39 from skylab_inventory.lib import server as skylab_server
40except ImportError:
41 pass
Ningning Xia84190b82018-04-16 15:01:40 -070042
43
Shuqian Zhaodb205af2018-02-28 15:13:03 -080044RESPECT_SKYLAB_SERVERDB = global_config.global_config.get_config_value(
45 'SKYLAB', 'respect_skylab_serverdb', type=bool, default=False)
46ATEST_DISABLE_MSG = ('Updating server_db via atest server command has been '
47 'disabled. Please use use go/cros-infra-inventory-tool '
48 'to update it in skylab inventory service.')
Ningning Xiaa043aad2018-04-23 15:07:09 -070049SUBMIT_CL_MSG = 'Please submit the CL at %s to make the change effective.'
Shuqian Zhaodb205af2018-02-28 15:13:03 -080050
Dan Shi784df0c2014-11-26 10:11:15 -080051
52class server(topic_common.atest):
53 """Server class
54
55 atest server [list|create|delete|modify] <options>
56 """
57 usage_action = '[list|create|delete|modify]'
58 topic = msg_topic = 'server'
59 msg_items = '<server>'
60
61 def __init__(self, hostname_required=True):
62 """Add to the parser the options common to all the server actions.
63
64 @param hostname_required: True to require the command has hostname
65 specified. Default is True.
66 """
67 super(server, self).__init__()
68
69 self.parser.add_option('-r', '--role',
70 help='Name of a role',
71 type='string',
72 default=None,
73 metavar='ROLE')
Dan Shi56f1ba72014-12-03 19:16:53 -080074 self.parser.add_option('-x', '--action',
75 help=('Set to True to apply actions when role '
76 'or status is changed, e.g., restart '
Ningning Xia84190b82018-04-16 15:01:40 -070077 'scheduler when a drone is removed. Note: '
78 'This is NOT supported when --skylab is '
79 'enabled.'),
Dan Shi56f1ba72014-12-03 19:16:53 -080080 action='store_true',
81 default=False,
82 metavar='ACTION')
Dan Shi784df0c2014-11-26 10:11:15 -080083
Ningning Xia84190b82018-04-16 15:01:40 -070084 self.add_skylab_options()
85
Dan Shi784df0c2014-11-26 10:11:15 -080086 self.topic_parse_info = topic_common.item_parse_info(
87 attribute_name='hostname', use_leftover=True)
88
89 self.hostname_required = hostname_required
90
91
92 def parse(self):
93 """Parse command arguments.
94 """
95 role_info = topic_common.item_parse_info(attribute_name='role')
96 kwargs = {}
97 if self.hostname_required:
98 kwargs['req_items'] = 'hostname'
99 (options, leftover) = super(server, self).parse([role_info], **kwargs)
100 if options.web_server:
101 self.invalid_syntax('Server actions will access server database '
102 'defined in your local global config. It does '
103 'not rely on RPC, no autotest server needs to '
104 'be specified.')
105
106 # self.hostname is a list. Action on server only needs one hostname at
107 # most.
108 if ((not self.hostname and self.hostname_required) or
109 len(self.hostname) > 1):
110 self.invalid_syntax('`server` topic can only manipulate 1 server. '
111 'Use -h to see available options.')
112 if self.hostname:
113 # Override self.hostname with the first hostname in the list.
114 self.hostname = self.hostname[0]
115 self.role = options.role
Ningning Xia84190b82018-04-16 15:01:40 -0700116
117 if self.skylab and self.role:
118 translation_utils.validate_server_role(self.role)
119
Dan Shi784df0c2014-11-26 10:11:15 -0800120 return (options, leftover)
121
122
123 def output(self, results):
124 """Display output.
125
126 For most actions, the return is a string message, no formating needed.
127
128 @param results: return of the execute call.
129 """
130 print results
131
132
133class server_help(server):
134 """Just here to get the atest logic working. Usage is set by its parent.
135 """
136 pass
137
138
139class server_list(action_common.atest_list, server):
140 """atest server list [--role <role>]"""
141
142 def __init__(self):
143 """Initializer.
144 """
145 super(server_list, self).__init__(hostname_required=False)
Ningning Xia84190b82018-04-16 15:01:40 -0700146 warn_message_for_skylab = 'This is not supported with --skylab.'
147
Dan Shi784df0c2014-11-26 10:11:15 -0800148 self.parser.add_option('-t', '--table',
149 help=('List details of all servers in a table, '
150 'e.g., \tHostname | Status | Roles | '
151 'note\t\tserver1 | primary | scheduler | '
Ningning Xia84190b82018-04-16 15:01:40 -0700152 'lab. %s' % warn_message_for_skylab),
Dan Shi784df0c2014-11-26 10:11:15 -0800153 action='store_true',
Allen Lica17e7c2016-10-27 15:37:17 -0700154 default=False)
Dan Shi784df0c2014-11-26 10:11:15 -0800155 self.parser.add_option('-s', '--status',
Ningning Xia84190b82018-04-16 15:01:40 -0700156 help='Only show servers with given status.',
Dan Shi784df0c2014-11-26 10:11:15 -0800157 type='string',
158 default=None,
159 metavar='STATUS')
160 self.parser.add_option('-u', '--summary',
161 help=('Show the summary of roles and status '
162 'only, e.g.,\tscheduler: server1(primary) '
163 'server2(backup)\t\tdrone: server3(primary'
Ningning Xia84190b82018-04-16 15:01:40 -0700164 ') server4(backup). %s' %
165 warn_message_for_skylab),
Dan Shi784df0c2014-11-26 10:11:15 -0800166 action='store_true',
Allen Lica17e7c2016-10-27 15:37:17 -0700167 default=False)
168 self.parser.add_option('--json',
Ningning Xia84190b82018-04-16 15:01:40 -0700169 help=('Format output as JSON. %s' %
170 warn_message_for_skylab),
Allen Lica17e7c2016-10-27 15:37:17 -0700171 action='store_true',
172 default=False)
Aviv Keshete1729bb2017-05-31 13:27:09 -0700173 self.parser.add_option('-N', '--hostnames-only',
Ningning Xia84190b82018-04-16 15:01:40 -0700174 help=('Only return hostnames. %s' %
175 warn_message_for_skylab),
Aviv Keshete1729bb2017-05-31 13:27:09 -0700176 action='store_true',
177 default=False)
Dan Shi784df0c2014-11-26 10:11:15 -0800178
179
180 def parse(self):
181 """Parse command arguments.
182 """
183 (options, leftover) = super(server_list, self).parse()
Allen Lica17e7c2016-10-27 15:37:17 -0700184 self.json = options.json
Dan Shi784df0c2014-11-26 10:11:15 -0800185 self.table = options.table
186 self.status = options.status
187 self.summary = options.summary
Aviv Keshete1729bb2017-05-31 13:27:09 -0700188 self.namesonly = options.hostnames_only
Ningning Xia84190b82018-04-16 15:01:40 -0700189
190 # TODO(nxia): support all formats for skylab inventory.
191 if (self.skylab and (self.json or self.table or self.summary)):
192 self.invalid_syntax('The format (json|summary|json|hostnames-only)'
193 ' is not supported with --skylab.')
194
Aviv Keshete1729bb2017-05-31 13:27:09 -0700195 if sum([self.table, self.summary, self.json, self.namesonly]) > 1:
196 self.invalid_syntax('May only specify up to 1 output-format flag.')
Dan Shi784df0c2014-11-26 10:11:15 -0800197 return (options, leftover)
198
199
200 def execute(self):
201 """Execute the command.
202
203 @return: A list of servers matched given hostname and role.
204 """
Ningning Xia84190b82018-04-16 15:01:40 -0700205 if self.skylab:
206 try:
207 inventory_repo = skylab_utils.InventoryRepo(
208 self.inventory_repo_dir)
209 inventory_repo.initialize()
210 infrastructure = text_manager.load_infrastructure(
Ningning Xiae8714052018-04-30 18:58:54 -0700211 inventory_repo.get_data_dir())
Ningning Xia84190b82018-04-16 15:01:40 -0700212
213 return skylab_server.get_servers(
214 infrastructure,
Ningning Xiae8714052018-04-30 18:58:54 -0700215 self.environment,
Ningning Xia84190b82018-04-16 15:01:40 -0700216 hostname=self.hostname,
217 role=self.role,
218 status=self.status)
219 except (skylab_server.SkylabServerActionError,
220 revision_control.GitError) as e:
221 self.failure(e, what_failed='Failed to list servers from skylab'
222 ' inventory.', item=self.hostname, fatal=True)
223 else:
224 try:
225 return server_manager_utils.get_servers(
226 hostname=self.hostname,
227 role=self.role,
228 status=self.status)
229 except (server_manager_utils.ServerActionError,
230 error.InvalidDataError) as e:
231 self.failure(e, what_failed='Failed to find servers',
232 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800233
234
235 def output(self, results):
236 """Display output.
237
238 @param results: return of the execute call, a list of server object that
239 contains server information.
240 """
Allen Li90a84ea2016-10-27 15:07:42 -0700241 if results:
Allen Lica17e7c2016-10-27 15:37:17 -0700242 if self.json:
243 formatter = server_manager_utils.format_servers_json
244 elif self.table:
Allen Li90a84ea2016-10-27 15:07:42 -0700245 formatter = server_manager_utils.format_servers_table
246 elif self.summary:
247 formatter = server_manager_utils.format_servers_summary
Aviv Keshete1729bb2017-05-31 13:27:09 -0700248 elif self.namesonly:
249 formatter = server_manager_utils.format_servers_nameonly
Allen Li90a84ea2016-10-27 15:07:42 -0700250 else:
251 formatter = server_manager_utils.format_servers
252 print formatter(results)
253 else:
Dan Shi784df0c2014-11-26 10:11:15 -0800254 self.failure('No server is found.',
255 what_failed='Failed to find servers',
256 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800257
258
259class server_create(server):
260 """atest server create hostname --role <role> --note <note>
261 """
262
263 def __init__(self):
264 """Initializer.
265 """
266 super(server_create, self).__init__()
267 self.parser.add_option('-n', '--note',
268 help='note of the server',
269 type='string',
270 default=None,
271 metavar='NOTE')
272
273
274 def parse(self):
275 """Parse command arguments.
276 """
277 (options, leftover) = super(server_create, self).parse()
278 self.note = options.note
279
280 if not self.role:
281 self.invalid_syntax('--role is required to create a server.')
282
283 return (options, leftover)
284
285
Ningning Xia84190b82018-04-16 15:01:40 -0700286 def execute_skylab(self):
287 """Execute the command for skylab inventory changes."""
288 inventory_repo = skylab_utils.InventoryRepo(
289 self.inventory_repo_dir)
290 inventory_repo.initialize()
291 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700292 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700293
294 new_server = skylab_server.create(
295 infrastructure,
296 self.hostname,
297 self.environment,
298 role=self.role,
299 note=self.note)
Ningning Xiae8714052018-04-30 18:58:54 -0700300 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700301
302 message = skylab_utils.construct_commit_message(
303 'Add new server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700304 self.change_number = inventory_repo.upload_change(
305 message, draft=self.draft, dryrun=self.dryrun,
306 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700307
308 return new_server
309
310
Dan Shi784df0c2014-11-26 10:11:15 -0800311 def execute(self):
312 """Execute the command.
313
314 @return: A Server object if it is created successfully.
315 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800316 if RESPECT_SKYLAB_SERVERDB:
317 self.failure(ATEST_DISABLE_MSG,
318 what_failed='Failed to create server',
319 item=self.hostname, fatal=True)
320
Ningning Xia84190b82018-04-16 15:01:40 -0700321 if self.skylab:
322 try:
323 return self.execute_skylab()
324 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700325 revision_control.GitError,
326 gob_util.GOBError) as e:
Ningning Xiae8714052018-04-30 18:58:54 -0700327 self.failure(e, what_failed='Failed to create server in skylab '
Ningning Xia84190b82018-04-16 15:01:40 -0700328 'inventory.', item=self.hostname, fatal=True)
329 else:
330 try:
331 return server_manager.create(
332 hostname=self.hostname,
333 role=self.role,
334 note=self.note)
335 except (server_manager_utils.ServerActionError,
336 error.InvalidDataError) as e:
337 self.failure(e, what_failed='Failed to create server',
338 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800339
340
341 def output(self, results):
342 """Display output.
343
344 @param results: return of the execute call, a server object that
345 contains server information.
346 """
347 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700348 print 'Server %s is added.\n' % self.hostname
Dan Shi784df0c2014-11-26 10:11:15 -0800349 print results
350
Ningning Xiaa043aad2018-04-23 15:07:09 -0700351 if self.skylab and not self.dryrun and not self.submit:
352 print SUBMIT_CL_MSG % skylab_utils.get_cl_url(
353 self.change_number)
354
Ningning Xia84190b82018-04-16 15:01:40 -0700355
Dan Shi784df0c2014-11-26 10:11:15 -0800356
357class server_delete(server):
358 """atest server delete hostname"""
359
Ningning Xia84190b82018-04-16 15:01:40 -0700360 def execute_skylab(self):
361 """Execute the command for skylab inventory changes."""
362 inventory_repo = skylab_utils.InventoryRepo(
363 self.inventory_repo_dir)
364 inventory_repo.initialize()
365 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700366 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700367
Ningning Xiae8714052018-04-30 18:58:54 -0700368 skylab_server.delete(infrastructure, self.hostname, self.environment)
369 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700370
371 message = skylab_utils.construct_commit_message(
372 'Delete server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700373 self.change_number = inventory_repo.upload_change(
374 message, draft=self.draft, dryrun=self.dryrun,
375 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700376
377
Dan Shi784df0c2014-11-26 10:11:15 -0800378 def execute(self):
379 """Execute the command.
380
381 @return: True if server is deleted successfully.
382 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800383 if RESPECT_SKYLAB_SERVERDB:
384 self.failure(ATEST_DISABLE_MSG,
385 what_failed='Failed to delete server',
386 item=self.hostname, fatal=True)
387
Ningning Xia84190b82018-04-16 15:01:40 -0700388 if self.skylab:
389 try:
390 self.execute_skylab()
391 return True
392 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700393 revision_control.GitError,
394 gob_util.GOBError) as e:
Ningning Xia84190b82018-04-16 15:01:40 -0700395 self.failure(e, what_failed='Failed to delete server from '
396 'skylab inventory.', item=self.hostname,
397 fatal=True)
398 else:
399 try:
400 server_manager.delete(hostname=self.hostname)
401 return True
402 except (server_manager_utils.ServerActionError,
403 error.InvalidDataError) as e:
404 self.failure(e, what_failed='Failed to delete server',
405 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800406
407
408 def output(self, results):
409 """Display output.
410
411 @param results: return of the execute call.
412 """
413 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700414 print ('Server %s is deleted.\n' %
Dan Shi784df0c2014-11-26 10:11:15 -0800415 self.hostname)
416
Ningning Xiaa043aad2018-04-23 15:07:09 -0700417 if self.skylab and not self.dryrun and not self.submit:
418 print SUBMIT_CL_MSG % skylab_utils.get_cl_url(
419 self.change_number)
420
Ningning Xia84190b82018-04-16 15:01:40 -0700421
Dan Shi784df0c2014-11-26 10:11:15 -0800422
423class server_modify(server):
424 """atest server modify hostname
425
426 modify action can only change one input at a time. Available inputs are:
427 --status: Status of the server.
428 --note: Note of the server.
429 --role: New role to be added to the server.
430 --delete_role: Existing role to be deleted from the server.
431 """
432
433 def __init__(self):
434 """Initializer.
435 """
436 super(server_modify, self).__init__()
437 self.parser.add_option('-s', '--status',
438 help='Status of the server',
439 type='string',
440 metavar='STATUS')
441 self.parser.add_option('-n', '--note',
442 help='Note of the server',
443 type='string',
444 default=None,
445 metavar='NOTE')
446 self.parser.add_option('-d', '--delete',
447 help=('Set to True to delete given role.'),
448 action='store_true',
449 default=False,
450 metavar='DELETE')
451 self.parser.add_option('-a', '--attribute',
452 help='Name of the attribute of the server',
453 type='string',
454 default=None,
455 metavar='ATTRIBUTE')
456 self.parser.add_option('-e', '--value',
457 help='Value for the attribute of the server',
458 type='string',
459 default=None,
460 metavar='VALUE')
461
462
463 def parse(self):
464 """Parse command arguments.
465 """
466 (options, leftover) = super(server_modify, self).parse()
467 self.status = options.status
468 self.note = options.note
469 self.delete = options.delete
470 self.attribute = options.attribute
471 self.value = options.value
Dan Shi56f1ba72014-12-03 19:16:53 -0800472 self.action = options.action
Dan Shi784df0c2014-11-26 10:11:15 -0800473
474 # modify supports various options. However, it's safer to limit one
475 # option at a time so no complicated role-dependent logic is needed
476 # to handle scenario that both role and status are changed.
477 # self.parser is optparse, which does not have function in argparse like
478 # add_mutually_exclusive_group. That's why the count is used here.
479 flags = [self.status is not None, self.role is not None,
480 self.attribute is not None, self.note is not None]
481 if flags.count(True) != 1:
482 msg = ('Action modify only support one option at a time. You can '
483 'try one of following 5 options:\n'
484 '1. --status: Change server\'s status.\n'
485 '2. --note: Change server\'s note.\n'
486 '3. --role with optional -d: Add/delete role from server.\n'
487 '4. --attribute --value: Set/change the value of a '
488 'server\'s attribute.\n'
489 '5. --attribute -d: Delete the attribute from the '
490 'server.\n'
491 '\nUse option -h to see a complete list of options.')
492 self.invalid_syntax(msg)
493 if (self.status != None or self.note != None) and self.delete:
494 self.invalid_syntax('--delete does not apply to status or note.')
495 if self.attribute != None and not self.delete and self.value == None:
496 self.invalid_syntax('--attribute must be used with option --value '
497 'or --delete.')
Ningning Xia84190b82018-04-16 15:01:40 -0700498
499 # TODO(nxia): crbug.com/832964 support --action with --skylab
500 if self.skylab and self.action:
501 self.invalid_syntax('--action is currently not supported with'
502 ' --skylab.')
503
Dan Shi784df0c2014-11-26 10:11:15 -0800504 return (options, leftover)
505
506
Ningning Xia84190b82018-04-16 15:01:40 -0700507 def execute_skylab(self):
508 """Execute the command for skylab inventory changes."""
509 inventory_repo = skylab_utils.InventoryRepo(
510 self.inventory_repo_dir)
511 inventory_repo.initialize()
512 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700513 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700514
515 target_server = skylab_server.modify(
516 infrastructure,
517 self.hostname,
Ningning Xiae8714052018-04-30 18:58:54 -0700518 self.environment,
Ningning Xia84190b82018-04-16 15:01:40 -0700519 role=self.role,
520 status=self.status,
521 delete_role=self.delete,
522 note=self.note,
523 attribute=self.attribute,
524 value=self.value,
525 delete_attribute=self.delete)
Ningning Xiae8714052018-04-30 18:58:54 -0700526 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700527
528 status = inventory_repo.git_repo.status()
529 if not status:
530 print('Nothing is changed for server %s.' % self.hostname)
531 return
532
533 message = skylab_utils.construct_commit_message(
534 'Modify server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700535 self.change_number = inventory_repo.upload_change(
536 message, draft=self.draft, dryrun=self.dryrun,
537 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700538
539 return target_server
540
541
Dan Shi784df0c2014-11-26 10:11:15 -0800542 def execute(self):
543 """Execute the command.
544
545 @return: The updated server object if it is modified successfully.
546 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800547 if RESPECT_SKYLAB_SERVERDB:
548 self.failure(ATEST_DISABLE_MSG,
549 what_failed='Failed to modify server',
550 item=self.hostname, fatal=True)
551
Ningning Xia84190b82018-04-16 15:01:40 -0700552 if self.skylab:
553 try:
554 return self.execute_skylab()
555 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700556 revision_control.GitError,
557 gob_util.GOBError) as e:
Ningning Xia84190b82018-04-16 15:01:40 -0700558 self.failure(e, what_failed='Failed to modify server in skylab'
559 ' inventory.', item=self.hostname, fatal=True)
560 else:
561 try:
562 return server_manager.modify(
563 hostname=self.hostname, role=self.role,
564 status=self.status, delete=self.delete,
565 note=self.note, attribute=self.attribute,
566 value=self.value, action=self.action)
567 except (server_manager_utils.ServerActionError,
568 error.InvalidDataError) as e:
569 self.failure(e, what_failed='Failed to modify server',
570 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800571
572
573 def output(self, results):
574 """Display output.
575
576 @param results: return of the execute call, which is the updated server
577 object.
578 """
579 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700580 print 'Server %s is modified.\n' % self.hostname
Dan Shi784df0c2014-11-26 10:11:15 -0800581 print results
Ningning Xia84190b82018-04-16 15:01:40 -0700582
Ningning Xiaa043aad2018-04-23 15:07:09 -0700583 if self.skylab and not self.dryrun and not self.submit:
584 print SUBMIT_CL_MSG % skylab_utils.get_cl_url(
585 self.change_number)