blob: dfa7ee51019ff0810d31fd690bd4dd8ef8f38be9 [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.')
49
Dan Shi784df0c2014-11-26 10:11:15 -080050
51class server(topic_common.atest):
52 """Server class
53
54 atest server [list|create|delete|modify] <options>
55 """
56 usage_action = '[list|create|delete|modify]'
57 topic = msg_topic = 'server'
58 msg_items = '<server>'
59
60 def __init__(self, hostname_required=True):
61 """Add to the parser the options common to all the server actions.
62
63 @param hostname_required: True to require the command has hostname
64 specified. Default is True.
65 """
66 super(server, self).__init__()
67
68 self.parser.add_option('-r', '--role',
69 help='Name of a role',
70 type='string',
71 default=None,
72 metavar='ROLE')
Dan Shi56f1ba72014-12-03 19:16:53 -080073 self.parser.add_option('-x', '--action',
74 help=('Set to True to apply actions when role '
75 'or status is changed, e.g., restart '
Ningning Xia84190b82018-04-16 15:01:40 -070076 'scheduler when a drone is removed. Note: '
77 'This is NOT supported when --skylab is '
78 'enabled.'),
Dan Shi56f1ba72014-12-03 19:16:53 -080079 action='store_true',
80 default=False,
81 metavar='ACTION')
Dan Shi784df0c2014-11-26 10:11:15 -080082
Ningning Xia84190b82018-04-16 15:01:40 -070083 self.add_skylab_options()
84
Dan Shi784df0c2014-11-26 10:11:15 -080085 self.topic_parse_info = topic_common.item_parse_info(
86 attribute_name='hostname', use_leftover=True)
87
88 self.hostname_required = hostname_required
89
90
91 def parse(self):
92 """Parse command arguments.
93 """
94 role_info = topic_common.item_parse_info(attribute_name='role')
95 kwargs = {}
96 if self.hostname_required:
97 kwargs['req_items'] = 'hostname'
98 (options, leftover) = super(server, self).parse([role_info], **kwargs)
99 if options.web_server:
100 self.invalid_syntax('Server actions will access server database '
101 'defined in your local global config. It does '
102 'not rely on RPC, no autotest server needs to '
103 'be specified.')
104
105 # self.hostname is a list. Action on server only needs one hostname at
106 # most.
107 if ((not self.hostname and self.hostname_required) or
108 len(self.hostname) > 1):
109 self.invalid_syntax('`server` topic can only manipulate 1 server. '
110 'Use -h to see available options.')
111 if self.hostname:
112 # Override self.hostname with the first hostname in the list.
113 self.hostname = self.hostname[0]
114 self.role = options.role
Ningning Xia84190b82018-04-16 15:01:40 -0700115
116 if self.skylab and self.role:
117 translation_utils.validate_server_role(self.role)
118
Dan Shi784df0c2014-11-26 10:11:15 -0800119 return (options, leftover)
120
121
122 def output(self, results):
123 """Display output.
124
125 For most actions, the return is a string message, no formating needed.
126
127 @param results: return of the execute call.
128 """
129 print results
130
131
132class server_help(server):
133 """Just here to get the atest logic working. Usage is set by its parent.
134 """
135 pass
136
137
138class server_list(action_common.atest_list, server):
139 """atest server list [--role <role>]"""
140
141 def __init__(self):
142 """Initializer.
143 """
144 super(server_list, self).__init__(hostname_required=False)
Ningning Xia84190b82018-04-16 15:01:40 -0700145 warn_message_for_skylab = 'This is not supported with --skylab.'
146
Dan Shi784df0c2014-11-26 10:11:15 -0800147 self.parser.add_option('-t', '--table',
148 help=('List details of all servers in a table, '
149 'e.g., \tHostname | Status | Roles | '
150 'note\t\tserver1 | primary | scheduler | '
Ningning Xia84190b82018-04-16 15:01:40 -0700151 'lab. %s' % warn_message_for_skylab),
Dan Shi784df0c2014-11-26 10:11:15 -0800152 action='store_true',
Allen Lica17e7c2016-10-27 15:37:17 -0700153 default=False)
Dan Shi784df0c2014-11-26 10:11:15 -0800154 self.parser.add_option('-s', '--status',
Ningning Xia84190b82018-04-16 15:01:40 -0700155 help='Only show servers with given status.',
Dan Shi784df0c2014-11-26 10:11:15 -0800156 type='string',
157 default=None,
158 metavar='STATUS')
159 self.parser.add_option('-u', '--summary',
160 help=('Show the summary of roles and status '
161 'only, e.g.,\tscheduler: server1(primary) '
162 'server2(backup)\t\tdrone: server3(primary'
Ningning Xia84190b82018-04-16 15:01:40 -0700163 ') server4(backup). %s' %
164 warn_message_for_skylab),
Dan Shi784df0c2014-11-26 10:11:15 -0800165 action='store_true',
Allen Lica17e7c2016-10-27 15:37:17 -0700166 default=False)
167 self.parser.add_option('--json',
Ningning Xia84190b82018-04-16 15:01:40 -0700168 help=('Format output as JSON. %s' %
169 warn_message_for_skylab),
Allen Lica17e7c2016-10-27 15:37:17 -0700170 action='store_true',
171 default=False)
Aviv Keshete1729bb2017-05-31 13:27:09 -0700172 self.parser.add_option('-N', '--hostnames-only',
Ningning Xia84190b82018-04-16 15:01:40 -0700173 help=('Only return hostnames. %s' %
174 warn_message_for_skylab),
Aviv Keshete1729bb2017-05-31 13:27:09 -0700175 action='store_true',
176 default=False)
Dan Shi784df0c2014-11-26 10:11:15 -0800177
178
179 def parse(self):
180 """Parse command arguments.
181 """
182 (options, leftover) = super(server_list, self).parse()
Allen Lica17e7c2016-10-27 15:37:17 -0700183 self.json = options.json
Dan Shi784df0c2014-11-26 10:11:15 -0800184 self.table = options.table
185 self.status = options.status
186 self.summary = options.summary
Aviv Keshete1729bb2017-05-31 13:27:09 -0700187 self.namesonly = options.hostnames_only
Ningning Xia84190b82018-04-16 15:01:40 -0700188
189 # TODO(nxia): support all formats for skylab inventory.
190 if (self.skylab and (self.json or self.table or self.summary)):
191 self.invalid_syntax('The format (json|summary|json|hostnames-only)'
192 ' is not supported with --skylab.')
193
Aviv Keshete1729bb2017-05-31 13:27:09 -0700194 if sum([self.table, self.summary, self.json, self.namesonly]) > 1:
195 self.invalid_syntax('May only specify up to 1 output-format flag.')
Dan Shi784df0c2014-11-26 10:11:15 -0800196 return (options, leftover)
197
198
199 def execute(self):
200 """Execute the command.
201
202 @return: A list of servers matched given hostname and role.
203 """
Ningning Xia84190b82018-04-16 15:01:40 -0700204 if self.skylab:
205 try:
206 inventory_repo = skylab_utils.InventoryRepo(
207 self.inventory_repo_dir)
208 inventory_repo.initialize()
209 infrastructure = text_manager.load_infrastructure(
Ningning Xiae8714052018-04-30 18:58:54 -0700210 inventory_repo.get_data_dir())
Ningning Xia84190b82018-04-16 15:01:40 -0700211
212 return skylab_server.get_servers(
213 infrastructure,
Ningning Xiae8714052018-04-30 18:58:54 -0700214 self.environment,
Ningning Xia84190b82018-04-16 15:01:40 -0700215 hostname=self.hostname,
216 role=self.role,
217 status=self.status)
218 except (skylab_server.SkylabServerActionError,
219 revision_control.GitError) as e:
220 self.failure(e, what_failed='Failed to list servers from skylab'
221 ' inventory.', item=self.hostname, fatal=True)
222 else:
223 try:
224 return server_manager_utils.get_servers(
225 hostname=self.hostname,
226 role=self.role,
227 status=self.status)
228 except (server_manager_utils.ServerActionError,
229 error.InvalidDataError) as e:
230 self.failure(e, what_failed='Failed to find servers',
231 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800232
233
234 def output(self, results):
235 """Display output.
236
237 @param results: return of the execute call, a list of server object that
238 contains server information.
239 """
Allen Li90a84ea2016-10-27 15:07:42 -0700240 if results:
Allen Lica17e7c2016-10-27 15:37:17 -0700241 if self.json:
242 formatter = server_manager_utils.format_servers_json
243 elif self.table:
Allen Li90a84ea2016-10-27 15:07:42 -0700244 formatter = server_manager_utils.format_servers_table
245 elif self.summary:
246 formatter = server_manager_utils.format_servers_summary
Aviv Keshete1729bb2017-05-31 13:27:09 -0700247 elif self.namesonly:
248 formatter = server_manager_utils.format_servers_nameonly
Allen Li90a84ea2016-10-27 15:07:42 -0700249 else:
250 formatter = server_manager_utils.format_servers
251 print formatter(results)
252 else:
Dan Shi784df0c2014-11-26 10:11:15 -0800253 self.failure('No server is found.',
254 what_failed='Failed to find servers',
255 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800256
257
258class server_create(server):
259 """atest server create hostname --role <role> --note <note>
260 """
261
262 def __init__(self):
263 """Initializer.
264 """
265 super(server_create, self).__init__()
266 self.parser.add_option('-n', '--note',
267 help='note of the server',
268 type='string',
269 default=None,
270 metavar='NOTE')
271
272
273 def parse(self):
274 """Parse command arguments.
275 """
276 (options, leftover) = super(server_create, self).parse()
277 self.note = options.note
278
279 if not self.role:
280 self.invalid_syntax('--role is required to create a server.')
281
282 return (options, leftover)
283
284
Ningning Xia84190b82018-04-16 15:01:40 -0700285 def execute_skylab(self):
286 """Execute the command for skylab inventory changes."""
287 inventory_repo = skylab_utils.InventoryRepo(
288 self.inventory_repo_dir)
289 inventory_repo.initialize()
290 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700291 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700292
293 new_server = skylab_server.create(
294 infrastructure,
295 self.hostname,
296 self.environment,
297 role=self.role,
298 note=self.note)
Ningning Xiae8714052018-04-30 18:58:54 -0700299 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700300
301 message = skylab_utils.construct_commit_message(
302 'Add new server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700303 self.change_number = inventory_repo.upload_change(
304 message, draft=self.draft, dryrun=self.dryrun,
305 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700306
307 return new_server
308
309
Dan Shi784df0c2014-11-26 10:11:15 -0800310 def execute(self):
311 """Execute the command.
312
313 @return: A Server object if it is created successfully.
314 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800315 if RESPECT_SKYLAB_SERVERDB:
316 self.failure(ATEST_DISABLE_MSG,
317 what_failed='Failed to create server',
318 item=self.hostname, fatal=True)
319
Ningning Xia84190b82018-04-16 15:01:40 -0700320 if self.skylab:
321 try:
322 return self.execute_skylab()
323 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700324 revision_control.GitError,
325 gob_util.GOBError) as e:
Ningning Xiae8714052018-04-30 18:58:54 -0700326 self.failure(e, what_failed='Failed to create server in skylab '
Ningning Xia84190b82018-04-16 15:01:40 -0700327 'inventory.', item=self.hostname, fatal=True)
328 else:
329 try:
330 return server_manager.create(
331 hostname=self.hostname,
332 role=self.role,
333 note=self.note)
334 except (server_manager_utils.ServerActionError,
335 error.InvalidDataError) as e:
336 self.failure(e, what_failed='Failed to create server',
337 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800338
339
340 def output(self, results):
341 """Display output.
342
343 @param results: return of the execute call, a server object that
344 contains server information.
345 """
346 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700347 print 'Server %s is added.\n' % self.hostname
Dan Shi784df0c2014-11-26 10:11:15 -0800348 print results
349
Ningning Xiaa043aad2018-04-23 15:07:09 -0700350 if self.skylab and not self.dryrun and not self.submit:
Ningning Xiaef35cb52018-05-04 17:58:20 -0700351 print skylab_utils.get_cl_message(self.change_number)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700352
Ningning Xia84190b82018-04-16 15:01:40 -0700353
Dan Shi784df0c2014-11-26 10:11:15 -0800354
355class server_delete(server):
356 """atest server delete hostname"""
357
Ningning Xia84190b82018-04-16 15:01:40 -0700358 def execute_skylab(self):
359 """Execute the command for skylab inventory changes."""
360 inventory_repo = skylab_utils.InventoryRepo(
361 self.inventory_repo_dir)
362 inventory_repo.initialize()
363 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700364 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700365
Ningning Xiae8714052018-04-30 18:58:54 -0700366 skylab_server.delete(infrastructure, self.hostname, self.environment)
367 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700368
369 message = skylab_utils.construct_commit_message(
370 'Delete server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700371 self.change_number = inventory_repo.upload_change(
372 message, draft=self.draft, dryrun=self.dryrun,
373 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700374
375
Dan Shi784df0c2014-11-26 10:11:15 -0800376 def execute(self):
377 """Execute the command.
378
379 @return: True if server is deleted successfully.
380 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800381 if RESPECT_SKYLAB_SERVERDB:
382 self.failure(ATEST_DISABLE_MSG,
383 what_failed='Failed to delete server',
384 item=self.hostname, fatal=True)
385
Ningning Xia84190b82018-04-16 15:01:40 -0700386 if self.skylab:
387 try:
388 self.execute_skylab()
389 return True
390 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700391 revision_control.GitError,
392 gob_util.GOBError) as e:
Ningning Xia84190b82018-04-16 15:01:40 -0700393 self.failure(e, what_failed='Failed to delete server from '
394 'skylab inventory.', item=self.hostname,
395 fatal=True)
396 else:
397 try:
398 server_manager.delete(hostname=self.hostname)
399 return True
400 except (server_manager_utils.ServerActionError,
401 error.InvalidDataError) as e:
402 self.failure(e, what_failed='Failed to delete server',
403 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800404
405
406 def output(self, results):
407 """Display output.
408
409 @param results: return of the execute call.
410 """
411 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700412 print ('Server %s is deleted.\n' %
Dan Shi784df0c2014-11-26 10:11:15 -0800413 self.hostname)
414
Ningning Xiaa043aad2018-04-23 15:07:09 -0700415 if self.skylab and not self.dryrun and not self.submit:
Ningning Xiaef35cb52018-05-04 17:58:20 -0700416 print skylab_utils.get_cl_message(self.change_number)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700417
Ningning Xia84190b82018-04-16 15:01:40 -0700418
Dan Shi784df0c2014-11-26 10:11:15 -0800419
420class server_modify(server):
421 """atest server modify hostname
422
423 modify action can only change one input at a time. Available inputs are:
424 --status: Status of the server.
425 --note: Note of the server.
426 --role: New role to be added to the server.
427 --delete_role: Existing role to be deleted from the server.
428 """
429
430 def __init__(self):
431 """Initializer.
432 """
433 super(server_modify, self).__init__()
434 self.parser.add_option('-s', '--status',
435 help='Status of the server',
436 type='string',
437 metavar='STATUS')
438 self.parser.add_option('-n', '--note',
439 help='Note of the server',
440 type='string',
441 default=None,
442 metavar='NOTE')
443 self.parser.add_option('-d', '--delete',
444 help=('Set to True to delete given role.'),
445 action='store_true',
446 default=False,
447 metavar='DELETE')
448 self.parser.add_option('-a', '--attribute',
449 help='Name of the attribute of the server',
450 type='string',
451 default=None,
452 metavar='ATTRIBUTE')
453 self.parser.add_option('-e', '--value',
454 help='Value for the attribute of the server',
455 type='string',
456 default=None,
457 metavar='VALUE')
458
459
460 def parse(self):
461 """Parse command arguments.
462 """
463 (options, leftover) = super(server_modify, self).parse()
464 self.status = options.status
465 self.note = options.note
466 self.delete = options.delete
467 self.attribute = options.attribute
468 self.value = options.value
Dan Shi56f1ba72014-12-03 19:16:53 -0800469 self.action = options.action
Dan Shi784df0c2014-11-26 10:11:15 -0800470
471 # modify supports various options. However, it's safer to limit one
472 # option at a time so no complicated role-dependent logic is needed
473 # to handle scenario that both role and status are changed.
474 # self.parser is optparse, which does not have function in argparse like
475 # add_mutually_exclusive_group. That's why the count is used here.
476 flags = [self.status is not None, self.role is not None,
477 self.attribute is not None, self.note is not None]
478 if flags.count(True) != 1:
479 msg = ('Action modify only support one option at a time. You can '
480 'try one of following 5 options:\n'
481 '1. --status: Change server\'s status.\n'
482 '2. --note: Change server\'s note.\n'
483 '3. --role with optional -d: Add/delete role from server.\n'
484 '4. --attribute --value: Set/change the value of a '
485 'server\'s attribute.\n'
486 '5. --attribute -d: Delete the attribute from the '
487 'server.\n'
488 '\nUse option -h to see a complete list of options.')
489 self.invalid_syntax(msg)
490 if (self.status != None or self.note != None) and self.delete:
491 self.invalid_syntax('--delete does not apply to status or note.')
492 if self.attribute != None and not self.delete and self.value == None:
493 self.invalid_syntax('--attribute must be used with option --value '
494 'or --delete.')
Ningning Xia84190b82018-04-16 15:01:40 -0700495
496 # TODO(nxia): crbug.com/832964 support --action with --skylab
497 if self.skylab and self.action:
498 self.invalid_syntax('--action is currently not supported with'
499 ' --skylab.')
500
Dan Shi784df0c2014-11-26 10:11:15 -0800501 return (options, leftover)
502
503
Ningning Xia84190b82018-04-16 15:01:40 -0700504 def execute_skylab(self):
505 """Execute the command for skylab inventory changes."""
506 inventory_repo = skylab_utils.InventoryRepo(
507 self.inventory_repo_dir)
508 inventory_repo.initialize()
509 data_dir = inventory_repo.get_data_dir()
Ningning Xiae8714052018-04-30 18:58:54 -0700510 infrastructure = text_manager.load_infrastructure(data_dir)
Ningning Xia84190b82018-04-16 15:01:40 -0700511
512 target_server = skylab_server.modify(
513 infrastructure,
514 self.hostname,
Ningning Xiae8714052018-04-30 18:58:54 -0700515 self.environment,
Ningning Xia84190b82018-04-16 15:01:40 -0700516 role=self.role,
517 status=self.status,
518 delete_role=self.delete,
519 note=self.note,
520 attribute=self.attribute,
521 value=self.value,
522 delete_attribute=self.delete)
Ningning Xiae8714052018-04-30 18:58:54 -0700523 text_manager.dump_infrastructure(data_dir, infrastructure)
Ningning Xia84190b82018-04-16 15:01:40 -0700524
525 status = inventory_repo.git_repo.status()
526 if not status:
527 print('Nothing is changed for server %s.' % self.hostname)
528 return
529
530 message = skylab_utils.construct_commit_message(
531 'Modify server: %s' % self.hostname)
Ningning Xiaa043aad2018-04-23 15:07:09 -0700532 self.change_number = inventory_repo.upload_change(
533 message, draft=self.draft, dryrun=self.dryrun,
534 submit=self.submit)
Ningning Xia84190b82018-04-16 15:01:40 -0700535
536 return target_server
537
538
Dan Shi784df0c2014-11-26 10:11:15 -0800539 def execute(self):
540 """Execute the command.
541
542 @return: The updated server object if it is modified successfully.
543 """
Shuqian Zhaodb205af2018-02-28 15:13:03 -0800544 if RESPECT_SKYLAB_SERVERDB:
545 self.failure(ATEST_DISABLE_MSG,
546 what_failed='Failed to modify server',
547 item=self.hostname, fatal=True)
548
Ningning Xia84190b82018-04-16 15:01:40 -0700549 if self.skylab:
550 try:
551 return self.execute_skylab()
552 except (skylab_server.SkylabServerActionError,
Ningning Xiaa043aad2018-04-23 15:07:09 -0700553 revision_control.GitError,
554 gob_util.GOBError) as e:
Ningning Xia84190b82018-04-16 15:01:40 -0700555 self.failure(e, what_failed='Failed to modify server in skylab'
556 ' inventory.', item=self.hostname, fatal=True)
557 else:
558 try:
559 return server_manager.modify(
560 hostname=self.hostname, role=self.role,
561 status=self.status, delete=self.delete,
562 note=self.note, attribute=self.attribute,
563 value=self.value, action=self.action)
564 except (server_manager_utils.ServerActionError,
565 error.InvalidDataError) as e:
566 self.failure(e, what_failed='Failed to modify server',
567 item=self.hostname, fatal=True)
Dan Shi784df0c2014-11-26 10:11:15 -0800568
569
570 def output(self, results):
571 """Display output.
572
573 @param results: return of the execute call, which is the updated server
574 object.
575 """
576 if results:
Ningning Xia84190b82018-04-16 15:01:40 -0700577 print 'Server %s is modified.\n' % self.hostname
Dan Shi784df0c2014-11-26 10:11:15 -0800578 print results
Ningning Xia84190b82018-04-16 15:01:40 -0700579
Ningning Xiaa043aad2018-04-23 15:07:09 -0700580 if self.skylab and not self.dryrun and not self.submit:
Ningning Xiaef35cb52018-05-04 17:58:20 -0700581 print skylab_utils.get_cl_message(self.change_number)