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