2 # Copyright 2008 Google Inc. All Rights Reserved.
5 The host module contains the objects and method used to
6 manage a host in Autotest.
10 delete: deletes host(s)
12 stat: displays host(s) information
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.
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
):
31 atest host [create|delete|list|stat|mod|jobs] <options>"""
32 usage_action
= '[create|delete|list|stat|mod|jobs]'
33 topic
= msg_topic
= 'host'
36 protections
= host_protections
.Protection
.names
40 """Add to the parser the options common to all the
42 super(host
, self
).__init
__()
44 self
.parser
.add_option('-M', '--mlist',
45 help='File listing the machines',
48 metavar
='MACHINE_FLIST')
50 self
.topic_parse_info
= topic_common
.item_parse_info(
51 attribute_name
='hosts',
52 filename_option
='mlist',
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.')
62 self
.data
['locked'] = True
63 self
.messages
.append('Locked host')
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"""
72 return [label
for label
in labels
76 return [label
for label
in labels
77 if not label
['platform']]
79 # This is a hack - the server will soon
80 # do this, so all this code should be removed.
88 class host_help(host
):
89 """Just here to get the atest logic working.
90 Usage is set by its parent"""
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>]"""
99 super(host_list
, self
).__init
__()
101 self
.parser
.add_option('-b', '--label',
103 help='Only list hosts with all these labels '
105 self
.parser
.add_option('-s', '--status',
107 help='Only list hosts with any of these '
108 'statuses (comma separated)')
109 self
.parser
.add_option('-a', '--acl',
111 help='Only list hosts within this ACL')
112 self
.parser
.add_option('-u', '--user',
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.',
118 self
.parser
.add_option('--locked',
120 help='Only list locked hosts',
122 self
.parser
.add_option('--unlocked',
124 help='Only list unlocked hosts',
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
)
153 filters
['hostname__in'] = self
.hosts
154 check_results
['hostname__in'] = 'hostname'
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
162 filters
['multiple_labels'] = self
.labels
163 check_results
['multiple_labels'] = None
166 statuses
= self
.status
.split(',')
167 statuses
= [status
.strip() for status
in statuses
170 filters
['status__in'] = statuses
171 check_results
['status__in'] = None
174 filters
['aclgroup__name'] = self
.acl
175 check_results
['aclgroup__name'] = None
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',
186 check_results
=check_results
)
189 def output(self
, results
):
191 # Remove the platform from the labels.
192 for result
in results
:
193 result
['labels'] = self
._cleanup
_labels
(result
['labels'],
195 if self
.hostnames_only
:
196 self
.print_list(results
, key
='hostname')
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'
208 # Convert wildcards into real host stats.
210 for host
in self
.hosts
:
211 if host
.endswith('*'):
212 stats
= self
.execute_rpc('get_hosts',
213 hostname__startswith
=host
.rstrip('*'))
215 self
.failure('No hosts matching %s' % host
, item
=host
,
216 what_failed
='Failed to stat')
219 stats
= self
.execute_rpc('get_hosts', hostname
=host
)
221 self
.failure('Unknown host %s' % host
, item
=host
,
222 what_failed
='Failed to stat')
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
])
236 def output(self
, results
):
237 for stats
, acls
, labels
in results
:
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'
253 super(host_jobs
, self
).__init
__()
254 self
.parser
.add_option('-q', '--max-query',
255 help='Limits the number of results '
257 type='int', default
=20)
261 """Consume the specific options"""
262 (options
, leftover
) = super(host_jobs
, self
).parse()
263 self
.max_queries
= options
.max_query
264 return (options
, leftover
)
270 for host
in self
.hosts
:
271 if host
.endswith('*'):
272 stats
= self
.execute_rpc('get_hosts',
273 hostname__startswith
=host
.rstrip('*'))
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
]
279 real_hosts
.append(host
)
281 for host
in real_hosts
:
282 queue_entries
= self
.execute_rpc('get_host_queue_entries',
284 query_limit
=self
.max_queries
,
285 sort_by
=['-job__id'])
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']}
293 results
.append((host
, jobs
))
297 def output(self
, results
):
298 for host
, jobs
in results
:
300 print 'Hostname: %s' % host
301 self
.print_table(jobs
, keys_header
=['job_id',
307 class host_mod(host
):
308 """atest host mod --lock|--unlock|--protection
309 --mlist <file>|<hosts>"""
313 """Add the options specific to the mod action"""
316 super(host_mod
, self
).__init
__()
317 self
.parser
.add_option('-l', '--lock',
320 self
.parser
.add_option('-u', '--unlock',
323 self
.parser
.add_option('-p', '--protection', type='choice',
324 help=('Set the protection level on a host. '
325 'Must be one of: %s' %
327 for p
in self
.protections
)),
328 choices
=self
.protections
)
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
)
348 for host
in self
.hosts
:
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()
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'
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)',
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',
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',
400 self
.parser
.add_option('-p', '--protection', type='choice',
401 help=('Set the protection level on a host. '
402 'Must be one of: %s' %
404 for p
in self
.protections
)),
405 choices
=self
.protections
)
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
,
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
[:]
438 labels
.append(self
.platform
)
440 self
.execute_rpc('host_add_labels', id=host
, labels
=labels
)
444 # We need to check if these labels & ACLs exist,
445 # and create them if not.
447 self
.check_and_create_items('get_labels', 'add_label',
452 self
.check_and_create_items('get_labels', 'add_label',
457 self
.check_and_create_items('get_acl_groups',
461 success
= self
.site_create_hosts_hook()
464 for acl
in self
.acls
:
465 self
.execute_rpc('acl_group_add_hosts', id=acl
, hosts
=success
)
469 self
.execute_rpc('modify_host', id=host
, locked
=False)
473 def site_create_hosts_hook(self
):
475 for host
in self
.hosts
:
477 self
._execute
_add
_one
_host
(host
)
479 except topic_common
.CliError
:
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>"""