blob: e5bfd10810602ecf6daf76ff09a7629689841665 [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
mblighbe630eb2008-08-01 16:41:48 +000020See topic_common.py for a High Level Design and Algorithm.
21
22"""
23
mbligh6996fe82008-08-13 00:32:27 +000024import os, sys, socket
mblighbe630eb2008-08-01 16:41:48 +000025from autotest_lib.cli import topic_common, action_common
mblighaed47e82009-03-17 19:06:18 +000026from autotest_lib.client.common_lib import host_protections
mblighbe630eb2008-08-01 16:41:48 +000027
28
29class host(topic_common.atest):
30 """Host class
31 atest host [create|delete|list|stat|mod|jobs] <options>"""
32 usage_action = '[create|delete|list|stat|mod|jobs]'
33 topic = msg_topic = 'host'
34 msg_items = '<hosts>'
35
mblighaed47e82009-03-17 19:06:18 +000036 protections = host_protections.Protection.names
37
mblighbe630eb2008-08-01 16:41:48 +000038
39 def __init__(self):
40 """Add to the parser the options common to all the
41 host actions"""
42 super(host, self).__init__()
43
44 self.parser.add_option('-M', '--mlist',
45 help='File listing the machines',
46 type='string',
47 default=None,
48 metavar='MACHINE_FLIST')
49
mbligh9deeefa2009-05-01 23:11:08 +000050 self.topic_parse_info = topic_common.item_parse_info(
51 attribute_name='hosts',
52 filename_option='mlist',
53 use_leftover=True)
mblighbe630eb2008-08-01 16:41:48 +000054
55
56 def _parse_lock_options(self, options):
57 if options.lock and options.unlock:
58 self.invalid_syntax('Only specify one of '
59 '--lock and --unlock.')
60
61 if options.lock:
62 self.data['locked'] = True
63 self.messages.append('Locked host')
64 elif options.unlock:
65 self.data['locked'] = False
66 self.messages.append('Unlocked host')
67
68
69 def _cleanup_labels(self, labels, platform=None):
70 """Removes the platform label from the overall labels"""
71 if platform:
72 return [label for label in labels
73 if label != platform]
74 else:
75 try:
76 return [label for label in labels
77 if not label['platform']]
78 except TypeError:
79 # This is a hack - the server will soon
80 # do this, so all this code should be removed.
81 return labels
82
83
84 def get_items(self):
85 return self.hosts
86
87
88class host_help(host):
89 """Just here to get the atest logic working.
90 Usage is set by its parent"""
91 pass
92
93
94class host_list(action_common.atest_list, host):
95 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +000096 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +000097
98 def __init__(self):
99 super(host_list, self).__init__()
100
101 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000102 default='',
103 help='Only list hosts with all these labels '
104 '(comma separated)')
mblighbe630eb2008-08-01 16:41:48 +0000105 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000106 default='',
107 help='Only list hosts with any of these '
108 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000109 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000110 default='',
mbligh536a5242008-10-18 14:35:54 +0000111 help='Only list hosts within this ACL')
112 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000113 default='',
mbligh536a5242008-10-18 14:35:54 +0000114 help='Only list hosts available to this user')
mbligh70b9bf42008-11-18 15:00:46 +0000115 self.parser.add_option('-N', '--hostnames-only', help='Only return '
116 'hostnames for the machines queried.',
117 action='store_true')
mbligh91e0efd2009-02-26 01:02:16 +0000118 self.parser.add_option('--locked',
119 default=False,
120 help='Only list locked hosts',
121 action='store_true')
122 self.parser.add_option('--unlocked',
123 default=False,
124 help='Only list unlocked hosts',
125 action='store_true')
126
mblighbe630eb2008-08-01 16:41:48 +0000127
128
129 def parse(self):
130 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000131 (options, leftover) = super(host_list, self).parse()
mbligh6444c6b2008-10-27 20:55:13 +0000132 self.labels = options.label
mblighbe630eb2008-08-01 16:41:48 +0000133 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000134 self.acl = options.acl
135 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000136 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000137
138 if options.locked and options.unlocked:
139 self.invalid_syntax('--locked and --unlocked are '
140 'mutually exclusive')
141 self.locked = options.locked
142 self.unlocked = options.unlocked
mblighbe630eb2008-08-01 16:41:48 +0000143 return (options, leftover)
144
145
146 def execute(self):
147 filters = {}
148 check_results = {}
149 if self.hosts:
150 filters['hostname__in'] = self.hosts
151 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000152
153 if self.labels:
154 labels = self.labels.split(',')
155 labels = [label.strip() for label in labels if label.strip()]
156
mblighf703fb42009-01-30 00:35:05 +0000157 if len(labels) == 1:
158 # This is needed for labels with wildcards (x86*)
159 filters['labels__name__in'] = labels
160 check_results['labels__name__in'] = None
161 else:
162 filters['multiple_labels'] = labels
163 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000164
mblighbe630eb2008-08-01 16:41:48 +0000165 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000166 statuses = self.status.split(',')
167 statuses = [status.strip() for status in statuses
168 if status.strip()]
169
170 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000171 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000172
mbligh536a5242008-10-18 14:35:54 +0000173 if self.acl:
showardd9ac4452009-02-07 02:04:37 +0000174 filters['aclgroup__name'] = self.acl
175 check_results['aclgroup__name'] = None
mbligh536a5242008-10-18 14:35:54 +0000176 if self.user:
showardd9ac4452009-02-07 02:04:37 +0000177 filters['aclgroup__users__login'] = self.user
178 check_results['aclgroup__users__login'] = None
mbligh91e0efd2009-02-26 01:02:16 +0000179
180 if self.locked or self.unlocked:
181 filters['locked'] = self.locked
182 check_results['locked'] = None
183
mblighbe630eb2008-08-01 16:41:48 +0000184 return super(host_list, self).execute(op='get_hosts',
185 filters=filters,
186 check_results=check_results)
187
188
189 def output(self, results):
190 if results:
191 # Remove the platform from the labels.
192 for result in results:
193 result['labels'] = self._cleanup_labels(result['labels'],
194 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000195 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000196 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000197 else:
198 super(host_list, self).output(results, keys=['hostname', 'status',
199 'locked', 'platform', 'labels'])
mblighbe630eb2008-08-01 16:41:48 +0000200
201
202class host_stat(host):
203 """atest host stat --mlist <file>|<hosts>"""
204 usage_action = 'stat'
205
206 def execute(self):
207 results = []
208 # Convert wildcards into real host stats.
209 existing_hosts = []
210 for host in self.hosts:
211 if host.endswith('*'):
212 stats = self.execute_rpc('get_hosts',
213 hostname__startswith=host.rstrip('*'))
214 if len(stats) == 0:
215 self.failure('No hosts matching %s' % host, item=host,
216 what_failed='Failed to stat')
217 continue
218 else:
219 stats = self.execute_rpc('get_hosts', hostname=host)
220 if len(stats) == 0:
221 self.failure('Unknown host %s' % host, item=host,
222 what_failed='Failed to stat')
223 continue
224 existing_hosts.extend(stats)
225
226 for stat in existing_hosts:
227 host = stat['hostname']
228 # The host exists, these should succeed
229 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
230
231 labels = self.execute_rpc('get_labels', host__hostname=host)
232 results.append ([[stat], acls, labels])
233 return results
234
235
236 def output(self, results):
237 for stats, acls, labels in results:
238 print '-'*5
239 self.print_fields(stats,
240 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000241 'status', 'locked', 'locked_by',
242 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000243 self.print_by_ids(acls, 'ACLs', line_before=True)
244 labels = self._cleanup_labels(labels)
245 self.print_by_ids(labels, 'Labels', line_before=True)
246
247
248class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000249 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000250 usage_action = 'jobs'
251
mbligh6996fe82008-08-13 00:32:27 +0000252 def __init__(self):
253 super(host_jobs, self).__init__()
254 self.parser.add_option('-q', '--max-query',
255 help='Limits the number of results '
256 '(20 by default)',
257 type='int', default=20)
258
259
260 def parse(self):
261 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000262 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000263 self.max_queries = options.max_query
264 return (options, leftover)
265
266
mblighbe630eb2008-08-01 16:41:48 +0000267 def execute(self):
268 results = []
269 real_hosts = []
270 for host in self.hosts:
271 if host.endswith('*'):
272 stats = self.execute_rpc('get_hosts',
273 hostname__startswith=host.rstrip('*'))
274 if len(stats) == 0:
275 self.failure('No host matching %s' % host, item=host,
276 what_failed='Failed to stat')
277 [real_hosts.append(stat['hostname']) for stat in stats]
278 else:
279 real_hosts.append(host)
280
281 for host in real_hosts:
282 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000283 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000284 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000285 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000286 jobs = []
287 for entry in queue_entries:
288 job = {'job_id': entry['job']['id'],
289 'job_owner': entry['job']['owner'],
290 'job_name': entry['job']['name'],
291 'status': entry['status']}
292 jobs.append(job)
293 results.append((host, jobs))
294 return results
295
296
297 def output(self, results):
298 for host, jobs in results:
299 print '-'*5
300 print 'Hostname: %s' % host
301 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000302 'job_owner',
303 'job_name',
304 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000305
306
307class host_mod(host):
showardbe0d8692009-08-20 23:42:44 +0000308 """atest host mod --lock|--unlock|--protection
mblighbe630eb2008-08-01 16:41:48 +0000309 --mlist <file>|<hosts>"""
310 usage_action = 'mod'
311
312 def __init__(self):
313 """Add the options specific to the mod action"""
314 self.data = {}
315 self.messages = []
316 super(host_mod, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000317 self.parser.add_option('-l', '--lock',
318 help='Lock hosts',
319 action='store_true')
320 self.parser.add_option('-u', '--unlock',
321 help='Unlock hosts',
322 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000323 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000324 help=('Set the protection level on a host. '
325 'Must be one of: %s' %
326 ', '.join('"%s"' % p
327 for p in self.protections)),
328 choices=self.protections)
mblighbe630eb2008-08-01 16:41:48 +0000329
330
331 def parse(self):
332 """Consume the specific options"""
333 (options, leftover) = super(host_mod, self).parse()
334
335 self._parse_lock_options(options)
336
mblighe163b032008-10-18 14:30:27 +0000337 if options.protection:
338 self.data['protection'] = options.protection
showardbe0d8692009-08-20 23:42:44 +0000339 self.messages.append('Protection set to "%s"' % options.protection)
mblighe163b032008-10-18 14:30:27 +0000340
mblighbe630eb2008-08-01 16:41:48 +0000341 if len(self.data) == 0:
342 self.invalid_syntax('No modification requested')
343 return (options, leftover)
344
345
346 def execute(self):
347 successes = []
348 for host in self.hosts:
mbligh6eca4602009-01-07 16:48:50 +0000349 try:
350 res = self.execute_rpc('modify_host', item=host,
351 id=host, **self.data)
352 # TODO: Make the AFE return True or False,
353 # especially for lock
mblighbe630eb2008-08-01 16:41:48 +0000354 successes.append(host)
mbligh6eca4602009-01-07 16:48:50 +0000355 except topic_common.CliError, full_error:
356 # Already logged by execute_rpc()
357 pass
358
mblighbe630eb2008-08-01 16:41:48 +0000359 return successes
360
361
362 def output(self, hosts):
363 for msg in self.messages:
364 self.print_wrapped(msg, hosts)
365
366
367
368class host_create(host):
369 """atest host create [--lock|--unlock --platform <arch>
370 --labels <labels>|--blist <label_file>
371 --acls <acls>|--alist <acl_file>
mblighaed47e82009-03-17 19:06:18 +0000372 --protection <protection_type>
mblighbe630eb2008-08-01 16:41:48 +0000373 --mlist <mach_file>] <hosts>"""
374 usage_action = 'create'
375
376 def __init__(self):
377 self.messages = []
378 super(host_create, self).__init__()
379 self.parser.add_option('-l', '--lock',
380 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000381 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000382 self.parser.add_option('-u', '--unlock',
383 help='Create the hosts as '
384 'unlocked (default)',
385 action='store_true')
386 self.parser.add_option('-t', '--platform',
387 help='Sets the platform label')
388 self.parser.add_option('-b', '--labels',
389 help='Comma separated list of labels')
390 self.parser.add_option('-B', '--blist',
391 help='File listing the labels',
392 type='string',
393 metavar='LABEL_FLIST')
394 self.parser.add_option('-a', '--acls',
395 help='Comma separated list of ACLs')
396 self.parser.add_option('-A', '--alist',
397 help='File listing the acls',
398 type='string',
399 metavar='ACL_FLIST')
mblighaed47e82009-03-17 19:06:18 +0000400 self.parser.add_option('-p', '--protection', type='choice',
401 help=('Set the protection level on a host. '
402 'Must be one of: %s' %
403 ', '.join('"%s"' % p
404 for p in self.protections)),
405 choices=self.protections)
mblighbe630eb2008-08-01 16:41:48 +0000406
407
408 def parse(self):
mbligh9deeefa2009-05-01 23:11:08 +0000409 label_info = topic_common.item_parse_info(attribute_name='labels',
410 inline_option='labels',
411 filename_option='blist')
412 acl_info = topic_common.item_parse_info(attribute_name='acls',
413 inline_option='acls',
414 filename_option='alist')
415
416 (options, leftover) = super(host_create, self).parse([label_info,
417 acl_info],
418 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000419
420 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000421 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000422 self.platform = getattr(options, 'platform', None)
mblighaed47e82009-03-17 19:06:18 +0000423 if options.protection:
424 self.data['protection'] = options.protection
mblighbe630eb2008-08-01 16:41:48 +0000425 return (options, leftover)
426
427
428 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000429 # Always add the hosts as locked to avoid the host
mblighef87cad2009-03-11 18:18:10 +0000430 # being picked up by the scheduler before it's ACL'ed
mbligh719e14a2008-12-04 01:17:08 +0000431 self.data['locked'] = True
mblighbe630eb2008-08-01 16:41:48 +0000432 self.execute_rpc('add_host', hostname=host,
433 status="Ready", **self.data)
434
435 # Now add the platform label
436 labels = self.labels[:]
437 if self.platform:
438 labels.append(self.platform)
439 if len (labels):
440 self.execute_rpc('host_add_labels', id=host, labels=labels)
441
442
443 def execute(self):
444 # We need to check if these labels & ACLs exist,
445 # and create them if not.
446 if self.platform:
447 self.check_and_create_items('get_labels', 'add_label',
448 [self.platform],
449 platform=True)
450
451 if self.labels:
452 self.check_and_create_items('get_labels', 'add_label',
453 self.labels,
454 platform=False)
455
456 if self.acls:
457 self.check_and_create_items('get_acl_groups',
458 'add_acl_group',
459 self.acls)
460
461 success = self.site_create_hosts_hook()
462
463 if len(success):
464 for acl in self.acls:
465 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
mbligh719e14a2008-12-04 01:17:08 +0000466
467 if not self.locked:
468 for host in success:
469 self.execute_rpc('modify_host', id=host, locked=False)
mblighbe630eb2008-08-01 16:41:48 +0000470 return success
471
472
473 def site_create_hosts_hook(self):
474 success = []
475 for host in self.hosts:
476 try:
477 self._execute_add_one_host(host)
478 success.append(host)
479 except topic_common.CliError:
480 pass
481
482 return success
483
484
485 def output(self, hosts):
486 self.print_wrapped('Added host', hosts)
487
488
489class host_delete(action_common.atest_delete, host):
490 """atest host delete [--mlist <mach_file>] <hosts>"""
491 pass