blob: 53d572d95d8869e7fae1c099607c9bce7133d972 [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
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"""
jamesrenc2863162010-07-12 21:20:51 +0000131 label_info = topic_common.item_parse_info(attribute_name='labels',
132 inline_option='label')
133
134 (options, leftover) = super(host_list, self).parse([label_info])
135
mblighbe630eb2008-08-01 16:41:48 +0000136 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000137 self.acl = options.acl
138 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000139 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000140
141 if options.locked and options.unlocked:
142 self.invalid_syntax('--locked and --unlocked are '
143 'mutually exclusive')
144 self.locked = options.locked
145 self.unlocked = options.unlocked
mblighbe630eb2008-08-01 16:41:48 +0000146 return (options, leftover)
147
148
149 def execute(self):
150 filters = {}
151 check_results = {}
152 if self.hosts:
153 filters['hostname__in'] = self.hosts
154 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000155
156 if self.labels:
jamesrenc2863162010-07-12 21:20:51 +0000157 if len(self.labels) == 1:
mblighf703fb42009-01-30 00:35:05 +0000158 # This is needed for labels with wildcards (x86*)
jamesrenc2863162010-07-12 21:20:51 +0000159 filters['labels__name__in'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000160 check_results['labels__name__in'] = None
161 else:
jamesrenc2863162010-07-12 21:20:51 +0000162 filters['multiple_labels'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000163 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:
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800198 keys = ['hostname', 'status',
199 'shard', 'locked', 'platform', 'labels']
200 super(host_list, self).output(results, keys=keys)
mblighbe630eb2008-08-01 16:41:48 +0000201
202
203class host_stat(host):
204 """atest host stat --mlist <file>|<hosts>"""
205 usage_action = 'stat'
206
207 def execute(self):
208 results = []
209 # Convert wildcards into real host stats.
210 existing_hosts = []
211 for host in self.hosts:
212 if host.endswith('*'):
213 stats = self.execute_rpc('get_hosts',
214 hostname__startswith=host.rstrip('*'))
215 if len(stats) == 0:
216 self.failure('No hosts matching %s' % host, item=host,
217 what_failed='Failed to stat')
218 continue
219 else:
220 stats = self.execute_rpc('get_hosts', hostname=host)
221 if len(stats) == 0:
222 self.failure('Unknown host %s' % host, item=host,
223 what_failed='Failed to stat')
224 continue
225 existing_hosts.extend(stats)
226
227 for stat in existing_hosts:
228 host = stat['hostname']
229 # The host exists, these should succeed
230 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
231
232 labels = self.execute_rpc('get_labels', host__hostname=host)
Simran Basi0739d682015-02-25 16:22:56 -0800233 results.append([[stat], acls, labels, stat['attributes']])
mblighbe630eb2008-08-01 16:41:48 +0000234 return results
235
236
237 def output(self, results):
Simran Basi0739d682015-02-25 16:22:56 -0800238 for stats, acls, labels, attributes in results:
mblighbe630eb2008-08-01 16:41:48 +0000239 print '-'*5
240 self.print_fields(stats,
241 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000242 'status', 'locked', 'locked_by',
243 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000244 self.print_by_ids(acls, 'ACLs', line_before=True)
245 labels = self._cleanup_labels(labels)
246 self.print_by_ids(labels, 'Labels', line_before=True)
Simran Basi0739d682015-02-25 16:22:56 -0800247 self.print_dict(attributes, 'Host Attributes', line_before=True)
mblighbe630eb2008-08-01 16:41:48 +0000248
249
250class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000251 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000252 usage_action = 'jobs'
253
mbligh6996fe82008-08-13 00:32:27 +0000254 def __init__(self):
255 super(host_jobs, self).__init__()
256 self.parser.add_option('-q', '--max-query',
257 help='Limits the number of results '
258 '(20 by default)',
259 type='int', default=20)
260
261
262 def parse(self):
263 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000264 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000265 self.max_queries = options.max_query
266 return (options, leftover)
267
268
mblighbe630eb2008-08-01 16:41:48 +0000269 def execute(self):
270 results = []
271 real_hosts = []
272 for host in self.hosts:
273 if host.endswith('*'):
274 stats = self.execute_rpc('get_hosts',
275 hostname__startswith=host.rstrip('*'))
276 if len(stats) == 0:
277 self.failure('No host matching %s' % host, item=host,
278 what_failed='Failed to stat')
279 [real_hosts.append(stat['hostname']) for stat in stats]
280 else:
281 real_hosts.append(host)
282
283 for host in real_hosts:
284 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000285 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000286 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000287 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000288 jobs = []
289 for entry in queue_entries:
290 job = {'job_id': entry['job']['id'],
291 'job_owner': entry['job']['owner'],
292 'job_name': entry['job']['name'],
293 'status': entry['status']}
294 jobs.append(job)
295 results.append((host, jobs))
296 return results
297
298
299 def output(self, results):
300 for host, jobs in results:
301 print '-'*5
302 print 'Hostname: %s' % host
303 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000304 'job_owner',
305 'job_name',
306 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000307
308
309class host_mod(host):
showardbe0d8692009-08-20 23:42:44 +0000310 """atest host mod --lock|--unlock|--protection
mblighbe630eb2008-08-01 16:41:48 +0000311 --mlist <file>|<hosts>"""
312 usage_action = 'mod'
Simran Basi0739d682015-02-25 16:22:56 -0800313 attribute_regex = r'^(?P<attribute>\w+)=(?P<value>.+)?'
mblighbe630eb2008-08-01 16:41:48 +0000314
315 def __init__(self):
316 """Add the options specific to the mod action"""
317 self.data = {}
318 self.messages = []
Simran Basi0739d682015-02-25 16:22:56 -0800319 self.attribute = None
320 self.value = None
mblighbe630eb2008-08-01 16:41:48 +0000321 super(host_mod, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000322 self.parser.add_option('-l', '--lock',
323 help='Lock hosts',
324 action='store_true')
325 self.parser.add_option('-u', '--unlock',
326 help='Unlock hosts',
327 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000328 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000329 help=('Set the protection level on a host. '
330 'Must be one of: %s' %
331 ', '.join('"%s"' % p
332 for p in self.protections)),
333 choices=self.protections)
Simran Basi0739d682015-02-25 16:22:56 -0800334 self.parser.add_option('--attribute', '-a', default='',
335 help=('Host attribute to add or change. Format '
336 'is <attribute>=<value>. Value can be '
337 'blank to delete attribute.'))
mblighbe630eb2008-08-01 16:41:48 +0000338
339
340 def parse(self):
341 """Consume the specific options"""
342 (options, leftover) = super(host_mod, self).parse()
343
344 self._parse_lock_options(options)
345
mblighe163b032008-10-18 14:30:27 +0000346 if options.protection:
347 self.data['protection'] = options.protection
showardbe0d8692009-08-20 23:42:44 +0000348 self.messages.append('Protection set to "%s"' % options.protection)
mblighe163b032008-10-18 14:30:27 +0000349
Simran Basi0739d682015-02-25 16:22:56 -0800350 if len(self.data) == 0 and not options.attribute:
mblighbe630eb2008-08-01 16:41:48 +0000351 self.invalid_syntax('No modification requested')
Simran Basi0739d682015-02-25 16:22:56 -0800352
353 if options.attribute:
354 match = re.match(self.attribute_regex, options.attribute)
355 if not match:
356 self.invalid_syntax('Attributes must be in <attribute>=<value>'
357 ' syntax!')
358
359 self.attribute = match.group('attribute')
360 self.value = match.group('value')
361
mblighbe630eb2008-08-01 16:41:48 +0000362 return (options, leftover)
363
364
365 def execute(self):
366 successes = []
367 for host in self.hosts:
mbligh6eca4602009-01-07 16:48:50 +0000368 try:
369 res = self.execute_rpc('modify_host', item=host,
370 id=host, **self.data)
Simran Basi0739d682015-02-25 16:22:56 -0800371 if self.attribute:
372 self.execute_rpc('set_host_attribute',
373 attribute=self.attribute,
374 value=self.value, hostname=host)
mbligh6eca4602009-01-07 16:48:50 +0000375 # TODO: Make the AFE return True or False,
376 # especially for lock
mblighbe630eb2008-08-01 16:41:48 +0000377 successes.append(host)
mbligh6eca4602009-01-07 16:48:50 +0000378 except topic_common.CliError, full_error:
379 # Already logged by execute_rpc()
380 pass
381
mblighbe630eb2008-08-01 16:41:48 +0000382 return successes
383
384
385 def output(self, hosts):
386 for msg in self.messages:
387 self.print_wrapped(msg, hosts)
388
389
mblighbe630eb2008-08-01 16:41:48 +0000390class host_create(host):
391 """atest host create [--lock|--unlock --platform <arch>
392 --labels <labels>|--blist <label_file>
393 --acls <acls>|--alist <acl_file>
mblighaed47e82009-03-17 19:06:18 +0000394 --protection <protection_type>
mblighbe630eb2008-08-01 16:41:48 +0000395 --mlist <mach_file>] <hosts>"""
396 usage_action = 'create'
397
398 def __init__(self):
399 self.messages = []
400 super(host_create, self).__init__()
401 self.parser.add_option('-l', '--lock',
402 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000403 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000404 self.parser.add_option('-u', '--unlock',
405 help='Create the hosts as '
406 'unlocked (default)',
407 action='store_true')
408 self.parser.add_option('-t', '--platform',
409 help='Sets the platform label')
410 self.parser.add_option('-b', '--labels',
411 help='Comma separated list of labels')
412 self.parser.add_option('-B', '--blist',
413 help='File listing the labels',
414 type='string',
415 metavar='LABEL_FLIST')
416 self.parser.add_option('-a', '--acls',
417 help='Comma separated list of ACLs')
418 self.parser.add_option('-A', '--alist',
419 help='File listing the acls',
420 type='string',
421 metavar='ACL_FLIST')
mblighaed47e82009-03-17 19:06:18 +0000422 self.parser.add_option('-p', '--protection', type='choice',
423 help=('Set the protection level on a host. '
424 'Must be one of: %s' %
425 ', '.join('"%s"' % p
426 for p in self.protections)),
427 choices=self.protections)
mblighbe630eb2008-08-01 16:41:48 +0000428
429
430 def parse(self):
mbligh9deeefa2009-05-01 23:11:08 +0000431 label_info = topic_common.item_parse_info(attribute_name='labels',
432 inline_option='labels',
433 filename_option='blist')
434 acl_info = topic_common.item_parse_info(attribute_name='acls',
435 inline_option='acls',
436 filename_option='alist')
437
438 (options, leftover) = super(host_create, self).parse([label_info,
439 acl_info],
440 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000441
442 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000443 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000444 self.platform = getattr(options, 'platform', None)
mblighaed47e82009-03-17 19:06:18 +0000445 if options.protection:
446 self.data['protection'] = options.protection
mblighbe630eb2008-08-01 16:41:48 +0000447 return (options, leftover)
448
449
450 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000451 # Always add the hosts as locked to avoid the host
mblighef87cad2009-03-11 18:18:10 +0000452 # being picked up by the scheduler before it's ACL'ed
mbligh719e14a2008-12-04 01:17:08 +0000453 self.data['locked'] = True
mblighbe630eb2008-08-01 16:41:48 +0000454 self.execute_rpc('add_host', hostname=host,
455 status="Ready", **self.data)
456
457 # Now add the platform label
458 labels = self.labels[:]
459 if self.platform:
460 labels.append(self.platform)
461 if len (labels):
462 self.execute_rpc('host_add_labels', id=host, labels=labels)
463
464
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700465 def _execute_add_hosts(self):
466 successful_hosts = self.site_create_hosts_hook()
467
468 if successful_hosts:
469 for acl in self.acls:
470 self.execute_rpc('acl_group_add_hosts',
471 id=acl,
472 hosts=successful_hosts)
473
474 if not self.locked:
475 for host in successful_hosts:
476 self.execute_rpc('modify_host', id=host, locked=False)
477 return successful_hosts
478
479
mblighbe630eb2008-08-01 16:41:48 +0000480 def execute(self):
481 # We need to check if these labels & ACLs exist,
482 # and create them if not.
483 if self.platform:
484 self.check_and_create_items('get_labels', 'add_label',
485 [self.platform],
486 platform=True)
487
488 if self.labels:
489 self.check_and_create_items('get_labels', 'add_label',
490 self.labels,
491 platform=False)
492
493 if self.acls:
494 self.check_and_create_items('get_acl_groups',
495 'add_acl_group',
496 self.acls)
497
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700498 return self._execute_add_hosts()
mblighbe630eb2008-08-01 16:41:48 +0000499
500
501 def site_create_hosts_hook(self):
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700502 successful_hosts = []
mblighbe630eb2008-08-01 16:41:48 +0000503 for host in self.hosts:
504 try:
505 self._execute_add_one_host(host)
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700506 successful_hosts.append(host)
mblighbe630eb2008-08-01 16:41:48 +0000507 except topic_common.CliError:
508 pass
509
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700510 return successful_hosts
mblighbe630eb2008-08-01 16:41:48 +0000511
512
513 def output(self, hosts):
514 self.print_wrapped('Added host', hosts)
515
516
517class host_delete(action_common.atest_delete, host):
518 """atest host delete [--mlist <mach_file>] <hosts>"""
519 pass