blob: 93747050748de3e1aca5f950c779ea2953be8e4a [file] [log] [blame]
mblighbe630eb2008-08-01 16:41:48 +00001# Copyright 2008 Google Inc. All Rights Reserved.
2
3"""
4The host module contains the objects and method used to
5manage a host in Autotest.
6
7The valid actions are:
8create: adds host(s)
9delete: deletes host(s)
10list: lists host(s)
11stat: displays host(s) information
12mod: modifies host(s)
13jobs: lists all jobs that ran on host(s)
14
15The common options are:
16-M|--mlist: file containing a list of machines
17
18
mblighbe630eb2008-08-01 16:41:48 +000019See topic_common.py for a High Level Design and Algorithm.
20
21"""
Justin Giorgi16bba562016-06-22 10:42:13 -070022import common
Simran Basi0739d682015-02-25 16:22:56 -080023import re
Justin Giorgi16bba562016-06-22 10:42:13 -070024import socket
mblighbe630eb2008-08-01 16:41:48 +000025
Justin Giorgi16bba562016-06-22 10:42:13 -070026from autotest_lib.cli import action_common, rpc, topic_common
27from autotest_lib.client.bin import utils
28from autotest_lib.client.common_lib import error, host_protections
Justin Giorgi5208eaa2016-07-02 20:12:12 -070029from autotest_lib.server import frontend, hosts
mblighbe630eb2008-08-01 16:41:48 +000030
31
32class host(topic_common.atest):
33 """Host class
34 atest host [create|delete|list|stat|mod|jobs] <options>"""
35 usage_action = '[create|delete|list|stat|mod|jobs]'
36 topic = msg_topic = 'host'
37 msg_items = '<hosts>'
38
mblighaed47e82009-03-17 19:06:18 +000039 protections = host_protections.Protection.names
40
mblighbe630eb2008-08-01 16:41:48 +000041
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
mbligh9deeefa2009-05-01 23:11:08 +000053 self.topic_parse_info = topic_common.item_parse_info(
54 attribute_name='hosts',
55 filename_option='mlist',
56 use_leftover=True)
mblighbe630eb2008-08-01 16:41:48 +000057
58
59 def _parse_lock_options(self, options):
60 if options.lock and options.unlock:
61 self.invalid_syntax('Only specify one of '
62 '--lock and --unlock.')
63
64 if options.lock:
65 self.data['locked'] = True
66 self.messages.append('Locked host')
67 elif options.unlock:
68 self.data['locked'] = False
Matthew Sartori68186332015-04-27 17:19:53 -070069 self.data['lock_reason'] = ''
mblighbe630eb2008-08-01 16:41:48 +000070 self.messages.append('Unlocked host')
71
Matthew Sartori68186332015-04-27 17:19:53 -070072 if options.lock and options.lock_reason:
73 self.data['lock_reason'] = options.lock_reason
74
mblighbe630eb2008-08-01 16:41:48 +000075
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',
mbligh6444c6b2008-10-27 20:55:13 +0000109 default='',
110 help='Only list hosts with all these labels '
111 '(comma separated)')
mblighbe630eb2008-08-01 16:41:48 +0000112 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000113 default='',
114 help='Only list hosts with any of these '
115 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000116 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000117 default='',
mbligh536a5242008-10-18 14:35:54 +0000118 help='Only list hosts within this ACL')
119 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000120 default='',
mbligh536a5242008-10-18 14:35:54 +0000121 help='Only list hosts available to this user')
mbligh70b9bf42008-11-18 15:00:46 +0000122 self.parser.add_option('-N', '--hostnames-only', help='Only return '
123 'hostnames for the machines queried.',
124 action='store_true')
mbligh91e0efd2009-02-26 01:02:16 +0000125 self.parser.add_option('--locked',
126 default=False,
127 help='Only list locked hosts',
128 action='store_true')
129 self.parser.add_option('--unlocked',
130 default=False,
131 help='Only list unlocked hosts',
132 action='store_true')
133
mblighbe630eb2008-08-01 16:41:48 +0000134
135
136 def parse(self):
137 """Consume the specific options"""
jamesrenc2863162010-07-12 21:20:51 +0000138 label_info = topic_common.item_parse_info(attribute_name='labels',
139 inline_option='label')
140
141 (options, leftover) = super(host_list, self).parse([label_info])
142
mblighbe630eb2008-08-01 16:41:48 +0000143 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000144 self.acl = options.acl
145 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000146 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000147
148 if options.locked and options.unlocked:
149 self.invalid_syntax('--locked and --unlocked are '
150 'mutually exclusive')
151 self.locked = options.locked
152 self.unlocked = options.unlocked
mblighbe630eb2008-08-01 16:41:48 +0000153 return (options, leftover)
154
155
156 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800157 """Execute 'atest host list'."""
mblighbe630eb2008-08-01 16:41:48 +0000158 filters = {}
159 check_results = {}
160 if self.hosts:
161 filters['hostname__in'] = self.hosts
162 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000163
164 if self.labels:
jamesrenc2863162010-07-12 21:20:51 +0000165 if len(self.labels) == 1:
mblighf703fb42009-01-30 00:35:05 +0000166 # This is needed for labels with wildcards (x86*)
jamesrenc2863162010-07-12 21:20:51 +0000167 filters['labels__name__in'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000168 check_results['labels__name__in'] = None
169 else:
jamesrenc2863162010-07-12 21:20:51 +0000170 filters['multiple_labels'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000171 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000172
mblighbe630eb2008-08-01 16:41:48 +0000173 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000174 statuses = self.status.split(',')
175 statuses = [status.strip() for status in statuses
176 if status.strip()]
177
178 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000179 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000180
mbligh536a5242008-10-18 14:35:54 +0000181 if self.acl:
showardd9ac4452009-02-07 02:04:37 +0000182 filters['aclgroup__name'] = self.acl
183 check_results['aclgroup__name'] = None
mbligh536a5242008-10-18 14:35:54 +0000184 if self.user:
showardd9ac4452009-02-07 02:04:37 +0000185 filters['aclgroup__users__login'] = self.user
186 check_results['aclgroup__users__login'] = None
mbligh91e0efd2009-02-26 01:02:16 +0000187
188 if self.locked or self.unlocked:
189 filters['locked'] = self.locked
190 check_results['locked'] = None
191
mblighbe630eb2008-08-01 16:41:48 +0000192 return super(host_list, self).execute(op='get_hosts',
193 filters=filters,
194 check_results=check_results)
195
196
197 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800198 """Print output of 'atest host list'.
199
200 @param results: the results to be printed.
201 """
mblighbe630eb2008-08-01 16:41:48 +0000202 if results:
203 # Remove the platform from the labels.
204 for result in results:
205 result['labels'] = self._cleanup_labels(result['labels'],
206 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000207 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000208 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000209 else:
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800210 keys = ['hostname', 'status',
Aviv Keshet619901b2016-03-28 12:09:06 -0700211 'shard', 'locked', 'lock_reason', 'locked_by', 'platform',
212 'labels']
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800213 super(host_list, self).output(results, keys=keys)
mblighbe630eb2008-08-01 16:41:48 +0000214
215
216class host_stat(host):
217 """atest host stat --mlist <file>|<hosts>"""
218 usage_action = 'stat'
219
220 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800221 """Execute 'atest host stat'."""
mblighbe630eb2008-08-01 16:41:48 +0000222 results = []
223 # Convert wildcards into real host stats.
224 existing_hosts = []
225 for host in self.hosts:
226 if host.endswith('*'):
227 stats = self.execute_rpc('get_hosts',
228 hostname__startswith=host.rstrip('*'))
229 if len(stats) == 0:
230 self.failure('No hosts matching %s' % host, item=host,
231 what_failed='Failed to stat')
232 continue
233 else:
234 stats = self.execute_rpc('get_hosts', hostname=host)
235 if len(stats) == 0:
236 self.failure('Unknown host %s' % host, item=host,
237 what_failed='Failed to stat')
238 continue
239 existing_hosts.extend(stats)
240
241 for stat in existing_hosts:
242 host = stat['hostname']
243 # The host exists, these should succeed
244 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
245
246 labels = self.execute_rpc('get_labels', host__hostname=host)
Simran Basi0739d682015-02-25 16:22:56 -0800247 results.append([[stat], acls, labels, stat['attributes']])
mblighbe630eb2008-08-01 16:41:48 +0000248 return results
249
250
251 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800252 """Print output of 'atest host stat'.
253
254 @param results: the results to be printed.
255 """
Simran Basi0739d682015-02-25 16:22:56 -0800256 for stats, acls, labels, attributes in results:
mblighbe630eb2008-08-01 16:41:48 +0000257 print '-'*5
258 self.print_fields(stats,
259 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000260 'status', 'locked', 'locked_by',
Matthew Sartori68186332015-04-27 17:19:53 -0700261 'lock_time', 'lock_reason', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000262 self.print_by_ids(acls, 'ACLs', line_before=True)
263 labels = self._cleanup_labels(labels)
264 self.print_by_ids(labels, 'Labels', line_before=True)
Simran Basi0739d682015-02-25 16:22:56 -0800265 self.print_dict(attributes, 'Host Attributes', line_before=True)
mblighbe630eb2008-08-01 16:41:48 +0000266
267
268class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000269 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000270 usage_action = 'jobs'
271
mbligh6996fe82008-08-13 00:32:27 +0000272 def __init__(self):
273 super(host_jobs, self).__init__()
274 self.parser.add_option('-q', '--max-query',
275 help='Limits the number of results '
276 '(20 by default)',
277 type='int', default=20)
278
279
280 def parse(self):
281 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000282 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000283 self.max_queries = options.max_query
284 return (options, leftover)
285
286
mblighbe630eb2008-08-01 16:41:48 +0000287 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800288 """Execute 'atest host jobs'."""
mblighbe630eb2008-08-01 16:41:48 +0000289 results = []
290 real_hosts = []
291 for host in self.hosts:
292 if host.endswith('*'):
293 stats = self.execute_rpc('get_hosts',
294 hostname__startswith=host.rstrip('*'))
295 if len(stats) == 0:
296 self.failure('No host matching %s' % host, item=host,
297 what_failed='Failed to stat')
298 [real_hosts.append(stat['hostname']) for stat in stats]
299 else:
300 real_hosts.append(host)
301
302 for host in real_hosts:
303 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000304 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000305 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000306 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000307 jobs = []
308 for entry in queue_entries:
309 job = {'job_id': entry['job']['id'],
310 'job_owner': entry['job']['owner'],
311 'job_name': entry['job']['name'],
312 'status': entry['status']}
313 jobs.append(job)
314 results.append((host, jobs))
315 return results
316
317
318 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800319 """Print output of 'atest host jobs'.
320
321 @param results: the results to be printed.
322 """
mblighbe630eb2008-08-01 16:41:48 +0000323 for host, jobs in results:
324 print '-'*5
325 print 'Hostname: %s' % host
326 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000327 'job_owner',
328 'job_name',
329 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000330
Justin Giorgi16bba562016-06-22 10:42:13 -0700331class BaseHostModCreate(host):
xixuand6011f12016-12-08 15:01:58 -0800332 """The base class for host_mod and host_create"""
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700333 # Matches one attribute=value pair
334 attribute_regex = r'(?P<attribute>\w+)=(?P<value>.+)?'
mblighbe630eb2008-08-01 16:41:48 +0000335
336 def __init__(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700337 """Add the options shared between host mod and host create actions."""
mblighbe630eb2008-08-01 16:41:48 +0000338 self.messages = []
Justin Giorgi16bba562016-06-22 10:42:13 -0700339 self.host_ids = {}
340 super(BaseHostModCreate, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000341 self.parser.add_option('-l', '--lock',
342 help='Lock hosts',
343 action='store_true')
344 self.parser.add_option('-u', '--unlock',
345 help='Unlock hosts',
346 action='store_true')
Matthew Sartori68186332015-04-27 17:19:53 -0700347 self.parser.add_option('-r', '--lock_reason',
348 help='Reason for locking hosts',
349 default='')
mblighe163b032008-10-18 14:30:27 +0000350 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000351 help=('Set the protection level on a host. '
352 'Must be one of: %s' %
353 ', '.join('"%s"' % p
354 for p in self.protections)),
355 choices=self.protections)
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700356 self._attributes = []
357 self.parser.add_option('--attribute', '-i',
358 help=('Host attribute to add or change. Format '
359 'is <attribute>=<value>. Multiple '
360 'attributes can be set by passing the '
361 'argument multiple times. Attributes can '
362 'be unset by providing an empty value.'),
363 action='append')
mblighbe630eb2008-08-01 16:41:48 +0000364 self.parser.add_option('-b', '--labels',
365 help='Comma separated list of labels')
366 self.parser.add_option('-B', '--blist',
367 help='File listing the labels',
368 type='string',
369 metavar='LABEL_FLIST')
370 self.parser.add_option('-a', '--acls',
371 help='Comma separated list of ACLs')
372 self.parser.add_option('-A', '--alist',
373 help='File listing the acls',
374 type='string',
375 metavar='ACL_FLIST')
Justin Giorgi16bba562016-06-22 10:42:13 -0700376 self.parser.add_option('-t', '--platform',
377 help='Sets the platform label')
mblighbe630eb2008-08-01 16:41:48 +0000378
379
380 def parse(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700381 """Consume the options common to host create and host mod.
382 """
mbligh9deeefa2009-05-01 23:11:08 +0000383 label_info = topic_common.item_parse_info(attribute_name='labels',
384 inline_option='labels',
385 filename_option='blist')
386 acl_info = topic_common.item_parse_info(attribute_name='acls',
387 inline_option='acls',
388 filename_option='alist')
389
Justin Giorgi16bba562016-06-22 10:42:13 -0700390 (options, leftover) = super(BaseHostModCreate, self).parse([label_info,
mbligh9deeefa2009-05-01 23:11:08 +0000391 acl_info],
392 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000393
394 self._parse_lock_options(options)
Justin Giorgi16bba562016-06-22 10:42:13 -0700395
mblighaed47e82009-03-17 19:06:18 +0000396 if options.protection:
397 self.data['protection'] = options.protection
Justin Giorgi16bba562016-06-22 10:42:13 -0700398 self.messages.append('Protection set to "%s"' % options.protection)
399
400 self.attributes = {}
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700401 if options.attribute:
402 for pair in options.attribute:
403 m = re.match(self.attribute_regex, pair)
404 if not m:
405 raise topic_common.CliError('Attribute must be in key=value '
406 'syntax.')
407 elif m.group('attribute') in self.attributes:
xixuand6011f12016-12-08 15:01:58 -0800408 raise topic_common.CliError(
409 'Multiple values provided for attribute '
410 '%s.' % m.group('attribute'))
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700411 self.attributes[m.group('attribute')] = m.group('value')
Justin Giorgi16bba562016-06-22 10:42:13 -0700412
413 self.platform = options.platform
mblighbe630eb2008-08-01 16:41:48 +0000414 return (options, leftover)
415
416
Justin Giorgi16bba562016-06-22 10:42:13 -0700417 def _set_acls(self, hosts, acls):
418 """Add hosts to acls (and remove from all other acls).
419
420 @param hosts: list of hostnames
421 @param acls: list of acl names
422 """
423 # Remove from all ACLs except 'Everyone' and ACLs in list
424 # Skip hosts that don't exist
425 for host in hosts:
426 if host not in self.host_ids:
427 continue
428 host_id = self.host_ids[host]
429 for a in self.execute_rpc('get_acl_groups', hosts=host_id):
430 if a['name'] not in self.acls and a['id'] != 1:
431 self.execute_rpc('acl_group_remove_hosts', id=a['id'],
432 hosts=self.hosts)
433
434 # Add hosts to the ACLs
435 self.check_and_create_items('get_acl_groups', 'add_acl_group',
436 self.acls)
437 for a in acls:
438 self.execute_rpc('acl_group_add_hosts', id=a, hosts=hosts)
439
440
441 def _remove_labels(self, host, condition):
442 """Remove all labels from host that meet condition(label).
443
444 @param host: hostname
445 @param condition: callable that returns bool when given a label
446 """
447 if host in self.host_ids:
448 host_id = self.host_ids[host]
449 labels_to_remove = []
450 for l in self.execute_rpc('get_labels', host=host_id):
451 if condition(l):
452 labels_to_remove.append(l['id'])
453 if labels_to_remove:
454 self.execute_rpc('host_remove_labels', id=host_id,
455 labels=labels_to_remove)
456
457
458 def _set_labels(self, host, labels):
459 """Apply labels to host (and remove all other labels).
460
461 @param host: hostname
462 @param labels: list of label names
463 """
464 condition = lambda l: l['name'] not in labels and not l['platform']
465 self._remove_labels(host, condition)
466 self.check_and_create_items('get_labels', 'add_label', labels)
467 self.execute_rpc('host_add_labels', id=host, labels=labels)
468
469
470 def _set_platform_label(self, host, platform_label):
471 """Apply the platform label to host (and remove existing).
472
473 @param host: hostname
474 @param platform_label: platform label's name
475 """
476 self._remove_labels(host, lambda l: l['platform'])
477 self.check_and_create_items('get_labels', 'add_label', [platform_label],
478 platform=True)
479 self.execute_rpc('host_add_labels', id=host, labels=[platform_label])
480
481
482 def _set_attributes(self, host, attributes):
483 """Set attributes on host.
484
485 @param host: hostname
486 @param attributes: attribute dictionary
487 """
488 for attr, value in self.attributes.iteritems():
489 self.execute_rpc('set_host_attribute', attribute=attr,
490 value=value, hostname=host)
491
492
493class host_mod(BaseHostModCreate):
494 """atest host mod [--lock|--unlock --force_modify_locking
495 --platform <arch>
496 --labels <labels>|--blist <label_file>
497 --acls <acls>|--alist <acl_file>
498 --protection <protection_type>
499 --attributes <attr>=<value>;<attr>=<value>
500 --mlist <mach_file>] <hosts>"""
501 usage_action = 'mod'
Justin Giorgi16bba562016-06-22 10:42:13 -0700502
503 def __init__(self):
504 """Add the options specific to the mod action"""
505 super(host_mod, self).__init__()
506 self.parser.add_option('-f', '--force_modify_locking',
507 help='Forcefully lock\unlock a host',
508 action='store_true')
509 self.parser.add_option('--remove_acls',
510 help='Remove all active acls.',
511 action='store_true')
512 self.parser.add_option('--remove_labels',
513 help='Remove all labels.',
514 action='store_true')
515
516
517 def parse(self):
518 """Consume the specific options"""
519 (options, leftover) = super(host_mod, self).parse()
520
521 if options.force_modify_locking:
522 self.data['force_modify_locking'] = True
523
524 self.remove_acls = options.remove_acls
525 self.remove_labels = options.remove_labels
526
527 return (options, leftover)
528
529
530 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800531 """Execute 'atest host mod'."""
Justin Giorgi16bba562016-06-22 10:42:13 -0700532 successes = []
533 for host in self.execute_rpc('get_hosts', hostname__in=self.hosts):
534 self.host_ids[host['hostname']] = host['id']
535 for host in self.hosts:
536 if host not in self.host_ids:
537 self.failure('Cannot modify non-existant host %s.' % host)
538 continue
539 host_id = self.host_ids[host]
540
541 try:
542 if self.data:
543 self.execute_rpc('modify_host', item=host,
544 id=host, **self.data)
545
546 if self.attributes:
547 self._set_attributes(host, self.attributes)
548
549 if self.labels or self.remove_labels:
550 self._set_labels(host, self.labels)
551
552 if self.platform:
553 self._set_platform_label(host, self.platform)
554
555 # TODO: Make the AFE return True or False,
556 # especially for lock
557 successes.append(host)
558 except topic_common.CliError, full_error:
559 # Already logged by execute_rpc()
560 pass
561
562 if self.acls or self.remove_acls:
563 self._set_acls(self.hosts, self.acls)
564
565 return successes
566
567
568 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800569 """Print output of 'atest host mod'.
570
571 @param hosts: the host list to be printed.
572 """
Justin Giorgi16bba562016-06-22 10:42:13 -0700573 for msg in self.messages:
574 self.print_wrapped(msg, hosts)
575
576
577class HostInfo(object):
578 """Store host information so we don't have to keep looking it up."""
579 def __init__(self, hostname, platform, labels):
580 self.hostname = hostname
581 self.platform = platform
582 self.labels = labels
583
584
585class host_create(BaseHostModCreate):
586 """atest host create [--lock|--unlock --platform <arch>
587 --labels <labels>|--blist <label_file>
588 --acls <acls>|--alist <acl_file>
589 --protection <protection_type>
590 --attributes <attr>=<value>;<attr>=<value>
591 --mlist <mach_file>] <hosts>"""
592 usage_action = 'create'
593
594 def parse(self):
595 """Option logic specific to create action.
596 """
597 (options, leftovers) = super(host_create, self).parse()
598 self.locked = options.lock
599 if 'serials' in self.attributes:
600 if len(self.hosts) > 1:
601 raise topic_common.CliError('Can not specify serials with '
602 'multiple hosts.')
603
604
605 @classmethod
606 def construct_without_parse(
607 cls, web_server, hosts, platform=None,
608 locked=False, lock_reason='', labels=[], acls=[],
609 protection=host_protections.Protection.NO_PROTECTION):
610 """Construct a host_create object and fill in data from args.
611
612 Do not need to call parse after the construction.
613
614 Return an object of site_host_create ready to execute.
615
616 @param web_server: A string specifies the autotest webserver url.
617 It is needed to setup comm to make rpc.
618 @param hosts: A list of hostnames as strings.
619 @param platform: A string or None.
620 @param locked: A boolean.
621 @param lock_reason: A string.
622 @param labels: A list of labels as strings.
623 @param acls: A list of acls as strings.
624 @param protection: An enum defined in host_protections.
625 """
626 obj = cls()
627 obj.web_server = web_server
628 try:
629 # Setup stuff needed for afe comm.
630 obj.afe = rpc.afe_comm(web_server)
631 except rpc.AuthError, s:
632 obj.failure(str(s), fatal=True)
633 obj.hosts = hosts
634 obj.platform = platform
635 obj.locked = locked
636 if locked and lock_reason.strip():
637 obj.data['lock_reason'] = lock_reason.strip()
638 obj.labels = labels
639 obj.acls = acls
640 if protection:
641 obj.data['protection'] = protection
642 obj.attributes = {}
643 return obj
644
645
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700646 def _detect_host_info(self, host):
647 """Detect platform and labels from the host.
648
649 @param host: hostname
650
651 @return: HostInfo object
652 """
653 # Mock an afe_host object so that the host is constructed as if the
654 # data was already in afe
655 data = {'attributes': self.attributes, 'labels': self.labels}
656 afe_host = frontend.Host(None, data)
657 machine = {'hostname': host, 'afe_host': afe_host}
658 try:
659 if utils.ping(host, tries=1, deadline=1) == 0:
660 serials = self.attributes.get('serials', '').split(',')
661 if serials and len(serials) > 1:
662 host_dut = hosts.create_testbed(machine,
663 adb_serials=serials)
664 else:
665 adb_serial = self.attributes.get('serials')
666 host_dut = hosts.create_host(machine,
667 adb_serial=adb_serial)
xixuand6011f12016-12-08 15:01:58 -0800668
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700669 host_info = HostInfo(host, host_dut.get_platform(),
670 host_dut.get_labels())
xixuand6011f12016-12-08 15:01:58 -0800671 # Clean host to make sure nothing left after calling it,
672 # e.g. tunnels.
673 if hasattr(host_dut, 'close'):
674 host_dut.close()
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700675 else:
676 # Can't ping the host, use default information.
677 host_info = HostInfo(host, None, [])
678 except (socket.gaierror, error.AutoservRunError,
679 error.AutoservSSHTimeout):
680 # We may be adding a host that does not exist yet or we can't
681 # reach due to hostname/address issues or if the host is down.
682 host_info = HostInfo(host, None, [])
683 return host_info
684
685
mblighbe630eb2008-08-01 16:41:48 +0000686 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000687 # Always add the hosts as locked to avoid the host
Matthew Sartori68186332015-04-27 17:19:53 -0700688 # being picked up by the scheduler before it's ACL'ed.
mbligh719e14a2008-12-04 01:17:08 +0000689 self.data['locked'] = True
Matthew Sartori68186332015-04-27 17:19:53 -0700690 if not self.locked:
691 self.data['lock_reason'] = 'Forced lock on device creation'
Justin Giorgi16bba562016-06-22 10:42:13 -0700692 self.execute_rpc('add_host', hostname=host, status="Ready", **self.data)
mblighbe630eb2008-08-01 16:41:48 +0000693
Justin Giorgi16bba562016-06-22 10:42:13 -0700694 # If there are labels avaliable for host, use them.
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700695 host_info = self._detect_host_info(host)
Justin Giorgi16bba562016-06-22 10:42:13 -0700696 labels = set(self.labels)
697 if host_info.labels:
698 labels.update(host_info.labels)
699
700 if labels:
701 self._set_labels(host, list(labels))
702
703 # Now add the platform label.
704 # If a platform was not provided and we were able to retrieve it
705 # from the host, use the retrieved platform.
706 platform = self.platform if self.platform else host_info.platform
707 if platform:
708 self._set_platform_label(host, platform)
709
710 if self.attributes:
711 self._set_attributes(host, self.attributes)
mblighbe630eb2008-08-01 16:41:48 +0000712
713
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700714 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800715 """Execute 'atest host create'."""
Justin Giorgi16bba562016-06-22 10:42:13 -0700716 successful_hosts = []
717 for host in self.hosts:
718 try:
719 self._execute_add_one_host(host)
720 successful_hosts.append(host)
721 except topic_common.CliError:
722 pass
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700723
724 if successful_hosts:
Justin Giorgi16bba562016-06-22 10:42:13 -0700725 self._set_acls(successful_hosts, self.acls)
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700726
727 if not self.locked:
728 for host in successful_hosts:
Matthew Sartori68186332015-04-27 17:19:53 -0700729 self.execute_rpc('modify_host', id=host, locked=False,
730 lock_reason='')
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700731 return successful_hosts
732
733
mblighbe630eb2008-08-01 16:41:48 +0000734 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800735 """Print output of 'atest host create'.
736
737 @param hosts: the added host list to be printed.
738 """
mblighbe630eb2008-08-01 16:41:48 +0000739 self.print_wrapped('Added host', hosts)
740
741
742class host_delete(action_common.atest_delete, host):
743 """atest host delete [--mlist <mach_file>] <hosts>"""
744 pass