KVM test: add VM.verify_alive() and Monitor.verify_responsive()
[autotest-zwu.git] / cli / host.py
blob46013717cfd79173296278e61faaf80694c13540
2 # Copyright 2008 Google Inc. All Rights Reserved.
4 """
5 The host module contains the objects and method used to
6 manage a host in Autotest.
8 The valid actions are:
9 create: adds host(s)
10 delete: deletes host(s)
11 list: lists host(s)
12 stat: displays host(s) information
13 mod: modifies host(s)
14 jobs: lists all jobs that ran on host(s)
16 The common options are:
17 -M|--mlist: file containing a list of machines
20 See topic_common.py for a High Level Design and Algorithm.
22 """
24 import os, sys, socket
25 from autotest_lib.cli import topic_common, action_common
26 from autotest_lib.client.common_lib import host_protections
29 class host(topic_common.atest):
30 """Host class
31 atest host [create|delete|list|stat|mod|jobs] <options>"""
32 usage_action = '[create|delete|list|stat|mod|jobs]'
33 topic = msg_topic = 'host'
34 msg_items = '<hosts>'
36 protections = host_protections.Protection.names
39 def __init__(self):
40 """Add to the parser the options common to all the
41 host actions"""
42 super(host, self).__init__()
44 self.parser.add_option('-M', '--mlist',
45 help='File listing the machines',
46 type='string',
47 default=None,
48 metavar='MACHINE_FLIST')
50 self.topic_parse_info = topic_common.item_parse_info(
51 attribute_name='hosts',
52 filename_option='mlist',
53 use_leftover=True)
56 def _parse_lock_options(self, options):
57 if options.lock and options.unlock:
58 self.invalid_syntax('Only specify one of '
59 '--lock and --unlock.')
61 if options.lock:
62 self.data['locked'] = True
63 self.messages.append('Locked host')
64 elif options.unlock:
65 self.data['locked'] = False
66 self.messages.append('Unlocked host')
69 def _cleanup_labels(self, labels, platform=None):
70 """Removes the platform label from the overall labels"""
71 if platform:
72 return [label for label in labels
73 if label != platform]
74 else:
75 try:
76 return [label for label in labels
77 if not label['platform']]
78 except TypeError:
79 # This is a hack - the server will soon
80 # do this, so all this code should be removed.
81 return labels
84 def get_items(self):
85 return self.hosts
88 class host_help(host):
89 """Just here to get the atest logic working.
90 Usage is set by its parent"""
91 pass
94 class host_list(action_common.atest_list, host):
95 """atest host list [--mlist <file>|<hosts>] [--label <label>]
96 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
98 def __init__(self):
99 super(host_list, self).__init__()
101 self.parser.add_option('-b', '--label',
102 default='',
103 help='Only list hosts with all these labels '
104 '(comma separated)')
105 self.parser.add_option('-s', '--status',
106 default='',
107 help='Only list hosts with any of these '
108 'statuses (comma separated)')
109 self.parser.add_option('-a', '--acl',
110 default='',
111 help='Only list hosts within this ACL')
112 self.parser.add_option('-u', '--user',
113 default='',
114 help='Only list hosts available to this user')
115 self.parser.add_option('-N', '--hostnames-only', help='Only return '
116 'hostnames for the machines queried.',
117 action='store_true')
118 self.parser.add_option('--locked',
119 default=False,
120 help='Only list locked hosts',
121 action='store_true')
122 self.parser.add_option('--unlocked',
123 default=False,
124 help='Only list unlocked hosts',
125 action='store_true')
129 def parse(self):
130 """Consume the specific options"""
131 label_info = topic_common.item_parse_info(attribute_name='labels',
132 inline_option='label')
134 (options, leftover) = super(host_list, self).parse([label_info])
136 self.status = options.status
137 self.acl = options.acl
138 self.user = options.user
139 self.hostnames_only = options.hostnames_only
141 if options.locked and options.unlocked:
142 self.invalid_syntax('--locked and --unlocked are '
143 'mutually exclusive')
144 self.locked = options.locked
145 self.unlocked = options.unlocked
146 return (options, leftover)
149 def execute(self):
150 filters = {}
151 check_results = {}
152 if self.hosts:
153 filters['hostname__in'] = self.hosts
154 check_results['hostname__in'] = 'hostname'
156 if self.labels:
157 if len(self.labels) == 1:
158 # This is needed for labels with wildcards (x86*)
159 filters['labels__name__in'] = self.labels
160 check_results['labels__name__in'] = None
161 else:
162 filters['multiple_labels'] = self.labels
163 check_results['multiple_labels'] = None
165 if self.status:
166 statuses = self.status.split(',')
167 statuses = [status.strip() for status in statuses
168 if status.strip()]
170 filters['status__in'] = statuses
171 check_results['status__in'] = None
173 if self.acl:
174 filters['aclgroup__name'] = self.acl
175 check_results['aclgroup__name'] = None
176 if self.user:
177 filters['aclgroup__users__login'] = self.user
178 check_results['aclgroup__users__login'] = None
180 if self.locked or self.unlocked:
181 filters['locked'] = self.locked
182 check_results['locked'] = None
184 return super(host_list, self).execute(op='get_hosts',
185 filters=filters,
186 check_results=check_results)
189 def output(self, results):
190 if results:
191 # Remove the platform from the labels.
192 for result in results:
193 result['labels'] = self._cleanup_labels(result['labels'],
194 result['platform'])
195 if self.hostnames_only:
196 self.print_list(results, key='hostname')
197 else:
198 super(host_list, self).output(results, keys=['hostname', 'status',
199 'locked', 'platform', 'labels'])
202 class host_stat(host):
203 """atest host stat --mlist <file>|<hosts>"""
204 usage_action = 'stat'
206 def execute(self):
207 results = []
208 # Convert wildcards into real host stats.
209 existing_hosts = []
210 for host in self.hosts:
211 if host.endswith('*'):
212 stats = self.execute_rpc('get_hosts',
213 hostname__startswith=host.rstrip('*'))
214 if len(stats) == 0:
215 self.failure('No hosts matching %s' % host, item=host,
216 what_failed='Failed to stat')
217 continue
218 else:
219 stats = self.execute_rpc('get_hosts', hostname=host)
220 if len(stats) == 0:
221 self.failure('Unknown host %s' % host, item=host,
222 what_failed='Failed to stat')
223 continue
224 existing_hosts.extend(stats)
226 for stat in existing_hosts:
227 host = stat['hostname']
228 # The host exists, these should succeed
229 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
231 labels = self.execute_rpc('get_labels', host__hostname=host)
232 results.append ([[stat], acls, labels])
233 return results
236 def output(self, results):
237 for stats, acls, labels in results:
238 print '-'*5
239 self.print_fields(stats,
240 keys=['hostname', 'platform',
241 'status', 'locked', 'locked_by',
242 'lock_time', 'protection',])
243 self.print_by_ids(acls, 'ACLs', line_before=True)
244 labels = self._cleanup_labels(labels)
245 self.print_by_ids(labels, 'Labels', line_before=True)
248 class host_jobs(host):
249 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
250 usage_action = 'jobs'
252 def __init__(self):
253 super(host_jobs, self).__init__()
254 self.parser.add_option('-q', '--max-query',
255 help='Limits the number of results '
256 '(20 by default)',
257 type='int', default=20)
260 def parse(self):
261 """Consume the specific options"""
262 (options, leftover) = super(host_jobs, self).parse()
263 self.max_queries = options.max_query
264 return (options, leftover)
267 def execute(self):
268 results = []
269 real_hosts = []
270 for host in self.hosts:
271 if host.endswith('*'):
272 stats = self.execute_rpc('get_hosts',
273 hostname__startswith=host.rstrip('*'))
274 if len(stats) == 0:
275 self.failure('No host matching %s' % host, item=host,
276 what_failed='Failed to stat')
277 [real_hosts.append(stat['hostname']) for stat in stats]
278 else:
279 real_hosts.append(host)
281 for host in real_hosts:
282 queue_entries = self.execute_rpc('get_host_queue_entries',
283 host__hostname=host,
284 query_limit=self.max_queries,
285 sort_by=['-job__id'])
286 jobs = []
287 for entry in queue_entries:
288 job = {'job_id': entry['job']['id'],
289 'job_owner': entry['job']['owner'],
290 'job_name': entry['job']['name'],
291 'status': entry['status']}
292 jobs.append(job)
293 results.append((host, jobs))
294 return results
297 def output(self, results):
298 for host, jobs in results:
299 print '-'*5
300 print 'Hostname: %s' % host
301 self.print_table(jobs, keys_header=['job_id',
302 'job_owner',
303 'job_name',
304 'status'])
307 class host_mod(host):
308 """atest host mod --lock|--unlock|--protection
309 --mlist <file>|<hosts>"""
310 usage_action = 'mod'
312 def __init__(self):
313 """Add the options specific to the mod action"""
314 self.data = {}
315 self.messages = []
316 super(host_mod, self).__init__()
317 self.parser.add_option('-l', '--lock',
318 help='Lock hosts',
319 action='store_true')
320 self.parser.add_option('-u', '--unlock',
321 help='Unlock hosts',
322 action='store_true')
323 self.parser.add_option('-p', '--protection', type='choice',
324 help=('Set the protection level on a host. '
325 'Must be one of: %s' %
326 ', '.join('"%s"' % p
327 for p in self.protections)),
328 choices=self.protections)
331 def parse(self):
332 """Consume the specific options"""
333 (options, leftover) = super(host_mod, self).parse()
335 self._parse_lock_options(options)
337 if options.protection:
338 self.data['protection'] = options.protection
339 self.messages.append('Protection set to "%s"' % options.protection)
341 if len(self.data) == 0:
342 self.invalid_syntax('No modification requested')
343 return (options, leftover)
346 def execute(self):
347 successes = []
348 for host in self.hosts:
349 try:
350 res = self.execute_rpc('modify_host', item=host,
351 id=host, **self.data)
352 # TODO: Make the AFE return True or False,
353 # especially for lock
354 successes.append(host)
355 except topic_common.CliError, full_error:
356 # Already logged by execute_rpc()
357 pass
359 return successes
362 def output(self, hosts):
363 for msg in self.messages:
364 self.print_wrapped(msg, hosts)
368 class host_create(host):
369 """atest host create [--lock|--unlock --platform <arch>
370 --labels <labels>|--blist <label_file>
371 --acls <acls>|--alist <acl_file>
372 --protection <protection_type>
373 --mlist <mach_file>] <hosts>"""
374 usage_action = 'create'
376 def __init__(self):
377 self.messages = []
378 super(host_create, self).__init__()
379 self.parser.add_option('-l', '--lock',
380 help='Create the hosts as locked',
381 action='store_true', default=False)
382 self.parser.add_option('-u', '--unlock',
383 help='Create the hosts as '
384 'unlocked (default)',
385 action='store_true')
386 self.parser.add_option('-t', '--platform',
387 help='Sets the platform label')
388 self.parser.add_option('-b', '--labels',
389 help='Comma separated list of labels')
390 self.parser.add_option('-B', '--blist',
391 help='File listing the labels',
392 type='string',
393 metavar='LABEL_FLIST')
394 self.parser.add_option('-a', '--acls',
395 help='Comma separated list of ACLs')
396 self.parser.add_option('-A', '--alist',
397 help='File listing the acls',
398 type='string',
399 metavar='ACL_FLIST')
400 self.parser.add_option('-p', '--protection', type='choice',
401 help=('Set the protection level on a host. '
402 'Must be one of: %s' %
403 ', '.join('"%s"' % p
404 for p in self.protections)),
405 choices=self.protections)
408 def parse(self):
409 label_info = topic_common.item_parse_info(attribute_name='labels',
410 inline_option='labels',
411 filename_option='blist')
412 acl_info = topic_common.item_parse_info(attribute_name='acls',
413 inline_option='acls',
414 filename_option='alist')
416 (options, leftover) = super(host_create, self).parse([label_info,
417 acl_info],
418 req_items='hosts')
420 self._parse_lock_options(options)
421 self.locked = options.lock
422 self.platform = getattr(options, 'platform', None)
423 if options.protection:
424 self.data['protection'] = options.protection
425 return (options, leftover)
428 def _execute_add_one_host(self, host):
429 # Always add the hosts as locked to avoid the host
430 # being picked up by the scheduler before it's ACL'ed
431 self.data['locked'] = True
432 self.execute_rpc('add_host', hostname=host,
433 status="Ready", **self.data)
435 # Now add the platform label
436 labels = self.labels[:]
437 if self.platform:
438 labels.append(self.platform)
439 if len (labels):
440 self.execute_rpc('host_add_labels', id=host, labels=labels)
443 def execute(self):
444 # We need to check if these labels & ACLs exist,
445 # and create them if not.
446 if self.platform:
447 self.check_and_create_items('get_labels', 'add_label',
448 [self.platform],
449 platform=True)
451 if self.labels:
452 self.check_and_create_items('get_labels', 'add_label',
453 self.labels,
454 platform=False)
456 if self.acls:
457 self.check_and_create_items('get_acl_groups',
458 'add_acl_group',
459 self.acls)
461 success = self.site_create_hosts_hook()
463 if len(success):
464 for acl in self.acls:
465 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
467 if not self.locked:
468 for host in success:
469 self.execute_rpc('modify_host', id=host, locked=False)
470 return success
473 def site_create_hosts_hook(self):
474 success = []
475 for host in self.hosts:
476 try:
477 self._execute_add_one_host(host)
478 success.append(host)
479 except topic_common.CliError:
480 pass
482 return success
485 def output(self, hosts):
486 self.print_wrapped('Added host', hosts)
489 class host_delete(action_common.atest_delete, host):
490 """atest host delete [--mlist <mach_file>] <hosts>"""
491 pass