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