blob: 753492174e4995141e95ca38ba8e481422710333 [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
Ningning Xiaea02ab12018-05-11 15:08:57 -070026from autotest_lib.cli import action_common, rpc, topic_common, skylab_utils
Allen Li2c32d6b2017-02-03 15:28:10 -080027from autotest_lib.client.bin import utils as bin_utils
Justin Giorgi16bba562016-06-22 10:42:13 -070028from autotest_lib.client.common_lib import error, host_protections
Justin Giorgi5208eaa2016-07-02 20:12:12 -070029from autotest_lib.server import frontend, hosts
Prathmesh Prabhud252d262017-05-31 11:46:34 -070030from autotest_lib.server.hosts import host_info
mblighbe630eb2008-08-01 16:41:48 +000031
32
Ningning Xiaea02ab12018-05-11 15:08:57 -070033try:
34 from skylab_inventory import text_manager
35 from skylab_inventory.lib import device
36except ImportError:
37 pass
38
39
mblighbe630eb2008-08-01 16:41:48 +000040class host(topic_common.atest):
41 """Host class
42 atest host [create|delete|list|stat|mod|jobs] <options>"""
43 usage_action = '[create|delete|list|stat|mod|jobs]'
44 topic = msg_topic = 'host'
45 msg_items = '<hosts>'
46
mblighaed47e82009-03-17 19:06:18 +000047 protections = host_protections.Protection.names
48
mblighbe630eb2008-08-01 16:41:48 +000049
50 def __init__(self):
51 """Add to the parser the options common to all the
52 host actions"""
53 super(host, self).__init__()
54
55 self.parser.add_option('-M', '--mlist',
56 help='File listing the machines',
57 type='string',
58 default=None,
59 metavar='MACHINE_FLIST')
60
mbligh9deeefa2009-05-01 23:11:08 +000061 self.topic_parse_info = topic_common.item_parse_info(
62 attribute_name='hosts',
63 filename_option='mlist',
64 use_leftover=True)
mblighbe630eb2008-08-01 16:41:48 +000065
66
67 def _parse_lock_options(self, options):
68 if options.lock and options.unlock:
69 self.invalid_syntax('Only specify one of '
70 '--lock and --unlock.')
71
Ningning Xiaef35cb52018-05-04 17:58:20 -070072 if options.skylab and options.unlock and options.unlock_lock_id is None:
73 self.invalid_syntax('Must provide --unlock-lock-id with "--skylab '
74 '--unlock".')
75
76 if (not (options.skylab and options.unlock) and
77 options.unlock_lock_id is not None):
78 self.invalid_syntax('--unlock-lock-id is only valid with '
79 '"--skylab --unlock".')
80
81 self.lock = options.lock
82 self.unlock = options.unlock
83 self.lock_reason = options.lock_reason
84 self.unlock_lock_id = options.unlock_lock_id
85
mblighbe630eb2008-08-01 16:41:48 +000086 if options.lock:
87 self.data['locked'] = True
88 self.messages.append('Locked host')
89 elif options.unlock:
90 self.data['locked'] = False
Matthew Sartori68186332015-04-27 17:19:53 -070091 self.data['lock_reason'] = ''
mblighbe630eb2008-08-01 16:41:48 +000092 self.messages.append('Unlocked host')
93
Matthew Sartori68186332015-04-27 17:19:53 -070094 if options.lock and options.lock_reason:
95 self.data['lock_reason'] = options.lock_reason
96
mblighbe630eb2008-08-01 16:41:48 +000097
98 def _cleanup_labels(self, labels, platform=None):
99 """Removes the platform label from the overall labels"""
100 if platform:
101 return [label for label in labels
102 if label != platform]
103 else:
104 try:
105 return [label for label in labels
106 if not label['platform']]
107 except TypeError:
108 # This is a hack - the server will soon
109 # do this, so all this code should be removed.
110 return labels
111
112
113 def get_items(self):
114 return self.hosts
115
116
117class host_help(host):
118 """Just here to get the atest logic working.
119 Usage is set by its parent"""
120 pass
121
122
123class host_list(action_common.atest_list, host):
124 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +0000125 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +0000126
127 def __init__(self):
128 super(host_list, self).__init__()
129
130 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000131 default='',
132 help='Only list hosts with all these labels '
Ningning Xiaea02ab12018-05-11 15:08:57 -0700133 '(comma separated). When --skylab is provided, '
134 'a label must be in the format of '
135 'label-key:label-value (e.g., board:lumpy).')
mblighbe630eb2008-08-01 16:41:48 +0000136 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000137 default='',
138 help='Only list hosts with any of these '
139 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000140 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000141 default='',
Ningning Xiaea02ab12018-05-11 15:08:57 -0700142 help=('Only list hosts within this ACL. %s' %
143 skylab_utils.MSG_INVALID_IN_SKYLAB))
mbligh536a5242008-10-18 14:35:54 +0000144 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000145 default='',
Ningning Xiaea02ab12018-05-11 15:08:57 -0700146 help=('Only list hosts available to this user. '
147 '%s' % skylab_utils.MSG_INVALID_IN_SKYLAB))
mbligh70b9bf42008-11-18 15:00:46 +0000148 self.parser.add_option('-N', '--hostnames-only', help='Only return '
149 'hostnames for the machines queried.',
150 action='store_true')
mbligh91e0efd2009-02-26 01:02:16 +0000151 self.parser.add_option('--locked',
152 default=False,
153 help='Only list locked hosts',
154 action='store_true')
155 self.parser.add_option('--unlocked',
156 default=False,
157 help='Only list unlocked hosts',
158 action='store_true')
Ningning Xiaea02ab12018-05-11 15:08:57 -0700159 self.parser.add_option('--full-output',
160 default=False,
161 help=('Print out the full content of the hosts. '
162 'Only supported with --skylab.'),
163 action='store_true',
164 dest='full_output')
mbligh91e0efd2009-02-26 01:02:16 +0000165
Ningning Xiaea02ab12018-05-11 15:08:57 -0700166 self.add_skylab_options()
mblighbe630eb2008-08-01 16:41:48 +0000167
168
169 def parse(self):
170 """Consume the specific options"""
jamesrenc2863162010-07-12 21:20:51 +0000171 label_info = topic_common.item_parse_info(attribute_name='labels',
172 inline_option='label')
173
174 (options, leftover) = super(host_list, self).parse([label_info])
175
mblighbe630eb2008-08-01 16:41:48 +0000176 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000177 self.acl = options.acl
178 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000179 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000180
181 if options.locked and options.unlocked:
182 self.invalid_syntax('--locked and --unlocked are '
183 'mutually exclusive')
Ningning Xiaea02ab12018-05-11 15:08:57 -0700184
mbligh91e0efd2009-02-26 01:02:16 +0000185 self.locked = options.locked
186 self.unlocked = options.unlocked
Ningning Xia64ced002018-05-16 17:36:20 -0700187 self.label_map = None
Ningning Xiaea02ab12018-05-11 15:08:57 -0700188
189 if self.skylab:
190 if options.user or options.acl or options.status:
191 self.invalid_syntax('--user, --acl or --status is not '
192 'supported with --skylab.')
193 self.full_output = options.full_output
194 if self.full_output and self.hostnames_only:
195 self.invalid_syntax('--full-output is conflicted with '
196 '--hostnames-only.')
Ningning Xia64ced002018-05-16 17:36:20 -0700197
198 if self.labels:
199 self.label_map = device.convert_to_label_map(self.labels)
Ningning Xiaea02ab12018-05-11 15:08:57 -0700200 else:
201 if options.full_output:
202 self.invalid_syntax('--full_output is only supported with '
203 '--skylab.')
204
mblighbe630eb2008-08-01 16:41:48 +0000205 return (options, leftover)
206
207
Ningning Xiaea02ab12018-05-11 15:08:57 -0700208 def execute_skylab(self):
209 """Execute 'atest host list' with --skylab."""
210 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
211 inventory_repo.initialize()
212 lab = text_manager.load_lab(inventory_repo.get_data_dir())
213
214 # TODO(nxia): support filtering on run-time labels and status.
215 return device.get_devices(
216 lab,
217 'duts',
218 self.environment,
Ningning Xia64ced002018-05-16 17:36:20 -0700219 label_map=self.label_map,
Ningning Xiaea02ab12018-05-11 15:08:57 -0700220 hostnames=self.hosts,
221 locked=self.locked,
222 unlocked=self.unlocked)
223
224
mblighbe630eb2008-08-01 16:41:48 +0000225 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800226 """Execute 'atest host list'."""
Ningning Xiaea02ab12018-05-11 15:08:57 -0700227 if self.skylab:
228 return self.execute_skylab()
229
mblighbe630eb2008-08-01 16:41:48 +0000230 filters = {}
231 check_results = {}
232 if self.hosts:
233 filters['hostname__in'] = self.hosts
234 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000235
236 if self.labels:
jamesrenc2863162010-07-12 21:20:51 +0000237 if len(self.labels) == 1:
mblighf703fb42009-01-30 00:35:05 +0000238 # This is needed for labels with wildcards (x86*)
jamesrenc2863162010-07-12 21:20:51 +0000239 filters['labels__name__in'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000240 check_results['labels__name__in'] = None
241 else:
jamesrenc2863162010-07-12 21:20:51 +0000242 filters['multiple_labels'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000243 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000244
mblighbe630eb2008-08-01 16:41:48 +0000245 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000246 statuses = self.status.split(',')
247 statuses = [status.strip() for status in statuses
248 if status.strip()]
249
250 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000251 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000252
mbligh536a5242008-10-18 14:35:54 +0000253 if self.acl:
showardd9ac4452009-02-07 02:04:37 +0000254 filters['aclgroup__name'] = self.acl
255 check_results['aclgroup__name'] = None
mbligh536a5242008-10-18 14:35:54 +0000256 if self.user:
showardd9ac4452009-02-07 02:04:37 +0000257 filters['aclgroup__users__login'] = self.user
258 check_results['aclgroup__users__login'] = None
mbligh91e0efd2009-02-26 01:02:16 +0000259
260 if self.locked or self.unlocked:
261 filters['locked'] = self.locked
262 check_results['locked'] = None
263
mblighbe630eb2008-08-01 16:41:48 +0000264 return super(host_list, self).execute(op='get_hosts',
265 filters=filters,
266 check_results=check_results)
267
268
269 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800270 """Print output of 'atest host list'.
271
272 @param results: the results to be printed.
273 """
Ningning Xiaea02ab12018-05-11 15:08:57 -0700274 if results and not self.skylab:
mblighbe630eb2008-08-01 16:41:48 +0000275 # Remove the platform from the labels.
276 for result in results:
277 result['labels'] = self._cleanup_labels(result['labels'],
278 result['platform'])
Ningning Xiaea02ab12018-05-11 15:08:57 -0700279 if self.skylab and self.full_output:
280 print results
281 return
282
283 if self.skylab:
284 results = device.convert_to_autotest_hosts(results)
285
mbligh70b9bf42008-11-18 15:00:46 +0000286 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000287 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000288 else:
Ningning Xiaea02ab12018-05-11 15:08:57 -0700289 keys = ['hostname', 'status', 'shard', 'locked', 'lock_reason',
290 'locked_by', 'platform', 'labels']
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800291 super(host_list, self).output(results, keys=keys)
mblighbe630eb2008-08-01 16:41:48 +0000292
293
294class host_stat(host):
295 """atest host stat --mlist <file>|<hosts>"""
296 usage_action = 'stat'
297
298 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800299 """Execute 'atest host stat'."""
mblighbe630eb2008-08-01 16:41:48 +0000300 results = []
301 # Convert wildcards into real host stats.
302 existing_hosts = []
303 for host in self.hosts:
304 if host.endswith('*'):
305 stats = self.execute_rpc('get_hosts',
306 hostname__startswith=host.rstrip('*'))
307 if len(stats) == 0:
308 self.failure('No hosts matching %s' % host, item=host,
309 what_failed='Failed to stat')
310 continue
311 else:
312 stats = self.execute_rpc('get_hosts', hostname=host)
313 if len(stats) == 0:
314 self.failure('Unknown host %s' % host, item=host,
315 what_failed='Failed to stat')
316 continue
317 existing_hosts.extend(stats)
318
319 for stat in existing_hosts:
320 host = stat['hostname']
321 # The host exists, these should succeed
322 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
323
324 labels = self.execute_rpc('get_labels', host__hostname=host)
Simran Basi0739d682015-02-25 16:22:56 -0800325 results.append([[stat], acls, labels, stat['attributes']])
mblighbe630eb2008-08-01 16:41:48 +0000326 return results
327
328
329 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800330 """Print output of 'atest host stat'.
331
332 @param results: the results to be printed.
333 """
Simran Basi0739d682015-02-25 16:22:56 -0800334 for stats, acls, labels, attributes in results:
mblighbe630eb2008-08-01 16:41:48 +0000335 print '-'*5
336 self.print_fields(stats,
Aviv Kesheta40d1272017-11-16 00:56:35 -0800337 keys=['hostname', 'id', 'platform',
mblighe163b032008-10-18 14:30:27 +0000338 'status', 'locked', 'locked_by',
Matthew Sartori68186332015-04-27 17:19:53 -0700339 'lock_time', 'lock_reason', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000340 self.print_by_ids(acls, 'ACLs', line_before=True)
341 labels = self._cleanup_labels(labels)
342 self.print_by_ids(labels, 'Labels', line_before=True)
Simran Basi0739d682015-02-25 16:22:56 -0800343 self.print_dict(attributes, 'Host Attributes', line_before=True)
mblighbe630eb2008-08-01 16:41:48 +0000344
345
346class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000347 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000348 usage_action = 'jobs'
349
mbligh6996fe82008-08-13 00:32:27 +0000350 def __init__(self):
351 super(host_jobs, self).__init__()
352 self.parser.add_option('-q', '--max-query',
353 help='Limits the number of results '
354 '(20 by default)',
355 type='int', default=20)
356
357
358 def parse(self):
359 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000360 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000361 self.max_queries = options.max_query
362 return (options, leftover)
363
364
mblighbe630eb2008-08-01 16:41:48 +0000365 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800366 """Execute 'atest host jobs'."""
mblighbe630eb2008-08-01 16:41:48 +0000367 results = []
368 real_hosts = []
369 for host in self.hosts:
370 if host.endswith('*'):
371 stats = self.execute_rpc('get_hosts',
372 hostname__startswith=host.rstrip('*'))
373 if len(stats) == 0:
374 self.failure('No host matching %s' % host, item=host,
375 what_failed='Failed to stat')
376 [real_hosts.append(stat['hostname']) for stat in stats]
377 else:
378 real_hosts.append(host)
379
380 for host in real_hosts:
381 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000382 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000383 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000384 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000385 jobs = []
386 for entry in queue_entries:
387 job = {'job_id': entry['job']['id'],
388 'job_owner': entry['job']['owner'],
389 'job_name': entry['job']['name'],
390 'status': entry['status']}
391 jobs.append(job)
392 results.append((host, jobs))
393 return results
394
395
396 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800397 """Print output of 'atest host jobs'.
398
399 @param results: the results to be printed.
400 """
mblighbe630eb2008-08-01 16:41:48 +0000401 for host, jobs in results:
402 print '-'*5
403 print 'Hostname: %s' % host
404 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000405 'job_owner',
406 'job_name',
407 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000408
Justin Giorgi16bba562016-06-22 10:42:13 -0700409class BaseHostModCreate(host):
xixuand6011f12016-12-08 15:01:58 -0800410 """The base class for host_mod and host_create"""
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700411 # Matches one attribute=value pair
412 attribute_regex = r'(?P<attribute>\w+)=(?P<value>.+)?'
mblighbe630eb2008-08-01 16:41:48 +0000413
414 def __init__(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700415 """Add the options shared between host mod and host create actions."""
mblighbe630eb2008-08-01 16:41:48 +0000416 self.messages = []
Justin Giorgi16bba562016-06-22 10:42:13 -0700417 self.host_ids = {}
418 super(BaseHostModCreate, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000419 self.parser.add_option('-l', '--lock',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700420 help='Lock hosts.',
mblighbe630eb2008-08-01 16:41:48 +0000421 action='store_true')
Matthew Sartori68186332015-04-27 17:19:53 -0700422 self.parser.add_option('-r', '--lock_reason',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700423 help='Reason for locking hosts.',
Matthew Sartori68186332015-04-27 17:19:53 -0700424 default='')
Ningning Xiaef35cb52018-05-04 17:58:20 -0700425 self.parser.add_option('-u', '--unlock',
426 help='Unlock hosts.',
427 action='store_true')
428 self.parser.add_option('--unlock-lock-id',
429 help=('Unlock the lock with the lock-id. Only '
430 'useful when unlocking skylab hosts.'),
431 default=None)
mblighe163b032008-10-18 14:30:27 +0000432 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000433 help=('Set the protection level on a host. '
Ningning Xiaef35cb52018-05-04 17:58:20 -0700434 'Must be one of: %s. %s' %
435 (', '.join('"%s"' % p
436 for p in self.protections),
437 skylab_utils.MSG_INVALID_IN_SKYLAB)),
mblighaed47e82009-03-17 19:06:18 +0000438 choices=self.protections)
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700439 self._attributes = []
440 self.parser.add_option('--attribute', '-i',
441 help=('Host attribute to add or change. Format '
442 'is <attribute>=<value>. Multiple '
443 'attributes can be set by passing the '
444 'argument multiple times. Attributes can '
445 'be unset by providing an empty value.'),
446 action='append')
mblighbe630eb2008-08-01 16:41:48 +0000447 self.parser.add_option('-b', '--labels',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700448 help=('Comma separated list of labels. '
449 'When --skylab is provided, a label must '
450 'be in the format of label-key:label-value'
451 ' (e.g., board:lumpy).'))
mblighbe630eb2008-08-01 16:41:48 +0000452 self.parser.add_option('-B', '--blist',
453 help='File listing the labels',
454 type='string',
455 metavar='LABEL_FLIST')
456 self.parser.add_option('-a', '--acls',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700457 help=('Comma separated list of ACLs. %s' %
458 skylab_utils.MSG_INVALID_IN_SKYLAB))
mblighbe630eb2008-08-01 16:41:48 +0000459 self.parser.add_option('-A', '--alist',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700460 help=('File listing the acls. %s' %
461 skylab_utils.MSG_INVALID_IN_SKYLAB),
mblighbe630eb2008-08-01 16:41:48 +0000462 type='string',
463 metavar='ACL_FLIST')
Justin Giorgi16bba562016-06-22 10:42:13 -0700464 self.parser.add_option('-t', '--platform',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700465 help=('Sets the platform label. This is '
466 'deprecated for --skylab. Please set '
467 'platform in labels (e.g., '
468 '-b platform:platform_name).'))
469
470 self.add_skylab_options()
mblighbe630eb2008-08-01 16:41:48 +0000471
472
473 def parse(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700474 """Consume the options common to host create and host mod.
475 """
mbligh9deeefa2009-05-01 23:11:08 +0000476 label_info = topic_common.item_parse_info(attribute_name='labels',
477 inline_option='labels',
478 filename_option='blist')
479 acl_info = topic_common.item_parse_info(attribute_name='acls',
480 inline_option='acls',
481 filename_option='alist')
482
Justin Giorgi16bba562016-06-22 10:42:13 -0700483 (options, leftover) = super(BaseHostModCreate, self).parse([label_info,
mbligh9deeefa2009-05-01 23:11:08 +0000484 acl_info],
485 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000486
487 self._parse_lock_options(options)
Justin Giorgi16bba562016-06-22 10:42:13 -0700488
Ningning Xia64ced002018-05-16 17:36:20 -0700489 self.label_map = None
490 if self.skylab:
Ningning Xiaef35cb52018-05-04 17:58:20 -0700491 # TODO(nxia): drop these flags when all hosts are migrated to skylab
Ningning Xia64ced002018-05-16 17:36:20 -0700492 if (options.protection or options.acls or options.alist or
493 options.platform):
494 self.invalid_syntax(
495 '--protection, --acls, --alist or --platform is not '
496 'supported with --skylab.')
497
498 if self.labels:
499 self.label_map = device.convert_to_label_map(self.labels)
Ningning Xiaef35cb52018-05-04 17:58:20 -0700500
mblighaed47e82009-03-17 19:06:18 +0000501 if options.protection:
502 self.data['protection'] = options.protection
Justin Giorgi16bba562016-06-22 10:42:13 -0700503 self.messages.append('Protection set to "%s"' % options.protection)
504
505 self.attributes = {}
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700506 if options.attribute:
507 for pair in options.attribute:
508 m = re.match(self.attribute_regex, pair)
509 if not m:
510 raise topic_common.CliError('Attribute must be in key=value '
511 'syntax.')
512 elif m.group('attribute') in self.attributes:
xixuand6011f12016-12-08 15:01:58 -0800513 raise topic_common.CliError(
514 'Multiple values provided for attribute '
515 '%s.' % m.group('attribute'))
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700516 self.attributes[m.group('attribute')] = m.group('value')
Justin Giorgi16bba562016-06-22 10:42:13 -0700517
518 self.platform = options.platform
mblighbe630eb2008-08-01 16:41:48 +0000519 return (options, leftover)
520
521
Justin Giorgi16bba562016-06-22 10:42:13 -0700522 def _set_acls(self, hosts, acls):
523 """Add hosts to acls (and remove from all other acls).
524
525 @param hosts: list of hostnames
526 @param acls: list of acl names
527 """
528 # Remove from all ACLs except 'Everyone' and ACLs in list
529 # Skip hosts that don't exist
530 for host in hosts:
531 if host not in self.host_ids:
532 continue
533 host_id = self.host_ids[host]
534 for a in self.execute_rpc('get_acl_groups', hosts=host_id):
535 if a['name'] not in self.acls and a['id'] != 1:
536 self.execute_rpc('acl_group_remove_hosts', id=a['id'],
537 hosts=self.hosts)
538
539 # Add hosts to the ACLs
540 self.check_and_create_items('get_acl_groups', 'add_acl_group',
541 self.acls)
542 for a in acls:
543 self.execute_rpc('acl_group_add_hosts', id=a, hosts=hosts)
544
545
546 def _remove_labels(self, host, condition):
547 """Remove all labels from host that meet condition(label).
548
549 @param host: hostname
550 @param condition: callable that returns bool when given a label
551 """
552 if host in self.host_ids:
553 host_id = self.host_ids[host]
554 labels_to_remove = []
555 for l in self.execute_rpc('get_labels', host=host_id):
556 if condition(l):
557 labels_to_remove.append(l['id'])
558 if labels_to_remove:
559 self.execute_rpc('host_remove_labels', id=host_id,
560 labels=labels_to_remove)
561
562
563 def _set_labels(self, host, labels):
564 """Apply labels to host (and remove all other labels).
565
566 @param host: hostname
567 @param labels: list of label names
568 """
569 condition = lambda l: l['name'] not in labels and not l['platform']
570 self._remove_labels(host, condition)
571 self.check_and_create_items('get_labels', 'add_label', labels)
572 self.execute_rpc('host_add_labels', id=host, labels=labels)
573
574
575 def _set_platform_label(self, host, platform_label):
576 """Apply the platform label to host (and remove existing).
577
578 @param host: hostname
579 @param platform_label: platform label's name
580 """
581 self._remove_labels(host, lambda l: l['platform'])
582 self.check_and_create_items('get_labels', 'add_label', [platform_label],
583 platform=True)
584 self.execute_rpc('host_add_labels', id=host, labels=[platform_label])
585
586
587 def _set_attributes(self, host, attributes):
588 """Set attributes on host.
589
590 @param host: hostname
591 @param attributes: attribute dictionary
592 """
593 for attr, value in self.attributes.iteritems():
594 self.execute_rpc('set_host_attribute', attribute=attr,
595 value=value, hostname=host)
596
597
598class host_mod(BaseHostModCreate):
599 """atest host mod [--lock|--unlock --force_modify_locking
600 --platform <arch>
601 --labels <labels>|--blist <label_file>
602 --acls <acls>|--alist <acl_file>
603 --protection <protection_type>
604 --attributes <attr>=<value>;<attr>=<value>
605 --mlist <mach_file>] <hosts>"""
606 usage_action = 'mod'
Justin Giorgi16bba562016-06-22 10:42:13 -0700607
608 def __init__(self):
609 """Add the options specific to the mod action"""
610 super(host_mod, self).__init__()
611 self.parser.add_option('-f', '--force_modify_locking',
612 help='Forcefully lock\unlock a host',
613 action='store_true')
614 self.parser.add_option('--remove_acls',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700615 help=('Remove all active acls. %s' %
616 skylab_utils.MSG_INVALID_IN_SKYLAB),
Justin Giorgi16bba562016-06-22 10:42:13 -0700617 action='store_true')
618 self.parser.add_option('--remove_labels',
619 help='Remove all labels.',
620 action='store_true')
621
622
623 def parse(self):
624 """Consume the specific options"""
625 (options, leftover) = super(host_mod, self).parse()
626
627 if options.force_modify_locking:
628 self.data['force_modify_locking'] = True
629
Ningning Xiaef35cb52018-05-04 17:58:20 -0700630 if self.skylab and options.remove_acls:
631 # TODO(nxia): drop the flag when all hosts are migrated to skylab
632 self.invalid_syntax('--remove_acls is not supported with --skylab.')
633
Justin Giorgi16bba562016-06-22 10:42:13 -0700634 self.remove_acls = options.remove_acls
635 self.remove_labels = options.remove_labels
636
637 return (options, leftover)
638
639
Ningning Xiaef35cb52018-05-04 17:58:20 -0700640 def execute_skylab(self):
641 """Execute atest host mod with --skylab.
642
643 @return A list of hostnames which have been successfully modified.
644 """
645 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
646 inventory_repo.initialize()
647 data_dir = inventory_repo.get_data_dir()
648 lab = text_manager.load_lab(data_dir)
649
650 locked_by = None
651 if self.lock:
652 locked_by = inventory_repo.git_repo.config('user.email')
653
654 successes = []
655 for hostname in self.hosts:
656 try:
657 device.modify(
658 lab,
659 'duts',
660 hostname,
661 self.environment,
662 lock=self.lock,
663 locked_by=locked_by,
664 lock_reason = self.lock_reason,
665 unlock=self.unlock,
666 unlock_lock_id=self.unlock_lock_id,
667 attributes=self.attributes,
668 remove_labels=self.remove_labels,
Ningning Xia64ced002018-05-16 17:36:20 -0700669 label_map=self.label_map)
Ningning Xiaef35cb52018-05-04 17:58:20 -0700670 successes.append(hostname)
671 except device.SkylabDeviceActionError as e:
672 print('Cannot modify host %s: %s' % (hostname, e))
673
674 if successes:
675 text_manager.dump_lab(data_dir, lab)
676
677 status = inventory_repo.git_repo.status()
678 if not status:
679 print('Nothing is changed for hosts %s.' % successes)
680 return []
681
682 message = skylab_utils.construct_commit_message(
683 'Modify %d hosts.\n\n%s' % (len(successes), successes))
684 self.change_number = inventory_repo.upload_change(
685 message, draft=self.draft, dryrun=self.dryrun,
686 submit=self.submit)
687
688 return successes
689
690
Justin Giorgi16bba562016-06-22 10:42:13 -0700691 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800692 """Execute 'atest host mod'."""
Ningning Xiaef35cb52018-05-04 17:58:20 -0700693 if self.skylab:
694 return self.execute_skylab()
695
Justin Giorgi16bba562016-06-22 10:42:13 -0700696 successes = []
697 for host in self.execute_rpc('get_hosts', hostname__in=self.hosts):
698 self.host_ids[host['hostname']] = host['id']
699 for host in self.hosts:
700 if host not in self.host_ids:
701 self.failure('Cannot modify non-existant host %s.' % host)
702 continue
703 host_id = self.host_ids[host]
704
705 try:
706 if self.data:
707 self.execute_rpc('modify_host', item=host,
708 id=host, **self.data)
709
710 if self.attributes:
711 self._set_attributes(host, self.attributes)
712
713 if self.labels or self.remove_labels:
714 self._set_labels(host, self.labels)
715
716 if self.platform:
717 self._set_platform_label(host, self.platform)
718
719 # TODO: Make the AFE return True or False,
720 # especially for lock
721 successes.append(host)
722 except topic_common.CliError, full_error:
723 # Already logged by execute_rpc()
724 pass
725
726 if self.acls or self.remove_acls:
727 self._set_acls(self.hosts, self.acls)
728
729 return successes
730
731
732 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800733 """Print output of 'atest host mod'.
734
735 @param hosts: the host list to be printed.
736 """
Justin Giorgi16bba562016-06-22 10:42:13 -0700737 for msg in self.messages:
738 self.print_wrapped(msg, hosts)
739
Ningning Xiaef35cb52018-05-04 17:58:20 -0700740 if hosts and self.skylab:
Ningning Xiac8b430d2018-05-17 15:10:25 -0700741 print('Modified hosts: %s.' % ', '.join(hosts))
Ningning Xiaef35cb52018-05-04 17:58:20 -0700742 if self.skylab and not self.dryrun and not self.submit:
Ningning Xiac8b430d2018-05-17 15:10:25 -0700743 print(skylab_utils.get_cl_message(self.change_number))
Ningning Xiaef35cb52018-05-04 17:58:20 -0700744
Justin Giorgi16bba562016-06-22 10:42:13 -0700745
746class HostInfo(object):
747 """Store host information so we don't have to keep looking it up."""
748 def __init__(self, hostname, platform, labels):
749 self.hostname = hostname
750 self.platform = platform
751 self.labels = labels
752
753
754class host_create(BaseHostModCreate):
755 """atest host create [--lock|--unlock --platform <arch>
756 --labels <labels>|--blist <label_file>
757 --acls <acls>|--alist <acl_file>
758 --protection <protection_type>
759 --attributes <attr>=<value>;<attr>=<value>
760 --mlist <mach_file>] <hosts>"""
761 usage_action = 'create'
762
763 def parse(self):
764 """Option logic specific to create action.
765 """
766 (options, leftovers) = super(host_create, self).parse()
767 self.locked = options.lock
768 if 'serials' in self.attributes:
769 if len(self.hosts) > 1:
770 raise topic_common.CliError('Can not specify serials with '
771 'multiple hosts.')
772
773
774 @classmethod
775 def construct_without_parse(
776 cls, web_server, hosts, platform=None,
777 locked=False, lock_reason='', labels=[], acls=[],
778 protection=host_protections.Protection.NO_PROTECTION):
779 """Construct a host_create object and fill in data from args.
780
781 Do not need to call parse after the construction.
782
783 Return an object of site_host_create ready to execute.
784
785 @param web_server: A string specifies the autotest webserver url.
786 It is needed to setup comm to make rpc.
787 @param hosts: A list of hostnames as strings.
788 @param platform: A string or None.
789 @param locked: A boolean.
790 @param lock_reason: A string.
791 @param labels: A list of labels as strings.
792 @param acls: A list of acls as strings.
793 @param protection: An enum defined in host_protections.
794 """
795 obj = cls()
796 obj.web_server = web_server
797 try:
798 # Setup stuff needed for afe comm.
799 obj.afe = rpc.afe_comm(web_server)
800 except rpc.AuthError, s:
801 obj.failure(str(s), fatal=True)
802 obj.hosts = hosts
803 obj.platform = platform
804 obj.locked = locked
805 if locked and lock_reason.strip():
806 obj.data['lock_reason'] = lock_reason.strip()
807 obj.labels = labels
808 obj.acls = acls
809 if protection:
810 obj.data['protection'] = protection
811 obj.attributes = {}
812 return obj
813
814
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700815 def _detect_host_info(self, host):
816 """Detect platform and labels from the host.
817
818 @param host: hostname
819
820 @return: HostInfo object
821 """
822 # Mock an afe_host object so that the host is constructed as if the
823 # data was already in afe
824 data = {'attributes': self.attributes, 'labels': self.labels}
825 afe_host = frontend.Host(None, data)
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700826 store = host_info.InMemoryHostInfoStore(
827 host_info.HostInfo(labels=self.labels,
828 attributes=self.attributes))
829 machine = {
830 'hostname': host,
831 'afe_host': afe_host,
832 'host_info_store': store
833 }
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700834 try:
Allen Li2c32d6b2017-02-03 15:28:10 -0800835 if bin_utils.ping(host, tries=1, deadline=1) == 0:
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700836 serials = self.attributes.get('serials', '').split(',')
Richard Barnette9db80682018-04-26 00:55:15 +0000837 adb_serial = self.attributes.get('serials')
838 host_dut = hosts.create_host(machine,
839 adb_serial=adb_serial)
xixuand6011f12016-12-08 15:01:58 -0800840
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700841 info = HostInfo(host, host_dut.get_platform(),
842 host_dut.get_labels())
xixuand6011f12016-12-08 15:01:58 -0800843 # Clean host to make sure nothing left after calling it,
844 # e.g. tunnels.
845 if hasattr(host_dut, 'close'):
846 host_dut.close()
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700847 else:
848 # Can't ping the host, use default information.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700849 info = HostInfo(host, None, [])
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700850 except (socket.gaierror, error.AutoservRunError,
851 error.AutoservSSHTimeout):
852 # We may be adding a host that does not exist yet or we can't
853 # reach due to hostname/address issues or if the host is down.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700854 info = HostInfo(host, None, [])
855 return info
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700856
857
mblighbe630eb2008-08-01 16:41:48 +0000858 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000859 # Always add the hosts as locked to avoid the host
Matthew Sartori68186332015-04-27 17:19:53 -0700860 # being picked up by the scheduler before it's ACL'ed.
mbligh719e14a2008-12-04 01:17:08 +0000861 self.data['locked'] = True
Matthew Sartori68186332015-04-27 17:19:53 -0700862 if not self.locked:
863 self.data['lock_reason'] = 'Forced lock on device creation'
Justin Giorgi16bba562016-06-22 10:42:13 -0700864 self.execute_rpc('add_host', hostname=host, status="Ready", **self.data)
mblighbe630eb2008-08-01 16:41:48 +0000865
Justin Giorgi16bba562016-06-22 10:42:13 -0700866 # If there are labels avaliable for host, use them.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700867 info = self._detect_host_info(host)
Justin Giorgi16bba562016-06-22 10:42:13 -0700868 labels = set(self.labels)
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700869 if info.labels:
870 labels.update(info.labels)
Justin Giorgi16bba562016-06-22 10:42:13 -0700871
872 if labels:
873 self._set_labels(host, list(labels))
874
875 # Now add the platform label.
876 # If a platform was not provided and we were able to retrieve it
877 # from the host, use the retrieved platform.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700878 platform = self.platform if self.platform else info.platform
Justin Giorgi16bba562016-06-22 10:42:13 -0700879 if platform:
880 self._set_platform_label(host, platform)
881
882 if self.attributes:
883 self._set_attributes(host, self.attributes)
mblighbe630eb2008-08-01 16:41:48 +0000884
885
Ningning Xia73868fb2018-05-16 19:05:52 -0700886 def execute_skylab(self):
887 """Execute atest host create with --skylab.
888
889 @return A list of hostnames which have been successfully created.
890 """
891 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
892 inventory_repo.initialize()
893 data_dir = inventory_repo.get_data_dir()
894 lab = text_manager.load_lab(data_dir)
895
896 locked_by = None
897 if self.lock:
898 locked_by = inventory_repo.git_repo.config('user.email')
899
900 successes = []
901 for hostname in self.hosts:
902 try:
903 device.create(
904 lab,
905 'duts',
906 hostname,
907 self.environment,
908 lock=self.lock,
909 locked_by=locked_by,
910 lock_reason = self.lock_reason,
911 attributes=self.attributes,
912 label_map=self.label_map)
913 successes.append(hostname)
914 except device.SkylabDeviceActionError as e:
915 print('Cannot create host %s: %s' % (hostname, e))
916
917 if successes:
918 text_manager.dump_lab(data_dir, lab)
919 message = skylab_utils.construct_commit_message(
920 'Create %d hosts.\n\n%s' % (len(successes), successes))
921 self.change_number = inventory_repo.upload_change(
922 message, draft=self.draft, dryrun=self.dryrun,
923 submit=self.submit)
924
925 return successes
926
927
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700928 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800929 """Execute 'atest host create'."""
Ningning Xia73868fb2018-05-16 19:05:52 -0700930 if self.skylab:
931 return self.execute_skylab()
932
Justin Giorgi16bba562016-06-22 10:42:13 -0700933 successful_hosts = []
934 for host in self.hosts:
935 try:
936 self._execute_add_one_host(host)
937 successful_hosts.append(host)
938 except topic_common.CliError:
939 pass
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700940
941 if successful_hosts:
Justin Giorgi16bba562016-06-22 10:42:13 -0700942 self._set_acls(successful_hosts, self.acls)
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700943
944 if not self.locked:
945 for host in successful_hosts:
Matthew Sartori68186332015-04-27 17:19:53 -0700946 self.execute_rpc('modify_host', id=host, locked=False,
947 lock_reason='')
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700948 return successful_hosts
949
950
mblighbe630eb2008-08-01 16:41:48 +0000951 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800952 """Print output of 'atest host create'.
953
954 @param hosts: the added host list to be printed.
955 """
mblighbe630eb2008-08-01 16:41:48 +0000956 self.print_wrapped('Added host', hosts)
957
958
959class host_delete(action_common.atest_delete, host):
960 """atest host delete [--mlist <mach_file>] <hosts>"""
Ningning Xiac8b430d2018-05-17 15:10:25 -0700961
962 def __init__(self):
963 super(host_delete, self).__init__()
964
965 self.add_skylab_options()
966
967
968 def execute_skylab(self):
969 """Execute 'atest host delete' with '--skylab'.
970
971 @return A list of hostnames which have been successfully deleted.
972 """
973 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
974 inventory_repo.initialize()
975 data_dir = inventory_repo.get_data_dir()
976 lab = text_manager.load_lab(data_dir)
977
978 successes = []
979 for hostname in self.hosts:
980 try:
981 device.delete(
982 lab,
983 'duts',
984 hostname,
985 self.environment)
986 successes.append(hostname)
987 except device.SkylabDeviceActionError as e:
988 print('Cannot delete host %s: %s' % (hostname, e))
989
990 if successes:
991 text_manager.dump_lab(data_dir, lab)
992 message = skylab_utils.construct_commit_message(
993 'Delete %d hosts.\n\n%s' % (len(successes), successes))
994 self.change_number = inventory_repo.upload_change(
995 message, draft=self.draft, dryrun=self.dryrun,
996 submit=self.submit)
997
998 return successes
999
1000
1001 def execute(self):
1002 """Execute 'atest host delete'.
1003
1004 @return A list of hostnames which have been successfully deleted.
1005 """
1006 if self.skylab:
1007 return self.execute_skylab()
1008
1009 return super(host_delete, self).execute()