blob: 4e4ee891f6f64f309e32132d7893c2cea7ff3909 [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 self.lock = options.lock
73 self.unlock = options.unlock
74 self.lock_reason = options.lock_reason
Ningning Xiaef35cb52018-05-04 17:58:20 -070075
mblighbe630eb2008-08-01 16:41:48 +000076 if options.lock:
77 self.data['locked'] = True
78 self.messages.append('Locked host')
79 elif options.unlock:
80 self.data['locked'] = False
Matthew Sartori68186332015-04-27 17:19:53 -070081 self.data['lock_reason'] = ''
mblighbe630eb2008-08-01 16:41:48 +000082 self.messages.append('Unlocked host')
83
Matthew Sartori68186332015-04-27 17:19:53 -070084 if options.lock and options.lock_reason:
85 self.data['lock_reason'] = options.lock_reason
86
mblighbe630eb2008-08-01 16:41:48 +000087
88 def _cleanup_labels(self, labels, platform=None):
89 """Removes the platform label from the overall labels"""
90 if platform:
91 return [label for label in labels
92 if label != platform]
93 else:
94 try:
95 return [label for label in labels
96 if not label['platform']]
97 except TypeError:
98 # This is a hack - the server will soon
99 # do this, so all this code should be removed.
100 return labels
101
102
103 def get_items(self):
104 return self.hosts
105
106
107class host_help(host):
108 """Just here to get the atest logic working.
109 Usage is set by its parent"""
110 pass
111
112
113class host_list(action_common.atest_list, host):
114 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +0000115 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +0000116
117 def __init__(self):
118 super(host_list, self).__init__()
119
120 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000121 default='',
122 help='Only list hosts with all these labels '
Ningning Xiaea02ab12018-05-11 15:08:57 -0700123 '(comma separated). When --skylab is provided, '
124 'a label must be in the format of '
125 'label-key:label-value (e.g., board:lumpy).')
mblighbe630eb2008-08-01 16:41:48 +0000126 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000127 default='',
128 help='Only list hosts with any of these '
129 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000130 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000131 default='',
Ningning Xiaea02ab12018-05-11 15:08:57 -0700132 help=('Only list hosts within this ACL. %s' %
133 skylab_utils.MSG_INVALID_IN_SKYLAB))
mbligh536a5242008-10-18 14:35:54 +0000134 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000135 default='',
Ningning Xiaea02ab12018-05-11 15:08:57 -0700136 help=('Only list hosts available to this user. '
137 '%s' % skylab_utils.MSG_INVALID_IN_SKYLAB))
mbligh70b9bf42008-11-18 15:00:46 +0000138 self.parser.add_option('-N', '--hostnames-only', help='Only return '
139 'hostnames for the machines queried.',
140 action='store_true')
mbligh91e0efd2009-02-26 01:02:16 +0000141 self.parser.add_option('--locked',
142 default=False,
143 help='Only list locked hosts',
144 action='store_true')
145 self.parser.add_option('--unlocked',
146 default=False,
147 help='Only list unlocked hosts',
148 action='store_true')
Ningning Xiaea02ab12018-05-11 15:08:57 -0700149 self.parser.add_option('--full-output',
150 default=False,
151 help=('Print out the full content of the hosts. '
152 'Only supported with --skylab.'),
153 action='store_true',
154 dest='full_output')
mbligh91e0efd2009-02-26 01:02:16 +0000155
Ningning Xiaea02ab12018-05-11 15:08:57 -0700156 self.add_skylab_options()
mblighbe630eb2008-08-01 16:41:48 +0000157
158
159 def parse(self):
160 """Consume the specific options"""
jamesrenc2863162010-07-12 21:20:51 +0000161 label_info = topic_common.item_parse_info(attribute_name='labels',
162 inline_option='label')
163
164 (options, leftover) = super(host_list, self).parse([label_info])
165
mblighbe630eb2008-08-01 16:41:48 +0000166 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000167 self.acl = options.acl
168 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000169 self.hostnames_only = options.hostnames_only
mbligh91e0efd2009-02-26 01:02:16 +0000170
171 if options.locked and options.unlocked:
172 self.invalid_syntax('--locked and --unlocked are '
173 'mutually exclusive')
Ningning Xiaea02ab12018-05-11 15:08:57 -0700174
mbligh91e0efd2009-02-26 01:02:16 +0000175 self.locked = options.locked
176 self.unlocked = options.unlocked
Ningning Xia64ced002018-05-16 17:36:20 -0700177 self.label_map = None
Ningning Xiaea02ab12018-05-11 15:08:57 -0700178
179 if self.skylab:
180 if options.user or options.acl or options.status:
181 self.invalid_syntax('--user, --acl or --status is not '
182 'supported with --skylab.')
183 self.full_output = options.full_output
184 if self.full_output and self.hostnames_only:
185 self.invalid_syntax('--full-output is conflicted with '
186 '--hostnames-only.')
Ningning Xia64ced002018-05-16 17:36:20 -0700187
188 if self.labels:
189 self.label_map = device.convert_to_label_map(self.labels)
Ningning Xiaea02ab12018-05-11 15:08:57 -0700190 else:
191 if options.full_output:
192 self.invalid_syntax('--full_output is only supported with '
193 '--skylab.')
194
mblighbe630eb2008-08-01 16:41:48 +0000195 return (options, leftover)
196
197
Ningning Xiaea02ab12018-05-11 15:08:57 -0700198 def execute_skylab(self):
199 """Execute 'atest host list' with --skylab."""
200 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
201 inventory_repo.initialize()
202 lab = text_manager.load_lab(inventory_repo.get_data_dir())
203
204 # TODO(nxia): support filtering on run-time labels and status.
205 return device.get_devices(
206 lab,
207 'duts',
208 self.environment,
Ningning Xia64ced002018-05-16 17:36:20 -0700209 label_map=self.label_map,
Ningning Xiaea02ab12018-05-11 15:08:57 -0700210 hostnames=self.hosts,
211 locked=self.locked,
212 unlocked=self.unlocked)
213
214
mblighbe630eb2008-08-01 16:41:48 +0000215 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800216 """Execute 'atest host list'."""
Ningning Xiaea02ab12018-05-11 15:08:57 -0700217 if self.skylab:
218 return self.execute_skylab()
219
mblighbe630eb2008-08-01 16:41:48 +0000220 filters = {}
221 check_results = {}
222 if self.hosts:
223 filters['hostname__in'] = self.hosts
224 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000225
226 if self.labels:
jamesrenc2863162010-07-12 21:20:51 +0000227 if len(self.labels) == 1:
mblighf703fb42009-01-30 00:35:05 +0000228 # This is needed for labels with wildcards (x86*)
jamesrenc2863162010-07-12 21:20:51 +0000229 filters['labels__name__in'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000230 check_results['labels__name__in'] = None
231 else:
jamesrenc2863162010-07-12 21:20:51 +0000232 filters['multiple_labels'] = self.labels
mblighf703fb42009-01-30 00:35:05 +0000233 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000234
mblighbe630eb2008-08-01 16:41:48 +0000235 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000236 statuses = self.status.split(',')
237 statuses = [status.strip() for status in statuses
238 if status.strip()]
239
240 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000241 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000242
mbligh536a5242008-10-18 14:35:54 +0000243 if self.acl:
showardd9ac4452009-02-07 02:04:37 +0000244 filters['aclgroup__name'] = self.acl
245 check_results['aclgroup__name'] = None
mbligh536a5242008-10-18 14:35:54 +0000246 if self.user:
showardd9ac4452009-02-07 02:04:37 +0000247 filters['aclgroup__users__login'] = self.user
248 check_results['aclgroup__users__login'] = None
mbligh91e0efd2009-02-26 01:02:16 +0000249
250 if self.locked or self.unlocked:
251 filters['locked'] = self.locked
252 check_results['locked'] = None
253
mblighbe630eb2008-08-01 16:41:48 +0000254 return super(host_list, self).execute(op='get_hosts',
255 filters=filters,
256 check_results=check_results)
257
258
259 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800260 """Print output of 'atest host list'.
261
262 @param results: the results to be printed.
263 """
Ningning Xiaea02ab12018-05-11 15:08:57 -0700264 if results and not self.skylab:
mblighbe630eb2008-08-01 16:41:48 +0000265 # Remove the platform from the labels.
266 for result in results:
267 result['labels'] = self._cleanup_labels(result['labels'],
268 result['platform'])
Ningning Xiaea02ab12018-05-11 15:08:57 -0700269 if self.skylab and self.full_output:
270 print results
271 return
272
273 if self.skylab:
274 results = device.convert_to_autotest_hosts(results)
275
mbligh70b9bf42008-11-18 15:00:46 +0000276 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000277 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000278 else:
Ningning Xiaea02ab12018-05-11 15:08:57 -0700279 keys = ['hostname', 'status', 'shard', 'locked', 'lock_reason',
280 'locked_by', 'platform', 'labels']
Prashanth Balasubramaniana5048562014-12-12 10:14:11 -0800281 super(host_list, self).output(results, keys=keys)
mblighbe630eb2008-08-01 16:41:48 +0000282
283
284class host_stat(host):
285 """atest host stat --mlist <file>|<hosts>"""
286 usage_action = 'stat'
287
288 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800289 """Execute 'atest host stat'."""
mblighbe630eb2008-08-01 16:41:48 +0000290 results = []
291 # Convert wildcards into real host stats.
292 existing_hosts = []
293 for host in self.hosts:
294 if host.endswith('*'):
295 stats = self.execute_rpc('get_hosts',
296 hostname__startswith=host.rstrip('*'))
297 if len(stats) == 0:
298 self.failure('No hosts matching %s' % host, item=host,
299 what_failed='Failed to stat')
300 continue
301 else:
302 stats = self.execute_rpc('get_hosts', hostname=host)
303 if len(stats) == 0:
304 self.failure('Unknown host %s' % host, item=host,
305 what_failed='Failed to stat')
306 continue
307 existing_hosts.extend(stats)
308
309 for stat in existing_hosts:
310 host = stat['hostname']
311 # The host exists, these should succeed
312 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
313
314 labels = self.execute_rpc('get_labels', host__hostname=host)
Simran Basi0739d682015-02-25 16:22:56 -0800315 results.append([[stat], acls, labels, stat['attributes']])
mblighbe630eb2008-08-01 16:41:48 +0000316 return results
317
318
319 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800320 """Print output of 'atest host stat'.
321
322 @param results: the results to be printed.
323 """
Simran Basi0739d682015-02-25 16:22:56 -0800324 for stats, acls, labels, attributes in results:
mblighbe630eb2008-08-01 16:41:48 +0000325 print '-'*5
326 self.print_fields(stats,
Aviv Kesheta40d1272017-11-16 00:56:35 -0800327 keys=['hostname', 'id', 'platform',
mblighe163b032008-10-18 14:30:27 +0000328 'status', 'locked', 'locked_by',
Matthew Sartori68186332015-04-27 17:19:53 -0700329 'lock_time', 'lock_reason', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000330 self.print_by_ids(acls, 'ACLs', line_before=True)
331 labels = self._cleanup_labels(labels)
332 self.print_by_ids(labels, 'Labels', line_before=True)
Simran Basi0739d682015-02-25 16:22:56 -0800333 self.print_dict(attributes, 'Host Attributes', line_before=True)
mblighbe630eb2008-08-01 16:41:48 +0000334
335
336class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000337 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000338 usage_action = 'jobs'
339
mbligh6996fe82008-08-13 00:32:27 +0000340 def __init__(self):
341 super(host_jobs, self).__init__()
342 self.parser.add_option('-q', '--max-query',
343 help='Limits the number of results '
344 '(20 by default)',
345 type='int', default=20)
346
347
348 def parse(self):
349 """Consume the specific options"""
mbligh9deeefa2009-05-01 23:11:08 +0000350 (options, leftover) = super(host_jobs, self).parse()
mbligh6996fe82008-08-13 00:32:27 +0000351 self.max_queries = options.max_query
352 return (options, leftover)
353
354
mblighbe630eb2008-08-01 16:41:48 +0000355 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800356 """Execute 'atest host jobs'."""
mblighbe630eb2008-08-01 16:41:48 +0000357 results = []
358 real_hosts = []
359 for host in self.hosts:
360 if host.endswith('*'):
361 stats = self.execute_rpc('get_hosts',
362 hostname__startswith=host.rstrip('*'))
363 if len(stats) == 0:
364 self.failure('No host matching %s' % host, item=host,
365 what_failed='Failed to stat')
366 [real_hosts.append(stat['hostname']) for stat in stats]
367 else:
368 real_hosts.append(host)
369
370 for host in real_hosts:
371 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000372 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000373 query_limit=self.max_queries,
showard6958c722009-09-23 20:03:16 +0000374 sort_by=['-job__id'])
mblighbe630eb2008-08-01 16:41:48 +0000375 jobs = []
376 for entry in queue_entries:
377 job = {'job_id': entry['job']['id'],
378 'job_owner': entry['job']['owner'],
379 'job_name': entry['job']['name'],
380 'status': entry['status']}
381 jobs.append(job)
382 results.append((host, jobs))
383 return results
384
385
386 def output(self, results):
xixuand6011f12016-12-08 15:01:58 -0800387 """Print output of 'atest host jobs'.
388
389 @param results: the results to be printed.
390 """
mblighbe630eb2008-08-01 16:41:48 +0000391 for host, jobs in results:
392 print '-'*5
393 print 'Hostname: %s' % host
394 self.print_table(jobs, keys_header=['job_id',
showardbe0d8692009-08-20 23:42:44 +0000395 'job_owner',
396 'job_name',
397 'status'])
mblighbe630eb2008-08-01 16:41:48 +0000398
Justin Giorgi16bba562016-06-22 10:42:13 -0700399class BaseHostModCreate(host):
xixuand6011f12016-12-08 15:01:58 -0800400 """The base class for host_mod and host_create"""
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700401 # Matches one attribute=value pair
402 attribute_regex = r'(?P<attribute>\w+)=(?P<value>.+)?'
mblighbe630eb2008-08-01 16:41:48 +0000403
404 def __init__(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700405 """Add the options shared between host mod and host create actions."""
mblighbe630eb2008-08-01 16:41:48 +0000406 self.messages = []
Justin Giorgi16bba562016-06-22 10:42:13 -0700407 self.host_ids = {}
408 super(BaseHostModCreate, self).__init__()
mblighbe630eb2008-08-01 16:41:48 +0000409 self.parser.add_option('-l', '--lock',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700410 help='Lock hosts.',
mblighbe630eb2008-08-01 16:41:48 +0000411 action='store_true')
Matthew Sartori68186332015-04-27 17:19:53 -0700412 self.parser.add_option('-r', '--lock_reason',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700413 help='Reason for locking hosts.',
Matthew Sartori68186332015-04-27 17:19:53 -0700414 default='')
Ningning Xiaef35cb52018-05-04 17:58:20 -0700415 self.parser.add_option('-u', '--unlock',
416 help='Unlock hosts.',
417 action='store_true')
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700418
mblighe163b032008-10-18 14:30:27 +0000419 self.parser.add_option('-p', '--protection', type='choice',
mblighaed47e82009-03-17 19:06:18 +0000420 help=('Set the protection level on a host. '
Ningning Xiaef35cb52018-05-04 17:58:20 -0700421 'Must be one of: %s. %s' %
422 (', '.join('"%s"' % p
423 for p in self.protections),
424 skylab_utils.MSG_INVALID_IN_SKYLAB)),
mblighaed47e82009-03-17 19:06:18 +0000425 choices=self.protections)
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700426 self._attributes = []
427 self.parser.add_option('--attribute', '-i',
428 help=('Host attribute to add or change. Format '
429 'is <attribute>=<value>. Multiple '
430 'attributes can be set by passing the '
431 'argument multiple times. Attributes can '
432 'be unset by providing an empty value.'),
433 action='append')
mblighbe630eb2008-08-01 16:41:48 +0000434 self.parser.add_option('-b', '--labels',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700435 help=('Comma separated list of labels. '
436 'When --skylab is provided, a label must '
437 'be in the format of label-key:label-value'
438 ' (e.g., board:lumpy).'))
mblighbe630eb2008-08-01 16:41:48 +0000439 self.parser.add_option('-B', '--blist',
440 help='File listing the labels',
441 type='string',
442 metavar='LABEL_FLIST')
443 self.parser.add_option('-a', '--acls',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700444 help=('Comma separated list of ACLs. %s' %
445 skylab_utils.MSG_INVALID_IN_SKYLAB))
mblighbe630eb2008-08-01 16:41:48 +0000446 self.parser.add_option('-A', '--alist',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700447 help=('File listing the acls. %s' %
448 skylab_utils.MSG_INVALID_IN_SKYLAB),
mblighbe630eb2008-08-01 16:41:48 +0000449 type='string',
450 metavar='ACL_FLIST')
Justin Giorgi16bba562016-06-22 10:42:13 -0700451 self.parser.add_option('-t', '--platform',
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700452 help=('Sets the platform label. %s Please set '
453 'platform in labels (e.g., -b '
454 'platform:platform_name) with --skylab.' %
455 skylab_utils.MSG_INVALID_IN_SKYLAB))
mblighbe630eb2008-08-01 16:41:48 +0000456
457
458 def parse(self):
Justin Giorgi16bba562016-06-22 10:42:13 -0700459 """Consume the options common to host create and host mod.
460 """
mbligh9deeefa2009-05-01 23:11:08 +0000461 label_info = topic_common.item_parse_info(attribute_name='labels',
462 inline_option='labels',
463 filename_option='blist')
464 acl_info = topic_common.item_parse_info(attribute_name='acls',
465 inline_option='acls',
466 filename_option='alist')
467
Justin Giorgi16bba562016-06-22 10:42:13 -0700468 (options, leftover) = super(BaseHostModCreate, self).parse([label_info,
mbligh9deeefa2009-05-01 23:11:08 +0000469 acl_info],
470 req_items='hosts')
mblighbe630eb2008-08-01 16:41:48 +0000471
472 self._parse_lock_options(options)
Justin Giorgi16bba562016-06-22 10:42:13 -0700473
Ningning Xia64ced002018-05-16 17:36:20 -0700474 self.label_map = None
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700475 if self.allow_skylab and self.skylab:
Ningning Xiaef35cb52018-05-04 17:58:20 -0700476 # TODO(nxia): drop these flags when all hosts are migrated to skylab
Ningning Xia64ced002018-05-16 17:36:20 -0700477 if (options.protection or options.acls or options.alist or
478 options.platform):
479 self.invalid_syntax(
480 '--protection, --acls, --alist or --platform is not '
481 'supported with --skylab.')
482
483 if self.labels:
484 self.label_map = device.convert_to_label_map(self.labels)
Ningning Xiaef35cb52018-05-04 17:58:20 -0700485
mblighaed47e82009-03-17 19:06:18 +0000486 if options.protection:
487 self.data['protection'] = options.protection
Justin Giorgi16bba562016-06-22 10:42:13 -0700488 self.messages.append('Protection set to "%s"' % options.protection)
489
490 self.attributes = {}
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700491 if options.attribute:
492 for pair in options.attribute:
493 m = re.match(self.attribute_regex, pair)
494 if not m:
495 raise topic_common.CliError('Attribute must be in key=value '
496 'syntax.')
497 elif m.group('attribute') in self.attributes:
xixuand6011f12016-12-08 15:01:58 -0800498 raise topic_common.CliError(
499 'Multiple values provided for attribute '
500 '%s.' % m.group('attribute'))
Justin Giorgi9261f9f2016-07-11 17:48:52 -0700501 self.attributes[m.group('attribute')] = m.group('value')
Justin Giorgi16bba562016-06-22 10:42:13 -0700502
503 self.platform = options.platform
mblighbe630eb2008-08-01 16:41:48 +0000504 return (options, leftover)
505
506
Justin Giorgi16bba562016-06-22 10:42:13 -0700507 def _set_acls(self, hosts, acls):
508 """Add hosts to acls (and remove from all other acls).
509
510 @param hosts: list of hostnames
511 @param acls: list of acl names
512 """
513 # Remove from all ACLs except 'Everyone' and ACLs in list
514 # Skip hosts that don't exist
515 for host in hosts:
516 if host not in self.host_ids:
517 continue
518 host_id = self.host_ids[host]
519 for a in self.execute_rpc('get_acl_groups', hosts=host_id):
520 if a['name'] not in self.acls and a['id'] != 1:
521 self.execute_rpc('acl_group_remove_hosts', id=a['id'],
522 hosts=self.hosts)
523
524 # Add hosts to the ACLs
525 self.check_and_create_items('get_acl_groups', 'add_acl_group',
526 self.acls)
527 for a in acls:
528 self.execute_rpc('acl_group_add_hosts', id=a, hosts=hosts)
529
530
531 def _remove_labels(self, host, condition):
532 """Remove all labels from host that meet condition(label).
533
534 @param host: hostname
535 @param condition: callable that returns bool when given a label
536 """
537 if host in self.host_ids:
538 host_id = self.host_ids[host]
539 labels_to_remove = []
540 for l in self.execute_rpc('get_labels', host=host_id):
541 if condition(l):
542 labels_to_remove.append(l['id'])
543 if labels_to_remove:
544 self.execute_rpc('host_remove_labels', id=host_id,
545 labels=labels_to_remove)
546
547
548 def _set_labels(self, host, labels):
549 """Apply labels to host (and remove all other labels).
550
551 @param host: hostname
552 @param labels: list of label names
553 """
554 condition = lambda l: l['name'] not in labels and not l['platform']
555 self._remove_labels(host, condition)
556 self.check_and_create_items('get_labels', 'add_label', labels)
557 self.execute_rpc('host_add_labels', id=host, labels=labels)
558
559
560 def _set_platform_label(self, host, platform_label):
561 """Apply the platform label to host (and remove existing).
562
563 @param host: hostname
564 @param platform_label: platform label's name
565 """
566 self._remove_labels(host, lambda l: l['platform'])
567 self.check_and_create_items('get_labels', 'add_label', [platform_label],
568 platform=True)
569 self.execute_rpc('host_add_labels', id=host, labels=[platform_label])
570
571
572 def _set_attributes(self, host, attributes):
573 """Set attributes on host.
574
575 @param host: hostname
576 @param attributes: attribute dictionary
577 """
578 for attr, value in self.attributes.iteritems():
579 self.execute_rpc('set_host_attribute', attribute=attr,
580 value=value, hostname=host)
581
582
583class host_mod(BaseHostModCreate):
584 """atest host mod [--lock|--unlock --force_modify_locking
585 --platform <arch>
586 --labels <labels>|--blist <label_file>
587 --acls <acls>|--alist <acl_file>
588 --protection <protection_type>
589 --attributes <attr>=<value>;<attr>=<value>
590 --mlist <mach_file>] <hosts>"""
591 usage_action = 'mod'
Justin Giorgi16bba562016-06-22 10:42:13 -0700592
593 def __init__(self):
594 """Add the options specific to the mod action"""
595 super(host_mod, self).__init__()
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700596 self.parser.add_option('--unlock-lock-id',
597 help=('Unlock the lock with the lock-id. %s' %
598 skylab_utils.MSG_ONLY_VALID_IN_SKYLAB),
599 default=None)
Justin Giorgi16bba562016-06-22 10:42:13 -0700600 self.parser.add_option('-f', '--force_modify_locking',
601 help='Forcefully lock\unlock a host',
602 action='store_true')
603 self.parser.add_option('--remove_acls',
Ningning Xiaef35cb52018-05-04 17:58:20 -0700604 help=('Remove all active acls. %s' %
605 skylab_utils.MSG_INVALID_IN_SKYLAB),
Justin Giorgi16bba562016-06-22 10:42:13 -0700606 action='store_true')
607 self.parser.add_option('--remove_labels',
608 help='Remove all labels.',
609 action='store_true')
610
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700611 self.add_skylab_options()
612
613
614 def _parse_unlock_options(self, options):
615 """Parse unlock related options."""
616 if self.skylab and options.unlock and options.unlock_lock_id is None:
617 self.invalid_syntax('Must provide --unlock-lock-id with "--skylab '
618 '--unlock".')
619
620 if (not (self.skylab and options.unlock) and
621 options.unlock_lock_id is not None):
622 self.invalid_syntax('--unlock-lock-id is only valid with '
623 '"--skylab --unlock".')
624
625 self.unlock_lock_id = options.unlock_lock_id
626
Justin Giorgi16bba562016-06-22 10:42:13 -0700627
628 def parse(self):
629 """Consume the specific options"""
630 (options, leftover) = super(host_mod, self).parse()
631
Ningning Xia5c0e8f32018-05-21 16:26:50 -0700632 self._parse_unlock_options(options)
633
Justin Giorgi16bba562016-06-22 10:42:13 -0700634 if options.force_modify_locking:
635 self.data['force_modify_locking'] = True
636
Ningning Xiaef35cb52018-05-04 17:58:20 -0700637 if self.skylab and options.remove_acls:
638 # TODO(nxia): drop the flag when all hosts are migrated to skylab
639 self.invalid_syntax('--remove_acls is not supported with --skylab.')
640
Justin Giorgi16bba562016-06-22 10:42:13 -0700641 self.remove_acls = options.remove_acls
642 self.remove_labels = options.remove_labels
643
644 return (options, leftover)
645
646
Ningning Xiaef35cb52018-05-04 17:58:20 -0700647 def execute_skylab(self):
648 """Execute atest host mod with --skylab.
649
650 @return A list of hostnames which have been successfully modified.
651 """
652 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
653 inventory_repo.initialize()
654 data_dir = inventory_repo.get_data_dir()
655 lab = text_manager.load_lab(data_dir)
656
657 locked_by = None
658 if self.lock:
659 locked_by = inventory_repo.git_repo.config('user.email')
660
661 successes = []
662 for hostname in self.hosts:
663 try:
664 device.modify(
665 lab,
666 'duts',
667 hostname,
668 self.environment,
669 lock=self.lock,
670 locked_by=locked_by,
671 lock_reason = self.lock_reason,
672 unlock=self.unlock,
673 unlock_lock_id=self.unlock_lock_id,
674 attributes=self.attributes,
675 remove_labels=self.remove_labels,
Ningning Xia64ced002018-05-16 17:36:20 -0700676 label_map=self.label_map)
Ningning Xiaef35cb52018-05-04 17:58:20 -0700677 successes.append(hostname)
678 except device.SkylabDeviceActionError as e:
679 print('Cannot modify host %s: %s' % (hostname, e))
680
681 if successes:
682 text_manager.dump_lab(data_dir, lab)
683
684 status = inventory_repo.git_repo.status()
685 if not status:
686 print('Nothing is changed for hosts %s.' % successes)
687 return []
688
689 message = skylab_utils.construct_commit_message(
690 'Modify %d hosts.\n\n%s' % (len(successes), successes))
691 self.change_number = inventory_repo.upload_change(
692 message, draft=self.draft, dryrun=self.dryrun,
693 submit=self.submit)
694
695 return successes
696
697
Justin Giorgi16bba562016-06-22 10:42:13 -0700698 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800699 """Execute 'atest host mod'."""
Ningning Xiaef35cb52018-05-04 17:58:20 -0700700 if self.skylab:
701 return self.execute_skylab()
702
Justin Giorgi16bba562016-06-22 10:42:13 -0700703 successes = []
704 for host in self.execute_rpc('get_hosts', hostname__in=self.hosts):
705 self.host_ids[host['hostname']] = host['id']
706 for host in self.hosts:
707 if host not in self.host_ids:
708 self.failure('Cannot modify non-existant host %s.' % host)
709 continue
710 host_id = self.host_ids[host]
711
712 try:
713 if self.data:
714 self.execute_rpc('modify_host', item=host,
715 id=host, **self.data)
716
717 if self.attributes:
718 self._set_attributes(host, self.attributes)
719
720 if self.labels or self.remove_labels:
721 self._set_labels(host, self.labels)
722
723 if self.platform:
724 self._set_platform_label(host, self.platform)
725
726 # TODO: Make the AFE return True or False,
727 # especially for lock
728 successes.append(host)
729 except topic_common.CliError, full_error:
730 # Already logged by execute_rpc()
731 pass
732
733 if self.acls or self.remove_acls:
734 self._set_acls(self.hosts, self.acls)
735
736 return successes
737
738
739 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800740 """Print output of 'atest host mod'.
741
742 @param hosts: the host list to be printed.
743 """
Justin Giorgi16bba562016-06-22 10:42:13 -0700744 for msg in self.messages:
745 self.print_wrapped(msg, hosts)
746
Ningning Xiaef35cb52018-05-04 17:58:20 -0700747 if hosts and self.skylab:
Ningning Xiac8b430d2018-05-17 15:10:25 -0700748 print('Modified hosts: %s.' % ', '.join(hosts))
Ningning Xiaef35cb52018-05-04 17:58:20 -0700749 if self.skylab and not self.dryrun and not self.submit:
Ningning Xiac8b430d2018-05-17 15:10:25 -0700750 print(skylab_utils.get_cl_message(self.change_number))
Ningning Xiaef35cb52018-05-04 17:58:20 -0700751
Justin Giorgi16bba562016-06-22 10:42:13 -0700752
753class HostInfo(object):
754 """Store host information so we don't have to keep looking it up."""
755 def __init__(self, hostname, platform, labels):
756 self.hostname = hostname
757 self.platform = platform
758 self.labels = labels
759
760
761class host_create(BaseHostModCreate):
762 """atest host create [--lock|--unlock --platform <arch>
763 --labels <labels>|--blist <label_file>
764 --acls <acls>|--alist <acl_file>
765 --protection <protection_type>
766 --attributes <attr>=<value>;<attr>=<value>
767 --mlist <mach_file>] <hosts>"""
768 usage_action = 'create'
769
770 def parse(self):
771 """Option logic specific to create action.
772 """
773 (options, leftovers) = super(host_create, self).parse()
774 self.locked = options.lock
775 if 'serials' in self.attributes:
776 if len(self.hosts) > 1:
777 raise topic_common.CliError('Can not specify serials with '
778 'multiple hosts.')
779
780
781 @classmethod
782 def construct_without_parse(
783 cls, web_server, hosts, platform=None,
784 locked=False, lock_reason='', labels=[], acls=[],
785 protection=host_protections.Protection.NO_PROTECTION):
786 """Construct a host_create object and fill in data from args.
787
788 Do not need to call parse after the construction.
789
790 Return an object of site_host_create ready to execute.
791
792 @param web_server: A string specifies the autotest webserver url.
793 It is needed to setup comm to make rpc.
794 @param hosts: A list of hostnames as strings.
795 @param platform: A string or None.
796 @param locked: A boolean.
797 @param lock_reason: A string.
798 @param labels: A list of labels as strings.
799 @param acls: A list of acls as strings.
800 @param protection: An enum defined in host_protections.
801 """
802 obj = cls()
803 obj.web_server = web_server
804 try:
805 # Setup stuff needed for afe comm.
806 obj.afe = rpc.afe_comm(web_server)
807 except rpc.AuthError, s:
808 obj.failure(str(s), fatal=True)
809 obj.hosts = hosts
810 obj.platform = platform
811 obj.locked = locked
812 if locked and lock_reason.strip():
813 obj.data['lock_reason'] = lock_reason.strip()
814 obj.labels = labels
815 obj.acls = acls
816 if protection:
817 obj.data['protection'] = protection
818 obj.attributes = {}
819 return obj
820
821
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700822 def _detect_host_info(self, host):
823 """Detect platform and labels from the host.
824
825 @param host: hostname
826
827 @return: HostInfo object
828 """
829 # Mock an afe_host object so that the host is constructed as if the
830 # data was already in afe
831 data = {'attributes': self.attributes, 'labels': self.labels}
832 afe_host = frontend.Host(None, data)
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700833 store = host_info.InMemoryHostInfoStore(
834 host_info.HostInfo(labels=self.labels,
835 attributes=self.attributes))
836 machine = {
837 'hostname': host,
838 'afe_host': afe_host,
839 'host_info_store': store
840 }
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700841 try:
Allen Li2c32d6b2017-02-03 15:28:10 -0800842 if bin_utils.ping(host, tries=1, deadline=1) == 0:
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700843 serials = self.attributes.get('serials', '').split(',')
Richard Barnette9db80682018-04-26 00:55:15 +0000844 adb_serial = self.attributes.get('serials')
845 host_dut = hosts.create_host(machine,
846 adb_serial=adb_serial)
xixuand6011f12016-12-08 15:01:58 -0800847
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700848 info = HostInfo(host, host_dut.get_platform(),
849 host_dut.get_labels())
xixuand6011f12016-12-08 15:01:58 -0800850 # Clean host to make sure nothing left after calling it,
851 # e.g. tunnels.
852 if hasattr(host_dut, 'close'):
853 host_dut.close()
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700854 else:
855 # Can't ping the host, use default information.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700856 info = HostInfo(host, None, [])
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700857 except (socket.gaierror, error.AutoservRunError,
858 error.AutoservSSHTimeout):
859 # We may be adding a host that does not exist yet or we can't
860 # reach due to hostname/address issues or if the host is down.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700861 info = HostInfo(host, None, [])
862 return info
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700863
864
mblighbe630eb2008-08-01 16:41:48 +0000865 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000866 # Always add the hosts as locked to avoid the host
Matthew Sartori68186332015-04-27 17:19:53 -0700867 # being picked up by the scheduler before it's ACL'ed.
mbligh719e14a2008-12-04 01:17:08 +0000868 self.data['locked'] = True
Matthew Sartori68186332015-04-27 17:19:53 -0700869 if not self.locked:
870 self.data['lock_reason'] = 'Forced lock on device creation'
Justin Giorgi16bba562016-06-22 10:42:13 -0700871 self.execute_rpc('add_host', hostname=host, status="Ready", **self.data)
mblighbe630eb2008-08-01 16:41:48 +0000872
Justin Giorgi16bba562016-06-22 10:42:13 -0700873 # If there are labels avaliable for host, use them.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700874 info = self._detect_host_info(host)
Justin Giorgi16bba562016-06-22 10:42:13 -0700875 labels = set(self.labels)
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700876 if info.labels:
877 labels.update(info.labels)
Justin Giorgi16bba562016-06-22 10:42:13 -0700878
879 if labels:
880 self._set_labels(host, list(labels))
881
882 # Now add the platform label.
883 # If a platform was not provided and we were able to retrieve it
884 # from the host, use the retrieved platform.
Prathmesh Prabhud252d262017-05-31 11:46:34 -0700885 platform = self.platform if self.platform else info.platform
Justin Giorgi16bba562016-06-22 10:42:13 -0700886 if platform:
887 self._set_platform_label(host, platform)
888
889 if self.attributes:
890 self._set_attributes(host, self.attributes)
mblighbe630eb2008-08-01 16:41:48 +0000891
892
Justin Giorgi5208eaa2016-07-02 20:12:12 -0700893 def execute(self):
xixuand6011f12016-12-08 15:01:58 -0800894 """Execute 'atest host create'."""
Justin Giorgi16bba562016-06-22 10:42:13 -0700895 successful_hosts = []
896 for host in self.hosts:
897 try:
898 self._execute_add_one_host(host)
899 successful_hosts.append(host)
900 except topic_common.CliError:
901 pass
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700902
903 if successful_hosts:
Justin Giorgi16bba562016-06-22 10:42:13 -0700904 self._set_acls(successful_hosts, self.acls)
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700905
906 if not self.locked:
907 for host in successful_hosts:
Matthew Sartori68186332015-04-27 17:19:53 -0700908 self.execute_rpc('modify_host', id=host, locked=False,
909 lock_reason='')
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700910 return successful_hosts
911
912
mblighbe630eb2008-08-01 16:41:48 +0000913 def output(self, hosts):
xixuand6011f12016-12-08 15:01:58 -0800914 """Print output of 'atest host create'.
915
916 @param hosts: the added host list to be printed.
917 """
mblighbe630eb2008-08-01 16:41:48 +0000918 self.print_wrapped('Added host', hosts)
919
920
921class host_delete(action_common.atest_delete, host):
922 """atest host delete [--mlist <mach_file>] <hosts>"""
Ningning Xiac8b430d2018-05-17 15:10:25 -0700923
924 def __init__(self):
925 super(host_delete, self).__init__()
926
927 self.add_skylab_options()
928
929
930 def execute_skylab(self):
931 """Execute 'atest host delete' with '--skylab'.
932
933 @return A list of hostnames which have been successfully deleted.
934 """
935 inventory_repo = skylab_utils.InventoryRepo(self.inventory_repo_dir)
936 inventory_repo.initialize()
937 data_dir = inventory_repo.get_data_dir()
938 lab = text_manager.load_lab(data_dir)
939
940 successes = []
941 for hostname in self.hosts:
942 try:
943 device.delete(
944 lab,
945 'duts',
946 hostname,
947 self.environment)
948 successes.append(hostname)
949 except device.SkylabDeviceActionError as e:
950 print('Cannot delete host %s: %s' % (hostname, e))
951
952 if successes:
953 text_manager.dump_lab(data_dir, lab)
954 message = skylab_utils.construct_commit_message(
955 'Delete %d hosts.\n\n%s' % (len(successes), successes))
956 self.change_number = inventory_repo.upload_change(
957 message, draft=self.draft, dryrun=self.dryrun,
958 submit=self.submit)
959
960 return successes
961
962
963 def execute(self):
964 """Execute 'atest host delete'.
965
966 @return A list of hostnames which have been successfully deleted.
967 """
968 if self.skylab:
969 return self.execute_skylab()
970
971 return super(host_delete, self).execute()