blob: e0c57177909cb199c64387bbd322c70b8142467e [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
24from autotest_lib.cli import topic_common
25from autotest_lib.client.common_lib import error
Dan Shib9144a42014-12-01 16:09:32 -080026# 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.
Dan Shi56f1ba72014-12-03 19:16:53 -080028from autotest_lib.frontend import setup_django_environment
Dan Shi784df0c2014-11-26 10:11:15 -080029from autotest_lib.site_utils import server_manager
Dan Shi56f1ba72014-12-03 19:16:53 -080030from autotest_lib.site_utils import server_manager_utils
Dan Shi784df0c2014-11-26 10:11:15 -080031
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')
Dan Shi56f1ba72014-12-03 19:16:53 -080055 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')
Dan Shi784df0c2014-11-26 10:11:15 -080062
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:
Dan Shi56f1ba72014-12-03 19:16:53 -0800161 return server_manager_utils.get_servers(hostname=self.hostname,
162 role=self.role,
163 status=self.status)
164 except (server_manager_utils.ServerActionError,
Dan Shi784df0c2014-11-26 10:11:15 -0800165 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:
Dan Shi56f1ba72014-12-03 19:16:53 -0800181 print server_manager_utils.get_server_details(results, self.table,
182 self.summary)
Dan Shi784df0c2014-11-26 10:11:15 -0800183
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)
Dan Shi56f1ba72014-12-03 19:16:53 -0800220 except (server_manager_utils.ServerActionError,
Dan Shi784df0c2014-11-26 10:11:15 -0800221 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
Dan Shi56f1ba72014-12-03 19:16:53 -0800248 except (server_manager_utils.ServerActionError,
Dan Shi784df0c2014-11-26 10:11:15 -0800249 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
Dan Shi56f1ba72014-12-03 19:16:53 -0800313 self.action = options.action
Dan Shi784df0c2014-11-26 10:11:15 -0800314
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,
Dan Shi56f1ba72014-12-03 19:16:53 -0800352 value=self.value, action=self.action)
353 except (server_manager_utils.ServerActionError,
Dan Shi784df0c2014-11-26 10:11:15 -0800354 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