xref: /aosp_15_r20/external/autotest/cli/server.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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