blob: df82996b4feef636819039b9195a81846f498f30 [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"""
Simran Basi0739d682015-02-25 16:22:56 -080023import re
mblighbe630eb2008-08-01 16:41:48 +000024
Jiaxi Luoc342f9f2014-05-19 16:22:03 -070025from autotest_lib.cli import action_common, topic_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
Matthew Sartori68186332015-04-27 17:19:53 -070066 self.data['lock_reason'] = ''
mblighbe630eb2008-08-01 16:41:48 +000067 self.messages.append('Unlocked host')
68
Matthew Sartori68186332015-04-27 17:19:53 -070069 if options.lock and options.lock_reason:
70 self.data['lock_reason'] = options.lock_reason
71
mblighbe630eb2008-08-01 16:41:48 +000072
73 def _cleanup_labels(self, labels, platform=None):
74 """Removes the platform label from the overall labels"""
75 if platform:
76 return [label for label in labels
77 if label != platform]
78 else:
79 try:
80 return [label for label in labels
81 if not label['platform']]
82 except TypeError:
83 # This is a hack - the server will soon
84 # do this, so all this code should be removed.
85 return labels
86
87
88 def get_items(self):
89 return self.hosts
90
91
92class host_help(host):
93 """Just here to get the atest logic working.
94 Usage is set by its parent"""
95 pass
96
97
98class host_list(action_common.atest_list, host):
99 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +0000100 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +0000101
102 def __init__(self):
103 super(host_list, self).__init__()
104
105 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000106 default='',
107 help='Only list hosts with all these labels '
108 '(comma separated)')
mblighbe630eb2008-08-01 16:41:48 +0000109 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000110 default='',
111 help='Only list hosts with any of these '
112 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000113 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000114 default='',
mbligh536a5242008-10-18 14:35:54 +0000115 help='Only list hosts within this ACL')
116 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000117 default='',
mbligh536a5242008-10-18 14:35:54 +0000118 help='Only list hosts available to this user')
mbligh70b9bf42008-11-18 15:00:46 +0000119 self.parser.add_option('-N', '--hostnames-only', help='Only return '
120 'hostnames for the machines queried.',
121 action='store_true')
mbligh91e0efd2009-02-26 01:02:16 +0000122 self.parser.add_option('--locked',
123 default=False,
124 help='Only list locked hosts',
125 action='store_true')
126 self.parser.add_option('--unlocked',
127 default=False,
128 help='Only list unlocked hosts',
129 action='store_true')
130
mblighbe630eb2008-08-01 16:41:48 +0000131
132
133 def parse(self):
134 """Consume the specific options"""
jamesrenc2863162010-07-12 21:20:51 +0000135 label_info = topic_common.item_parse_info(attribute_name='labels',
136 inline_option='label')
137
138 (options, leftover) = super(host_list, self).parse([label_info])
139
mblighbe630eb2008-08-01 16:41:48 +0000140 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000141 self.acl = options.acl
142 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000143 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000144
145 if options.locked and options.unlocked:
146 self.invalid_syntax('--locked and --unlocked are '
147 'mutually exclusive')
148 self.locked = options.locked
149 self.unlocked = options.unlocked
mblighbe630eb2008-08-01 16:41:48 +0000150 return (options, leftover)
151
152
153 def execute(self):
154 filters = {}
155 check_results = {}
156 if self.hosts:
157 filters['hostname__in'] = self.hosts
158 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000159
160 if self.labels:
jamesrenc2863162010-07-12 21:20:51 +0000161 if len(self.labels) == 1:
mblighf703fb42009-01-30 00:35:05 +0000162 # This is needed for labels with wildcards (x86*)
jamesrenc2863162010-07-12 21:20:51 +0000163 filters['labels__name__in'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000164 check_results['labels__name__in'] = None
165 else:
jamesrenc2863162010-07-12 21:20:51 +0000166 filters['multiple_labels'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000167 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000168
mblighbe630eb2008-08-01 16:41:48 +0000169 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000170 statuses = self.status.split(',')
171 statuses = [status.strip() for status in statuses
172 if status.strip()]
173
174 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000175 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000176
mbligh536a5242008-10-18 14:35:54 +0000177 if self.acl:
showardd9ac4452009-02-07 02:04:37 +0000178 filters['aclgroup__name'] = self.acl
179 check_results['aclgroup__name'] = None
mbligh536a5242008-10-18 14:35:54 +0000180 if self.user:
showardd9ac4452009-02-07 02:04:37 +0000181 filters['aclgroup__users__login'] = self.user
182 check_results['aclgroup__users__login'] = None
mbligh91e0efd2009-02-26 01:02:16 +0000183
184 if self.locked or self.unlocked:
185 filters['locked'] = self.locked
186 check_results['locked'] = None
187
mblighbe630eb2008-08-01 16:41:48 +0000188 return super(host_list, self).execute(op='get_hosts',
189 filters=filters,
190 check_results=check_results)
191
192
193 def output(self, results):
194 if results:
195 # Remove the platform from the labels.
196 for result in results:
197 result['labels'] = self._cleanup_labels(result['labels'],
198 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000199 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000200 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000201 else:
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800202 keys = ['hostname', 'status',
Matthew Sartori68186332015-04-27 17:19:53 -0700203 'shard', 'locked', 'lock_reason', 'platform', 'labels']
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800204 super(host_list, self).output(results, keys=keys)
mblighbe630eb2008-08-01 16:41:48 +0000205
206
207class host_stat(host):
208 """atest host stat --mlist <file>|<hosts>"""
209 usage_action = 'stat'
210
211 def execute(self):
212 results = []
213 # Convert wildcards into real host stats.
214 existing_hosts = []
215 for host in self.hosts:
216 if host.endswith('*'):
217 stats = self.execute_rpc('get_hosts',
218 hostname__startswith=host.rstrip('*'))
219 if len(stats) == 0:
220 self.failure('No hosts matching %s' % host, item=host,
221 what_failed='Failed to stat')
222 continue
223 else:
224 stats = self.execute_rpc('get_hosts', hostname=host)
225 if len(stats) == 0:
226 self.failure('Unknown host %s' % host, item=host,
227 what_failed='Failed to stat')
228 continue
229 existing_hosts.extend(stats)
230
231 for stat in existing_hosts:
232 host = stat['hostname']
233 # The host exists, these should succeed
234 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
235
236 labels = self.execute_rpc('get_labels', host__hostname=host)
Simran Basi0739d682015-02-25 16:22:56 -0800237 results.append([[stat], acls, labels, stat['attributes']])
mblighbe630eb2008-08-01 16:41:48 +0000238 return results
239
240
241 def output(self, results):
Simran Basi0739d682015-02-25 16:22:56 -0800242 for stats, acls, labels, attributes in results:
mblighbe630eb2008-08-01 16:41:48 +0000243 print '-'*5
244 self.print_fields(stats,
245 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000246 'status', 'locked', 'locked_by',
Matthew Sartori68186332015-04-27 17:19:53 -0700247 'lock_time', 'lock_reason', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000248 self.print_by_ids(acls, 'ACLs', line_before=True)
249 labels = self._cleanup_labels(labels)
250 self.print_by_ids(labels, 'Labels', line_before=True)
Simran Basi0739d682015-02-25 16:22:56 -0800251 self.print_dict(attributes, 'Host Attributes', line_before=True)
mblighbe630eb2008-08-01 16:41:48 +0000252
253
254class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000255 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000256 usage_action = 'jobs'
257
mbligh6996fe82008-08-13 00:32:27 +0000258 def __init__(self):
259 super(host_jobs, self).__init__()
260 self.parser.add_option('-q', '--max-query',
261 help='Limits the number of results '
262 '(20 by default)',
263 type='int', default=20)
264
265
266 def parse(self):
267 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000268 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000269 self.max_queries = options.max_query
270 return (options, leftover)
271
272
mblighbe630eb2008-08-01 16:41:48 +0000273 def execute(self):
274 results = []
275 real_hosts = []
276 for host in self.hosts:
277 if host.endswith('*'):
278 stats = self.execute_rpc('get_hosts',
279 hostname__startswith=host.rstrip('*'))
280 if len(stats) == 0:
281 self.failure('No host matching %s' % host, item=host,
282 what_failed='Failed to stat')
283 [real_hosts.append(stat['hostname']) for stat in stats]
284 else:
285 real_hosts.append(host)
286
287 for host in real_hosts:
288 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000289 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000290 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000291 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000292 jobs = []
293 for entry in queue_entries:
294 job = {'job_id': entry['job']['id'],
295 'job_owner': entry['job']['owner'],
296 'job_name': entry['job']['name'],
297 'status': entry['status']}
298 jobs.append(job)
299 results.append((host, jobs))
300 return results
301
302
303 def output(self, results):
304 for host, jobs in results:
305 print '-'*5
306 print 'Hostname: %s' % host
307 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000308 'job_owner',
309 'job_name',
310 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000311
312
313class host_mod(host):
showardbe0d8692009-08-20 23:42:44 +0000314 """atest host mod --lock|--unlock|--protection
mblighbe630eb2008-08-01 16:41:48 +0000315 --mlist <file>|<hosts>"""
316 usage_action = 'mod'
Simran Basi0739d682015-02-25 16:22:56 -0800317 attribute_regex = r'^(?P<attribute>\w+)=(?P<value>.+)?'
mblighbe630eb2008-08-01 16:41:48 +0000318
319 def __init__(self):
320 """Add the options specific to the mod action"""
321 self.data = {}
322 self.messages = []
Simran Basi0739d682015-02-25 16:22:56 -0800323 self.attribute = None
324 self.value = None
mblighbe630eb2008-08-01 16:41:48 +0000325 super(host_mod, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000326 self.parser.add_option('-l', '--lock',
327 help='Lock hosts',
328 action='store_true')
329 self.parser.add_option('-u', '--unlock',
330 help='Unlock hosts',
331 action='store_true')
Matthew Sartori68186332015-04-27 17:19:53 -0700332 self.parser.add_option('-r', '--lock_reason',
333 help='Reason for locking hosts',
334 default='')
mblighe163b032008-10-18 14:30:27 +0000335 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000336 help=('Set the protection level on a host. '
337 'Must be one of: %s' %
338 ', '.join('"%s"' % p
339 for p in self.protections)),
340 choices=self.protections)
Simran Basi0739d682015-02-25 16:22:56 -0800341 self.parser.add_option('--attribute', '-a', default='',
342 help=('Host attribute to add or change. Format '
343 'is <attribute>=<value>. Value can be '
344 'blank to delete attribute.'))
mblighbe630eb2008-08-01 16:41:48 +0000345
346
347 def parse(self):
348 """Consume the specific options"""
349 (options, leftover) = super(host_mod, self).parse()
350
351 self._parse_lock_options(options)
352
mblighe163b032008-10-18 14:30:27 +0000353 if options.protection:
354 self.data['protection'] = options.protection
showardbe0d8692009-08-20 23:42:44 +0000355 self.messages.append('Protection set to "%s"' % options.protection)
mblighe163b032008-10-18 14:30:27 +0000356
Simran Basi0739d682015-02-25 16:22:56 -0800357 if len(self.data) == 0 and not options.attribute:
mblighbe630eb2008-08-01 16:41:48 +0000358 self.invalid_syntax('No modification requested')
Simran Basi0739d682015-02-25 16:22:56 -0800359
360 if options.attribute:
361 match = re.match(self.attribute_regex, options.attribute)
362 if not match:
363 self.invalid_syntax('Attributes must be in <attribute>=<value>'
364 ' syntax!')
365
366 self.attribute = match.group('attribute')
367 self.value = match.group('value')
368
mblighbe630eb2008-08-01 16:41:48 +0000369 return (options, leftover)
370
371
372 def execute(self):
373 successes = []
374 for host in self.hosts:
mbligh6eca4602009-01-07 16:48:50 +0000375 try:
376 res = self.execute_rpc('modify_host', item=host,
377 id=host, **self.data)
Simran Basi0739d682015-02-25 16:22:56 -0800378 if self.attribute:
379 self.execute_rpc('set_host_attribute',
380 attribute=self.attribute,
381 value=self.value, hostname=host)
mbligh6eca4602009-01-07 16:48:50 +0000382 # TODO: Make the AFE return True or False,
383 # especially for lock
mblighbe630eb2008-08-01 16:41:48 +0000384 successes.append(host)
mbligh6eca4602009-01-07 16:48:50 +0000385 except topic_common.CliError, full_error:
386 # Already logged by execute_rpc()
387 pass
388
mblighbe630eb2008-08-01 16:41:48 +0000389 return successes
390
391
392 def output(self, hosts):
393 for msg in self.messages:
394 self.print_wrapped(msg, hosts)
395
396
mblighbe630eb2008-08-01 16:41:48 +0000397class host_create(host):
398 """atest host create [--lock|--unlock --platform <arch>
399 --labels <labels>|--blist <label_file>
400 --acls <acls>|--alist <acl_file>
mblighaed47e82009-03-17 19:06:18 +0000401 --protection <protection_type>
mblighbe630eb2008-08-01 16:41:48 +0000402 --mlist <mach_file>] <hosts>"""
403 usage_action = 'create'
404
405 def __init__(self):
406 self.messages = []
407 super(host_create, self).__init__()
408 self.parser.add_option('-l', '--lock',
409 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000410 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000411 self.parser.add_option('-u', '--unlock',
412 help='Create the hosts as '
413 'unlocked (default)',
414 action='store_true')
Matthew Sartori68186332015-04-27 17:19:53 -0700415 self.parser.add_option('-r', '--lock_reason',
416 help='Reason for locking hosts',
417 default='')
mblighbe630eb2008-08-01 16:41:48 +0000418 self.parser.add_option('-t', '--platform',
419 help='Sets the platform label')
420 self.parser.add_option('-b', '--labels',
421 help='Comma separated list of labels')
422 self.parser.add_option('-B', '--blist',
423 help='File listing the labels',
424 type='string',
425 metavar='LABEL_FLIST')
426 self.parser.add_option('-a', '--acls',
427 help='Comma separated list of ACLs')
428 self.parser.add_option('-A', '--alist',
429 help='File listing the acls',
430 type='string',
431 metavar='ACL_FLIST')
mblighaed47e82009-03-17 19:06:18 +0000432 self.parser.add_option('-p', '--protection', type='choice',
433 help=('Set the protection level on a host. '
434 'Must be one of: %s' %
435 ', '.join('"%s"' % p
436 for p in self.protections)),
437 choices=self.protections)
mblighbe630eb2008-08-01 16:41:48 +0000438
439
440 def parse(self):
mbligh9deeefa2009-05-01 23:11:08 +0000441 label_info = topic_common.item_parse_info(attribute_name='labels',
442 inline_option='labels',
443 filename_option='blist')
444 acl_info = topic_common.item_parse_info(attribute_name='acls',
445 inline_option='acls',
446 filename_option='alist')
447
448 (options, leftover) = super(host_create, self).parse([label_info,
449 acl_info],
450 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000451
452 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000453 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000454 self.platform = getattr(options, 'platform', None)
mblighaed47e82009-03-17 19:06:18 +0000455 if options.protection:
456 self.data['protection'] = options.protection
mblighbe630eb2008-08-01 16:41:48 +0000457 return (options, leftover)
458
459
460 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000461 # Always add the hosts as locked to avoid the host
Matthew Sartori68186332015-04-27 17:19:53 -0700462 # being picked up by the scheduler before it's ACL'ed.
463 # We enforce lock reasons for each lock, so we
464 # provide a 'dummy' if we are intending to unlock after.
mbligh719e14a2008-12-04 01:17:08 +0000465 self.data['locked'] = True
Matthew Sartori68186332015-04-27 17:19:53 -0700466 if not self.locked:
467 self.data['lock_reason'] = 'Forced lock on device creation'
mblighbe630eb2008-08-01 16:41:48 +0000468 self.execute_rpc('add_host', hostname=host,
469 status="Ready", **self.data)
470
471 # Now add the platform label
472 labels = self.labels[:]
473 if self.platform:
474 labels.append(self.platform)
475 if len (labels):
476 self.execute_rpc('host_add_labels', id=host, labels=labels)
477
478
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700479 def _execute_add_hosts(self):
480 successful_hosts = self.site_create_hosts_hook()
481
482 if successful_hosts:
483 for acl in self.acls:
484 self.execute_rpc('acl_group_add_hosts',
485 id=acl,
486 hosts=successful_hosts)
487
488 if not self.locked:
489 for host in successful_hosts:
Matthew Sartori68186332015-04-27 17:19:53 -0700490 self.execute_rpc('modify_host', id=host, locked=False,
491 lock_reason='')
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700492 return successful_hosts
493
494
mblighbe630eb2008-08-01 16:41:48 +0000495 def execute(self):
496 # We need to check if these labels & ACLs exist,
497 # and create them if not.
498 if self.platform:
499 self.check_and_create_items('get_labels', 'add_label',
500 [self.platform],
501 platform=True)
502
503 if self.labels:
504 self.check_and_create_items('get_labels', 'add_label',
505 self.labels,
506 platform=False)
507
508 if self.acls:
509 self.check_and_create_items('get_acl_groups',
510 'add_acl_group',
511 self.acls)
512
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700513 return self._execute_add_hosts()
mblighbe630eb2008-08-01 16:41:48 +0000514
515
516 def site_create_hosts_hook(self):
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700517 successful_hosts = []
mblighbe630eb2008-08-01 16:41:48 +0000518 for host in self.hosts:
519 try:
520 self._execute_add_one_host(host)
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700521 successful_hosts.append(host)
mblighbe630eb2008-08-01 16:41:48 +0000522 except topic_common.CliError:
523 pass
524
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700525 return successful_hosts
mblighbe630eb2008-08-01 16:41:48 +0000526
527
528 def output(self, hosts):
529 self.print_wrapped('Added host', hosts)
530
531
532class host_delete(action_common.atest_delete, host):
533 """atest host delete [--mlist <mach_file>] <hosts>"""
534 pass