blob: 36009b3b1f5584ba7c70ddeec18b79aab6c3a6d1 [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
143 filters['multiple_labels'] = labels
144 check_results['multiple_labels'] = None
145
mblighbe630eb2008-08-01 16:41:48 +0000146 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000147 statuses = self.status.split(',')
148 statuses = [status.strip() for status in statuses
149 if status.strip()]
150
151 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000152 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000153
mbligh536a5242008-10-18 14:35:54 +0000154 if self.acl:
155 filters['acl_group__name'] = self.acl
156 check_results['acl_group__name'] = None
157 if self.user:
158 filters['acl_group__users__login'] = self.user
159 check_results['acl_group__users__login'] = None
mblighbe630eb2008-08-01 16:41:48 +0000160 return super(host_list, self).execute(op='get_hosts',
161 filters=filters,
162 check_results=check_results)
163
164
165 def output(self, results):
166 if results:
167 # Remove the platform from the labels.
168 for result in results:
169 result['labels'] = self._cleanup_labels(result['labels'],
170 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000171 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000172 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000173 else:
174 super(host_list, self).output(results, keys=['hostname', 'status',
175 'locked', 'platform', 'labels'])
mblighbe630eb2008-08-01 16:41:48 +0000176
177
178class host_stat(host):
179 """atest host stat --mlist <file>|<hosts>"""
180 usage_action = 'stat'
181
182 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000183 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000184 results = []
185 # Convert wildcards into real host stats.
186 existing_hosts = []
187 for host in self.hosts:
188 if host.endswith('*'):
189 stats = self.execute_rpc('get_hosts',
190 hostname__startswith=host.rstrip('*'))
191 if len(stats) == 0:
192 self.failure('No hosts matching %s' % host, item=host,
193 what_failed='Failed to stat')
194 continue
195 else:
196 stats = self.execute_rpc('get_hosts', hostname=host)
197 if len(stats) == 0:
198 self.failure('Unknown host %s' % host, item=host,
199 what_failed='Failed to stat')
200 continue
201 existing_hosts.extend(stats)
202
203 for stat in existing_hosts:
204 host = stat['hostname']
205 # The host exists, these should succeed
206 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
207
208 labels = self.execute_rpc('get_labels', host__hostname=host)
209 results.append ([[stat], acls, labels])
210 return results
211
212
213 def output(self, results):
214 for stats, acls, labels in results:
215 print '-'*5
216 self.print_fields(stats,
217 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000218 'status', 'locked', 'locked_by',
219 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000220 self.print_by_ids(acls, 'ACLs', line_before=True)
221 labels = self._cleanup_labels(labels)
222 self.print_by_ids(labels, 'Labels', line_before=True)
223
224
225class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000226 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000227 usage_action = 'jobs'
228
mbligh6996fe82008-08-13 00:32:27 +0000229 def __init__(self):
230 super(host_jobs, self).__init__()
231 self.parser.add_option('-q', '--max-query',
232 help='Limits the number of results '
233 '(20 by default)',
234 type='int', default=20)
235
236
237 def parse(self):
238 """Consume the specific options"""
239 (options, leftover) = super(host_jobs, self).parse(req_items=None)
240 self.max_queries = options.max_query
241 return (options, leftover)
242
243
mblighbe630eb2008-08-01 16:41:48 +0000244 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000245 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000246 results = []
247 real_hosts = []
248 for host in self.hosts:
249 if host.endswith('*'):
250 stats = self.execute_rpc('get_hosts',
251 hostname__startswith=host.rstrip('*'))
252 if len(stats) == 0:
253 self.failure('No host matching %s' % host, item=host,
254 what_failed='Failed to stat')
255 [real_hosts.append(stat['hostname']) for stat in stats]
256 else:
257 real_hosts.append(host)
258
259 for host in real_hosts:
260 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000261 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000262 query_limit=self.max_queries,
263 sort_by=['-job_id'])
mblighbe630eb2008-08-01 16:41:48 +0000264 jobs = []
265 for entry in queue_entries:
266 job = {'job_id': entry['job']['id'],
267 'job_owner': entry['job']['owner'],
268 'job_name': entry['job']['name'],
269 'status': entry['status']}
270 jobs.append(job)
271 results.append((host, jobs))
272 return results
273
274
275 def output(self, results):
276 for host, jobs in results:
277 print '-'*5
278 print 'Hostname: %s' % host
279 self.print_table(jobs, keys_header=['job_id',
280 'job_owner',
281 'job_name',
282 'status'])
283
284
285class host_mod(host):
286 """atest host mod --lock|--unlock|--ready|--dead
287 --mlist <file>|<hosts>"""
288 usage_action = 'mod'
289
290 def __init__(self):
291 """Add the options specific to the mod action"""
292 self.data = {}
293 self.messages = []
294 super(host_mod, self).__init__()
295 self.parser.add_option('-y', '--ready',
296 help='Mark this host ready',
297 action='store_true')
298 self.parser.add_option('-d', '--dead',
299 help='Mark this host dead',
300 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000301 self.parser.add_option('-l', '--lock',
302 help='Lock hosts',
303 action='store_true')
304 self.parser.add_option('-u', '--unlock',
305 help='Unlock hosts',
306 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000307 self.parser.add_option('-p', '--protection', type='choice',
308 help='Set the protection level on a host. Must '
309 'be one of: "Repair filesystem only", '
310 '"No protection", or "Do not repair"',
311 choices=('No protection', 'Do not repair',
312 'Repair filesystem only'))
mblighbe630eb2008-08-01 16:41:48 +0000313
314
315 def parse(self):
316 """Consume the specific options"""
317 (options, leftover) = super(host_mod, self).parse()
318
319 self._parse_lock_options(options)
320
321 if options.ready and options.dead:
322 self.invalid_syntax('Only specify one of '
323 '--ready and --dead')
324
325 if options.ready:
326 self.data['status'] = 'Ready'
327 self.messages.append('Set status to Ready for host')
328 elif options.dead:
329 self.data['status'] = 'Dead'
330 self.messages.append('Set status to Dead for host')
331
mblighe163b032008-10-18 14:30:27 +0000332 if options.protection:
333 self.data['protection'] = options.protection
334
mblighbe630eb2008-08-01 16:41:48 +0000335 if len(self.data) == 0:
336 self.invalid_syntax('No modification requested')
337 return (options, leftover)
338
339
340 def execute(self):
341 successes = []
342 for host in self.hosts:
mbligh6eca4602009-01-07 16:48:50 +0000343 try:
344 res = self.execute_rpc('modify_host', item=host,
345 id=host, **self.data)
346 # TODO: Make the AFE return True or False,
347 # especially for lock
mblighbe630eb2008-08-01 16:41:48 +0000348 successes.append(host)
mbligh6eca4602009-01-07 16:48:50 +0000349 except topic_common.CliError, full_error:
350 # Already logged by execute_rpc()
351 pass
352
mblighbe630eb2008-08-01 16:41:48 +0000353 return successes
354
355
356 def output(self, hosts):
357 for msg in self.messages:
358 self.print_wrapped(msg, hosts)
359
360
361
362class host_create(host):
363 """atest host create [--lock|--unlock --platform <arch>
364 --labels <labels>|--blist <label_file>
365 --acls <acls>|--alist <acl_file>
366 --mlist <mach_file>] <hosts>"""
367 usage_action = 'create'
368
369 def __init__(self):
370 self.messages = []
371 super(host_create, self).__init__()
372 self.parser.add_option('-l', '--lock',
373 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000374 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000375 self.parser.add_option('-u', '--unlock',
376 help='Create the hosts as '
377 'unlocked (default)',
378 action='store_true')
379 self.parser.add_option('-t', '--platform',
380 help='Sets the platform label')
381 self.parser.add_option('-b', '--labels',
382 help='Comma separated list of labels')
383 self.parser.add_option('-B', '--blist',
384 help='File listing the labels',
385 type='string',
386 metavar='LABEL_FLIST')
387 self.parser.add_option('-a', '--acls',
388 help='Comma separated list of ACLs')
389 self.parser.add_option('-A', '--alist',
390 help='File listing the acls',
391 type='string',
392 metavar='ACL_FLIST')
393
394
395 def parse(self):
396 flists = [('labels', 'blist', 'labels', False),
397 ('acls', 'alist', 'acls', False)]
398 (options, leftover) = super(host_create, self).parse(flists)
399
400 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000401 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000402 self.platform = getattr(options, 'platform', None)
403 return (options, leftover)
404
405
406 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000407 # Always add the hosts as locked to avoid the host
408 # being picked up by the scheduler before it's ACL'ed
409 self.data['locked'] = True
mblighbe630eb2008-08-01 16:41:48 +0000410 self.execute_rpc('add_host', hostname=host,
411 status="Ready", **self.data)
412
413 # Now add the platform label
414 labels = self.labels[:]
415 if self.platform:
416 labels.append(self.platform)
417 if len (labels):
418 self.execute_rpc('host_add_labels', id=host, labels=labels)
419
420
421 def execute(self):
422 # We need to check if these labels & ACLs exist,
423 # and create them if not.
424 if self.platform:
425 self.check_and_create_items('get_labels', 'add_label',
426 [self.platform],
427 platform=True)
428
429 if self.labels:
430 self.check_and_create_items('get_labels', 'add_label',
431 self.labels,
432 platform=False)
433
434 if self.acls:
435 self.check_and_create_items('get_acl_groups',
436 'add_acl_group',
437 self.acls)
438
439 success = self.site_create_hosts_hook()
440
441 if len(success):
442 for acl in self.acls:
443 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
mbligh719e14a2008-12-04 01:17:08 +0000444
445 if not self.locked:
446 for host in success:
447 self.execute_rpc('modify_host', id=host, locked=False)
mblighbe630eb2008-08-01 16:41:48 +0000448 return success
449
450
451 def site_create_hosts_hook(self):
452 success = []
453 for host in self.hosts:
454 try:
455 self._execute_add_one_host(host)
456 success.append(host)
457 except topic_common.CliError:
458 pass
459
460 return success
461
462
463 def output(self, hosts):
464 self.print_wrapped('Added host', hosts)
465
466
467class host_delete(action_common.atest_delete, host):
468 """atest host delete [--mlist <mach_file>] <hosts>"""
469 pass