blob: 72f6dfee9d51270a205327597eaef68448d0da04 [file] [log] [blame]
mblighbe630eb2008-08-01 16:41:48 +00001#
2# Copyright 2008 Google Inc. All Rights Reserved.
3
4"""
5The host module contains the objects and method used to
6manage a host in Autotest.
7
8The valid actions are:
9create: adds host(s)
10delete: deletes host(s)
11list: lists host(s)
12stat: displays host(s) information
13mod: modifies host(s)
14jobs: lists all jobs that ran on host(s)
15
16The common options are:
17-M|--mlist: file containing a list of machines
18
19
mblighbe630eb2008-08-01 16:41:48 +000020See topic_common.py for a High Level Design and Algorithm.
21
22"""
23
mbligh6996fe82008-08-13 00:32:27 +000024import os, sys, socket
mblighbe630eb2008-08-01 16:41:48 +000025from autotest_lib.cli import topic_common, action_common
26
27
28class host(topic_common.atest):
29 """Host class
30 atest host [create|delete|list|stat|mod|jobs] <options>"""
31 usage_action = '[create|delete|list|stat|mod|jobs]'
32 topic = msg_topic = 'host'
33 msg_items = '<hosts>'
34
35
36 def __init__(self):
37 """Add to the parser the options common to all the
38 host actions"""
39 super(host, self).__init__()
40
41 self.parser.add_option('-M', '--mlist',
42 help='File listing the machines',
43 type='string',
44 default=None,
45 metavar='MACHINE_FLIST')
46
47
48 def parse(self, flists=None, req_items='hosts'):
49 """Consume the common host options"""
50 if flists:
51 flists.append(('hosts', 'mlist', '', True))
52 else:
53 flists = [('hosts', 'mlist', '', True)]
54 return self.parse_with_flist(flists, req_items)
55
56
57 def _parse_lock_options(self, options):
58 if options.lock and options.unlock:
59 self.invalid_syntax('Only specify one of '
60 '--lock and --unlock.')
61
62 if options.lock:
63 self.data['locked'] = True
64 self.messages.append('Locked host')
65 elif options.unlock:
66 self.data['locked'] = False
67 self.messages.append('Unlocked host')
68
69
70 def _cleanup_labels(self, labels, platform=None):
71 """Removes the platform label from the overall labels"""
72 if platform:
73 return [label for label in labels
74 if label != platform]
75 else:
76 try:
77 return [label for label in labels
78 if not label['platform']]
79 except TypeError:
80 # This is a hack - the server will soon
81 # do this, so all this code should be removed.
82 return labels
83
84
85 def get_items(self):
86 return self.hosts
87
88
89class host_help(host):
90 """Just here to get the atest logic working.
91 Usage is set by its parent"""
92 pass
93
94
95class host_list(action_common.atest_list, host):
96 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +000097 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +000098
99 def __init__(self):
100 super(host_list, self).__init__()
101
102 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000103 default='',
104 help='Only list hosts with all these labels '
105 '(comma separated)')
mblighbe630eb2008-08-01 16:41:48 +0000106 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000107 default='',
108 help='Only list hosts with any of these '
109 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000110 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000111 default='',
mbligh536a5242008-10-18 14:35:54 +0000112 help='Only list hosts within this ACL')
113 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000114 default='',
mbligh536a5242008-10-18 14:35:54 +0000115 help='Only list hosts available to this user')
mbligh70b9bf42008-11-18 15:00:46 +0000116 self.parser.add_option('-N', '--hostnames-only', help='Only return '
117 'hostnames for the machines queried.',
118 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000119
120
121 def parse(self):
122 """Consume the specific options"""
123 (options, leftover) = super(host_list, self).parse(req_items=None)
mbligh6444c6b2008-10-27 20:55:13 +0000124 self.labels = options.label
mblighbe630eb2008-08-01 16:41:48 +0000125 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000126 self.acl = options.acl
127 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000128 self.hostnames_only = options.hostnames_only
mblighbe630eb2008-08-01 16:41:48 +0000129 return (options, leftover)
130
131
132 def execute(self):
133 filters = {}
134 check_results = {}
135 if self.hosts:
136 filters['hostname__in'] = self.hosts
137 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000138
139 if self.labels:
140 labels = self.labels.split(',')
141 labels = [label.strip() for label in labels if label.strip()]
142
mblighf703fb42009-01-30 00:35:05 +0000143 if len(labels) == 1:
144 # This is needed for labels with wildcards (x86*)
145 filters['labels__name__in'] = labels
146 check_results['labels__name__in'] = None
147 else:
148 filters['multiple_labels'] = labels
149 check_results['multiple_labels'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000150
mblighbe630eb2008-08-01 16:41:48 +0000151 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000152 statuses = self.status.split(',')
153 statuses = [status.strip() for status in statuses
154 if status.strip()]
155
156 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000157 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000158
mbligh536a5242008-10-18 14:35:54 +0000159 if self.acl:
160 filters['acl_group__name'] = self.acl
161 check_results['acl_group__name'] = None
162 if self.user:
163 filters['acl_group__users__login'] = self.user
164 check_results['acl_group__users__login'] = None
mblighbe630eb2008-08-01 16:41:48 +0000165 return super(host_list, self).execute(op='get_hosts',
166 filters=filters,
167 check_results=check_results)
168
169
170 def output(self, results):
171 if results:
172 # Remove the platform from the labels.
173 for result in results:
174 result['labels'] = self._cleanup_labels(result['labels'],
175 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000176 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000177 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000178 else:
179 super(host_list, self).output(results, keys=['hostname', 'status',
180 'locked', 'platform', 'labels'])
mblighbe630eb2008-08-01 16:41:48 +0000181
182
183class host_stat(host):
184 """atest host stat --mlist <file>|<hosts>"""
185 usage_action = 'stat'
186
187 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000188 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000189 results = []
190 # Convert wildcards into real host stats.
191 existing_hosts = []
192 for host in self.hosts:
193 if host.endswith('*'):
194 stats = self.execute_rpc('get_hosts',
195 hostname__startswith=host.rstrip('*'))
196 if len(stats) == 0:
197 self.failure('No hosts matching %s' % host, item=host,
198 what_failed='Failed to stat')
199 continue
200 else:
201 stats = self.execute_rpc('get_hosts', hostname=host)
202 if len(stats) == 0:
203 self.failure('Unknown host %s' % host, item=host,
204 what_failed='Failed to stat')
205 continue
206 existing_hosts.extend(stats)
207
208 for stat in existing_hosts:
209 host = stat['hostname']
210 # The host exists, these should succeed
211 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
212
213 labels = self.execute_rpc('get_labels', host__hostname=host)
214 results.append ([[stat], acls, labels])
215 return results
216
217
218 def output(self, results):
219 for stats, acls, labels in results:
220 print '-'*5
221 self.print_fields(stats,
222 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000223 'status', 'locked', 'locked_by',
224 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000225 self.print_by_ids(acls, 'ACLs', line_before=True)
226 labels = self._cleanup_labels(labels)
227 self.print_by_ids(labels, 'Labels', line_before=True)
228
229
230class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000231 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000232 usage_action = 'jobs'
233
mbligh6996fe82008-08-13 00:32:27 +0000234 def __init__(self):
235 super(host_jobs, self).__init__()
236 self.parser.add_option('-q', '--max-query',
237 help='Limits the number of results '
238 '(20 by default)',
239 type='int', default=20)
240
241
242 def parse(self):
243 """Consume the specific options"""
244 (options, leftover) = super(host_jobs, self).parse(req_items=None)
245 self.max_queries = options.max_query
246 return (options, leftover)
247
248
mblighbe630eb2008-08-01 16:41:48 +0000249 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000250 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000251 results = []
252 real_hosts = []
253 for host in self.hosts:
254 if host.endswith('*'):
255 stats = self.execute_rpc('get_hosts',
256 hostname__startswith=host.rstrip('*'))
257 if len(stats) == 0:
258 self.failure('No host matching %s' % host, item=host,
259 what_failed='Failed to stat')
260 [real_hosts.append(stat['hostname']) for stat in stats]
261 else:
262 real_hosts.append(host)
263
264 for host in real_hosts:
265 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000266 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000267 query_limit=self.max_queries,
268 sort_by=['-job_id'])
mblighbe630eb2008-08-01 16:41:48 +0000269 jobs = []
270 for entry in queue_entries:
271 job = {'job_id': entry['job']['id'],
272 'job_owner': entry['job']['owner'],
273 'job_name': entry['job']['name'],
274 'status': entry['status']}
275 jobs.append(job)
276 results.append((host, jobs))
277 return results
278
279
280 def output(self, results):
281 for host, jobs in results:
282 print '-'*5
283 print 'Hostname: %s' % host
284 self.print_table(jobs, keys_header=['job_id',
285 'job_owner',
286 'job_name',
287 'status'])
288
289
290class host_mod(host):
291 """atest host mod --lock|--unlock|--ready|--dead
292 --mlist <file>|<hosts>"""
293 usage_action = 'mod'
294
295 def __init__(self):
296 """Add the options specific to the mod action"""
297 self.data = {}
298 self.messages = []
299 super(host_mod, self).__init__()
300 self.parser.add_option('-y', '--ready',
301 help='Mark this host ready',
302 action='store_true')
303 self.parser.add_option('-d', '--dead',
304 help='Mark this host dead',
305 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000306 self.parser.add_option('-l', '--lock',
307 help='Lock hosts',
308 action='store_true')
309 self.parser.add_option('-u', '--unlock',
310 help='Unlock hosts',
311 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000312 self.parser.add_option('-p', '--protection', type='choice',
313 help='Set the protection level on a host. Must '
314 'be one of: "Repair filesystem only", '
315 '"No protection", or "Do not repair"',
316 choices=('No protection', 'Do not repair',
317 'Repair filesystem only'))
mblighbe630eb2008-08-01 16:41:48 +0000318
319
320 def parse(self):
321 """Consume the specific options"""
322 (options, leftover) = super(host_mod, self).parse()
323
324 self._parse_lock_options(options)
325
326 if options.ready and options.dead:
327 self.invalid_syntax('Only specify one of '
328 '--ready and --dead')
329
330 if options.ready:
331 self.data['status'] = 'Ready'
332 self.messages.append('Set status to Ready for host')
333 elif options.dead:
334 self.data['status'] = 'Dead'
335 self.messages.append('Set status to Dead for host')
336
mblighe163b032008-10-18 14:30:27 +0000337 if options.protection:
338 self.data['protection'] = options.protection
339
mblighbe630eb2008-08-01 16:41:48 +0000340 if len(self.data) == 0:
341 self.invalid_syntax('No modification requested')
342 return (options, leftover)
343
344
345 def execute(self):
346 successes = []
347 for host in self.hosts:
mbligh6eca4602009-01-07 16:48:50 +0000348 try:
349 res = self.execute_rpc('modify_host', item=host,
350 id=host, **self.data)
351 # TODO: Make the AFE return True or False,
352 # especially for lock
mblighbe630eb2008-08-01 16:41:48 +0000353 successes.append(host)
mbligh6eca4602009-01-07 16:48:50 +0000354 except topic_common.CliError, full_error:
355 # Already logged by execute_rpc()
356 pass
357
mblighbe630eb2008-08-01 16:41:48 +0000358 return successes
359
360
361 def output(self, hosts):
362 for msg in self.messages:
363 self.print_wrapped(msg, hosts)
364
365
366
367class host_create(host):
368 """atest host create [--lock|--unlock --platform <arch>
369 --labels <labels>|--blist <label_file>
370 --acls <acls>|--alist <acl_file>
371 --mlist <mach_file>] <hosts>"""
372 usage_action = 'create'
373
374 def __init__(self):
375 self.messages = []
376 super(host_create, self).__init__()
377 self.parser.add_option('-l', '--lock',
378 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000379 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000380 self.parser.add_option('-u', '--unlock',
381 help='Create the hosts as '
382 'unlocked (default)',
383 action='store_true')
384 self.parser.add_option('-t', '--platform',
385 help='Sets the platform label')
386 self.parser.add_option('-b', '--labels',
387 help='Comma separated list of labels')
388 self.parser.add_option('-B', '--blist',
389 help='File listing the labels',
390 type='string',
391 metavar='LABEL_FLIST')
392 self.parser.add_option('-a', '--acls',
393 help='Comma separated list of ACLs')
394 self.parser.add_option('-A', '--alist',
395 help='File listing the acls',
396 type='string',
397 metavar='ACL_FLIST')
398
399
400 def parse(self):
401 flists = [('labels', 'blist', 'labels', False),
402 ('acls', 'alist', 'acls', False)]
403 (options, leftover) = super(host_create, self).parse(flists)
404
405 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000406 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000407 self.platform = getattr(options, 'platform', None)
408 return (options, leftover)
409
410
411 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000412 # Always add the hosts as locked to avoid the host
413 # being picked up by the scheduler before it's ACL'ed
414 self.data['locked'] = True
mblighbe630eb2008-08-01 16:41:48 +0000415 self.execute_rpc('add_host', hostname=host,
416 status="Ready", **self.data)
417
418 # Now add the platform label
419 labels = self.labels[:]
420 if self.platform:
421 labels.append(self.platform)
422 if len (labels):
423 self.execute_rpc('host_add_labels', id=host, labels=labels)
424
425
426 def execute(self):
427 # We need to check if these labels & ACLs exist,
428 # and create them if not.
429 if self.platform:
430 self.check_and_create_items('get_labels', 'add_label',
431 [self.platform],
432 platform=True)
433
434 if self.labels:
435 self.check_and_create_items('get_labels', 'add_label',
436 self.labels,
437 platform=False)
438
439 if self.acls:
440 self.check_and_create_items('get_acl_groups',
441 'add_acl_group',
442 self.acls)
443
444 success = self.site_create_hosts_hook()
445
446 if len(success):
447 for acl in self.acls:
448 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
mbligh719e14a2008-12-04 01:17:08 +0000449
450 if not self.locked:
451 for host in success:
452 self.execute_rpc('modify_host', id=host, locked=False)
mblighbe630eb2008-08-01 16:41:48 +0000453 return success
454
455
456 def site_create_hosts_hook(self):
457 success = []
458 for host in self.hosts:
459 try:
460 self._execute_add_one_host(host)
461 success.append(host)
462 except topic_common.CliError:
463 pass
464
465 return success
466
467
468 def output(self, hosts):
469 self.print_wrapped('Added host', hosts)
470
471
472class host_delete(action_common.atest_delete, host):
473 """atest host delete [--mlist <mach_file>] <hosts>"""
474 pass