• 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                               metavar='TABLE')
127        self.parser.add_option('-s', '--status',
128                               help='Only show servers with given status',
129                               type='string',
130                               default=None,
131                               metavar='STATUS')
132        self.parser.add_option('-u', '--summary',
133                               help=('Show the summary of roles and status '
134                                     'only, e.g.,\tscheduler: server1(primary) '
135                                     'server2(backup)\t\tdrone: server3(primary'
136                                     ') server4(backup)'),
137                               action='store_true',
138                               default=False,
139                               metavar='SUMMARY')
140
141
142    def parse(self):
143        """Parse command arguments.
144        """
145        (options, leftover) = super(server_list, self).parse()
146        self.table = options.table
147        self.status = options.status
148        self.summary = options.summary
149        if self.table and self.summary:
150            self.invalid_syntax('Option --table and --summary cannot be both '
151                                'specified.')
152        return (options, leftover)
153
154
155    def execute(self):
156        """Execute the command.
157
158        @return: A list of servers matched given hostname and role.
159        """
160        try:
161            return server_manager_utils.get_servers(hostname=self.hostname,
162                                                    role=self.role,
163                                                    status=self.status)
164        except (server_manager_utils.ServerActionError,
165                error.InvalidDataError) as e:
166            self.failure(e, what_failed='Failed to find servers',
167                         item=self.hostname, fatal=True)
168
169
170    def output(self, results):
171        """Display output.
172
173        @param results: return of the execute call, a list of server object that
174                        contains server information.
175        """
176        if not results:
177            self.failure('No server is found.',
178                         what_failed='Failed to find servers',
179                         item=self.hostname, fatal=True)
180        else:
181            print server_manager_utils.get_server_details(results, self.table,
182                                                          self.summary)
183
184
185class server_create(server):
186    """atest server create hostname --role <role> --note <note>
187    """
188
189    def __init__(self):
190        """Initializer.
191        """
192        super(server_create, self).__init__()
193        self.parser.add_option('-n', '--note',
194                               help='note of the server',
195                               type='string',
196                               default=None,
197                               metavar='NOTE')
198
199
200    def parse(self):
201        """Parse command arguments.
202        """
203        (options, leftover) = super(server_create, self).parse()
204        self.note = options.note
205
206        if not self.role:
207            self.invalid_syntax('--role is required to create a server.')
208
209        return (options, leftover)
210
211
212    def execute(self):
213        """Execute the command.
214
215        @return: A Server object if it is created successfully.
216        """
217        try:
218            return server_manager.create(hostname=self.hostname, role=self.role,
219                                         note=self.note)
220        except (server_manager_utils.ServerActionError,
221                error.InvalidDataError) as e:
222            self.failure(e, what_failed='Failed to create server',
223                         item=self.hostname, fatal=True)
224
225
226    def output(self, results):
227        """Display output.
228
229        @param results: return of the execute call, a server object that
230                        contains server information.
231        """
232        if results:
233            print 'Server %s is added to server database:\n' % self.hostname
234            print results
235
236
237class server_delete(server):
238    """atest server delete hostname"""
239
240    def execute(self):
241        """Execute the command.
242
243        @return: True if server is deleted successfully.
244        """
245        try:
246            server_manager.delete(hostname=self.hostname)
247            return True
248        except (server_manager_utils.ServerActionError,
249                error.InvalidDataError) as e:
250            self.failure(e, what_failed='Failed to delete server',
251                         item=self.hostname, fatal=True)
252
253
254    def output(self, results):
255        """Display output.
256
257        @param results: return of the execute call.
258        """
259        if results:
260            print ('Server %s is deleted from server database successfully.' %
261                   self.hostname)
262
263
264class server_modify(server):
265    """atest server modify hostname
266
267    modify action can only change one input at a time. Available inputs are:
268    --status:       Status of the server.
269    --note:         Note of the server.
270    --role:         New role to be added to the server.
271    --delete_role:  Existing role to be deleted from the server.
272    """
273
274    def __init__(self):
275        """Initializer.
276        """
277        super(server_modify, self).__init__()
278        self.parser.add_option('-s', '--status',
279                               help='Status of the server',
280                               type='string',
281                               metavar='STATUS')
282        self.parser.add_option('-n', '--note',
283                               help='Note of the server',
284                               type='string',
285                               default=None,
286                               metavar='NOTE')
287        self.parser.add_option('-d', '--delete',
288                               help=('Set to True to delete given role.'),
289                               action='store_true',
290                               default=False,
291                               metavar='DELETE')
292        self.parser.add_option('-a', '--attribute',
293                               help='Name of the attribute of the server',
294                               type='string',
295                               default=None,
296                               metavar='ATTRIBUTE')
297        self.parser.add_option('-e', '--value',
298                               help='Value for the attribute of the server',
299                               type='string',
300                               default=None,
301                               metavar='VALUE')
302
303
304    def parse(self):
305        """Parse command arguments.
306        """
307        (options, leftover) = super(server_modify, self).parse()
308        self.status = options.status
309        self.note = options.note
310        self.delete = options.delete
311        self.attribute = options.attribute
312        self.value = options.value
313        self.action = options.action
314
315        # modify supports various options. However, it's safer to limit one
316        # option at a time so no complicated role-dependent logic is needed
317        # to handle scenario that both role and status are changed.
318        # self.parser is optparse, which does not have function in argparse like
319        # add_mutually_exclusive_group. That's why the count is used here.
320        flags = [self.status is not None, self.role is not None,
321                 self.attribute is not None, self.note is not None]
322        if flags.count(True) != 1:
323            msg = ('Action modify only support one option at a time. You can '
324                   'try one of following 5 options:\n'
325                   '1. --status:                Change server\'s status.\n'
326                   '2. --note:                  Change server\'s note.\n'
327                   '3. --role with optional -d: Add/delete role from server.\n'
328                   '4. --attribute --value:     Set/change the value of a '
329                   'server\'s attribute.\n'
330                   '5. --attribute -d:          Delete the attribute from the '
331                   'server.\n'
332                   '\nUse option -h to see a complete list of options.')
333            self.invalid_syntax(msg)
334        if (self.status != None or self.note != None) and self.delete:
335            self.invalid_syntax('--delete does not apply to status or note.')
336        if self.attribute != None and not self.delete and self.value == None:
337            self.invalid_syntax('--attribute must be used with option --value '
338                                'or --delete.')
339        return (options, leftover)
340
341
342    def execute(self):
343        """Execute the command.
344
345        @return: The updated server object if it is modified successfully.
346        """
347        try:
348            return server_manager.modify(hostname=self.hostname, role=self.role,
349                                         status=self.status, delete=self.delete,
350                                         note=self.note,
351                                         attribute=self.attribute,
352                                         value=self.value, action=self.action)
353        except (server_manager_utils.ServerActionError,
354                error.InvalidDataError) as e:
355            self.failure(e, what_failed='Failed to modify server',
356                         item=self.hostname, fatal=True)
357
358
359    def output(self, results):
360        """Display output.
361
362        @param results: return of the execute call, which is the updated server
363                        object.
364        """
365        if results:
366            print 'Server %s is modified successfully.' % self.hostname
367            print results
368