blob: a9483e1c1bc753afda2eb1d428c66ef521da43b6 [file] [log] [blame]
mblighbe630eb2008-08-01 16:41:48 +00001#
2# Copyright 2008 Google Inc. All Rights Reserved.
3
4"""
5The host module contains the objects and method used to
6manage a host in Autotest.
7
8The valid actions are:
9create: adds host(s)
10delete: deletes host(s)
11list: lists host(s)
12stat: displays host(s) information
13mod: modifies host(s)
14jobs: lists all jobs that ran on host(s)
15
16The common options are:
17-M|--mlist: file containing a list of machines
18
19
20stat as has additional options:
21--lock/-l: Locks host(s)
22--unlock/-u: Unlock host(s)
23--ready/-y: Marks host(s) ready
24--dead/-d: Marks host(s) dead
25
26See topic_common.py for a High Level Design and Algorithm.
27
28"""
29
mbligh6996fe82008-08-13 00:32:27 +000030import os, sys, socket
mblighbe630eb2008-08-01 16:41:48 +000031from autotest_lib.cli import topic_common, action_common
32
33
34class host(topic_common.atest):
35 """Host class
36 atest host [create|delete|list|stat|mod|jobs] <options>"""
37 usage_action = '[create|delete|list|stat|mod|jobs]'
38 topic = msg_topic = 'host'
39 msg_items = '<hosts>'
40
41
42 def __init__(self):
43 """Add to the parser the options common to all the
44 host actions"""
45 super(host, self).__init__()
46
47 self.parser.add_option('-M', '--mlist',
48 help='File listing the machines',
49 type='string',
50 default=None,
51 metavar='MACHINE_FLIST')
52
53
54 def parse(self, flists=None, req_items='hosts'):
55 """Consume the common host options"""
56 if flists:
57 flists.append(('hosts', 'mlist', '', True))
58 else:
59 flists = [('hosts', 'mlist', '', True)]
60 return self.parse_with_flist(flists, req_items)
61
62
63 def _parse_lock_options(self, options):
64 if options.lock and options.unlock:
65 self.invalid_syntax('Only specify one of '
66 '--lock and --unlock.')
67
68 if options.lock:
69 self.data['locked'] = True
70 self.messages.append('Locked host')
71 elif options.unlock:
72 self.data['locked'] = False
73 self.messages.append('Unlocked host')
74
75
76 def _cleanup_labels(self, labels, platform=None):
77 """Removes the platform label from the overall labels"""
78 if platform:
79 return [label for label in labels
80 if label != platform]
81 else:
82 try:
83 return [label for label in labels
84 if not label['platform']]
85 except TypeError:
86 # This is a hack - the server will soon
87 # do this, so all this code should be removed.
88 return labels
89
90
91 def get_items(self):
92 return self.hosts
93
94
95class host_help(host):
96 """Just here to get the atest logic working.
97 Usage is set by its parent"""
98 pass
99
100
101class host_list(action_common.atest_list, host):
102 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +0000103 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +0000104
105 def __init__(self):
106 super(host_list, self).__init__()
107
108 self.parser.add_option('-b', '--label',
109 help='Only list hosts with this label')
110 self.parser.add_option('-s', '--status',
111 help='Only list hosts with this status')
mbligh536a5242008-10-18 14:35:54 +0000112 self.parser.add_option('-a', '--acl',
113 help='Only list hosts within this ACL')
114 self.parser.add_option('-u', '--user',
115 help='Only list hosts available to this user')
mblighbe630eb2008-08-01 16:41:48 +0000116
117
118 def parse(self):
119 """Consume the specific options"""
120 (options, leftover) = super(host_list, self).parse(req_items=None)
121 self.label = options.label
122 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000123 self.acl = options.acl
124 self.user = options.user
mblighbe630eb2008-08-01 16:41:48 +0000125 return (options, leftover)
126
127
128 def execute(self):
129 filters = {}
130 check_results = {}
131 if self.hosts:
132 filters['hostname__in'] = self.hosts
133 check_results['hostname__in'] = 'hostname'
134 if self.label:
135 filters['labels__name'] = self.label
136 check_results['labels__name'] = None
137 if self.status:
138 filters['status__in'] = self.status.split(',')
mblighcd8eb972008-08-25 19:20:39 +0000139 check_results['status__in'] = None
mbligh536a5242008-10-18 14:35:54 +0000140 if self.acl:
141 filters['acl_group__name'] = self.acl
142 check_results['acl_group__name'] = None
143 if self.user:
144 filters['acl_group__users__login'] = self.user
145 check_results['acl_group__users__login'] = None
mblighbe630eb2008-08-01 16:41:48 +0000146 return super(host_list, self).execute(op='get_hosts',
147 filters=filters,
148 check_results=check_results)
149
150
151 def output(self, results):
152 if results:
153 # Remove the platform from the labels.
154 for result in results:
155 result['labels'] = self._cleanup_labels(result['labels'],
156 result['platform'])
157 super(host_list, self).output(results,
158 keys=['hostname', 'status',
159 'locked', 'platform',
160 'labels'])
161
162
163class host_stat(host):
164 """atest host stat --mlist <file>|<hosts>"""
165 usage_action = 'stat'
166
167 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000168 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000169 results = []
170 # Convert wildcards into real host stats.
171 existing_hosts = []
172 for host in self.hosts:
173 if host.endswith('*'):
174 stats = self.execute_rpc('get_hosts',
175 hostname__startswith=host.rstrip('*'))
176 if len(stats) == 0:
177 self.failure('No hosts matching %s' % host, item=host,
178 what_failed='Failed to stat')
179 continue
180 else:
181 stats = self.execute_rpc('get_hosts', hostname=host)
182 if len(stats) == 0:
183 self.failure('Unknown host %s' % host, item=host,
184 what_failed='Failed to stat')
185 continue
186 existing_hosts.extend(stats)
187
188 for stat in existing_hosts:
189 host = stat['hostname']
190 # The host exists, these should succeed
191 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
192
193 labels = self.execute_rpc('get_labels', host__hostname=host)
194 results.append ([[stat], acls, labels])
195 return results
196
197
198 def output(self, results):
199 for stats, acls, labels in results:
200 print '-'*5
201 self.print_fields(stats,
202 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000203 'status', 'locked', 'locked_by',
204 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000205 self.print_by_ids(acls, 'ACLs', line_before=True)
206 labels = self._cleanup_labels(labels)
207 self.print_by_ids(labels, 'Labels', line_before=True)
208
209
210class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000211 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000212 usage_action = 'jobs'
213
mbligh6996fe82008-08-13 00:32:27 +0000214 def __init__(self):
215 super(host_jobs, self).__init__()
216 self.parser.add_option('-q', '--max-query',
217 help='Limits the number of results '
218 '(20 by default)',
219 type='int', default=20)
220
221
222 def parse(self):
223 """Consume the specific options"""
224 (options, leftover) = super(host_jobs, self).parse(req_items=None)
225 self.max_queries = options.max_query
226 return (options, leftover)
227
228
mblighbe630eb2008-08-01 16:41:48 +0000229 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000230 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000231 results = []
232 real_hosts = []
233 for host in self.hosts:
234 if host.endswith('*'):
235 stats = self.execute_rpc('get_hosts',
236 hostname__startswith=host.rstrip('*'))
237 if len(stats) == 0:
238 self.failure('No host matching %s' % host, item=host,
239 what_failed='Failed to stat')
240 [real_hosts.append(stat['hostname']) for stat in stats]
241 else:
242 real_hosts.append(host)
243
244 for host in real_hosts:
245 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000246 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000247 query_limit=self.max_queries,
248 sort_by=['-job_id'])
mblighbe630eb2008-08-01 16:41:48 +0000249 jobs = []
250 for entry in queue_entries:
251 job = {'job_id': entry['job']['id'],
252 'job_owner': entry['job']['owner'],
253 'job_name': entry['job']['name'],
254 'status': entry['status']}
255 jobs.append(job)
256 results.append((host, jobs))
257 return results
258
259
260 def output(self, results):
261 for host, jobs in results:
262 print '-'*5
263 print 'Hostname: %s' % host
264 self.print_table(jobs, keys_header=['job_id',
265 'job_owner',
266 'job_name',
267 'status'])
268
269
270class host_mod(host):
271 """atest host mod --lock|--unlock|--ready|--dead
272 --mlist <file>|<hosts>"""
273 usage_action = 'mod'
274
275 def __init__(self):
276 """Add the options specific to the mod action"""
277 self.data = {}
278 self.messages = []
279 super(host_mod, self).__init__()
280 self.parser.add_option('-y', '--ready',
281 help='Mark this host ready',
282 action='store_true')
283 self.parser.add_option('-d', '--dead',
284 help='Mark this host dead',
285 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000286 self.parser.add_option('-l', '--lock',
287 help='Lock hosts',
288 action='store_true')
289 self.parser.add_option('-u', '--unlock',
290 help='Unlock hosts',
291 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000292 self.parser.add_option('-p', '--protection', type='choice',
293 help='Set the protection level on a host. Must '
294 'be one of: "Repair filesystem only", '
295 '"No protection", or "Do not repair"',
296 choices=('No protection', 'Do not repair',
297 'Repair filesystem only'))
mblighbe630eb2008-08-01 16:41:48 +0000298
299
300 def parse(self):
301 """Consume the specific options"""
302 (options, leftover) = super(host_mod, self).parse()
303
304 self._parse_lock_options(options)
305
306 if options.ready and options.dead:
307 self.invalid_syntax('Only specify one of '
308 '--ready and --dead')
309
310 if options.ready:
311 self.data['status'] = 'Ready'
312 self.messages.append('Set status to Ready for host')
313 elif options.dead:
314 self.data['status'] = 'Dead'
315 self.messages.append('Set status to Dead for host')
316
mblighe163b032008-10-18 14:30:27 +0000317 if options.protection:
318 self.data['protection'] = options.protection
319
mblighbe630eb2008-08-01 16:41:48 +0000320 if len(self.data) == 0:
321 self.invalid_syntax('No modification requested')
322 return (options, leftover)
323
324
325 def execute(self):
326 successes = []
327 for host in self.hosts:
328 res = self.execute_rpc('modify_host', id=host, **self.data)
329 # TODO: Make the AFE return True or False,
330 # especially for lock
331 if res is None:
332 successes.append(host)
333 else:
334 self.invalid_arg("Unknown host %s" % host)
335 return successes
336
337
338 def output(self, hosts):
339 for msg in self.messages:
340 self.print_wrapped(msg, hosts)
341
342
343
344class host_create(host):
345 """atest host create [--lock|--unlock --platform <arch>
346 --labels <labels>|--blist <label_file>
347 --acls <acls>|--alist <acl_file>
348 --mlist <mach_file>] <hosts>"""
349 usage_action = 'create'
350
351 def __init__(self):
352 self.messages = []
353 super(host_create, self).__init__()
354 self.parser.add_option('-l', '--lock',
355 help='Create the hosts as locked',
356 action='store_true')
357 self.parser.add_option('-u', '--unlock',
358 help='Create the hosts as '
359 'unlocked (default)',
360 action='store_true')
361 self.parser.add_option('-t', '--platform',
362 help='Sets the platform label')
363 self.parser.add_option('-b', '--labels',
364 help='Comma separated list of labels')
365 self.parser.add_option('-B', '--blist',
366 help='File listing the labels',
367 type='string',
368 metavar='LABEL_FLIST')
369 self.parser.add_option('-a', '--acls',
370 help='Comma separated list of ACLs')
371 self.parser.add_option('-A', '--alist',
372 help='File listing the acls',
373 type='string',
374 metavar='ACL_FLIST')
375
376
377 def parse(self):
378 flists = [('labels', 'blist', 'labels', False),
379 ('acls', 'alist', 'acls', False)]
380 (options, leftover) = super(host_create, self).parse(flists)
381
382 self._parse_lock_options(options)
383 self.platform = getattr(options, 'platform', None)
384 return (options, leftover)
385
386
387 def _execute_add_one_host(self, host):
388 self.execute_rpc('add_host', hostname=host,
389 status="Ready", **self.data)
390
391 # Now add the platform label
392 labels = self.labels[:]
393 if self.platform:
394 labels.append(self.platform)
395 if len (labels):
396 self.execute_rpc('host_add_labels', id=host, labels=labels)
397
398
399 def execute(self):
400 # We need to check if these labels & ACLs exist,
401 # and create them if not.
402 if self.platform:
403 self.check_and_create_items('get_labels', 'add_label',
404 [self.platform],
405 platform=True)
406
407 if self.labels:
408 self.check_and_create_items('get_labels', 'add_label',
409 self.labels,
410 platform=False)
411
412 if self.acls:
413 self.check_and_create_items('get_acl_groups',
414 'add_acl_group',
415 self.acls)
416
417 success = self.site_create_hosts_hook()
418
419 if len(success):
420 for acl in self.acls:
421 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
422 return success
423
424
425 def site_create_hosts_hook(self):
426 success = []
427 for host in self.hosts:
428 try:
429 self._execute_add_one_host(host)
430 success.append(host)
431 except topic_common.CliError:
432 pass
433
434 return success
435
436
437 def output(self, hosts):
438 self.print_wrapped('Added host', hosts)
439
440
441class host_delete(action_common.atest_delete, host):
442 """atest host delete [--mlist <mach_file>] <hosts>"""
443 pass