blob: b3ea081b22fd7b79d959db2e527cfade0bf20474 [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
20stat as has additional options:
21--lock/-l: Locks host(s)
22--unlock/-u: Unlock host(s)
23--ready/-y: Marks host(s) ready
24--dead/-d: Marks host(s) dead
25
26See topic_common.py for a High Level Design and Algorithm.
27
28"""
29
mbligh6996fe82008-08-13 00:32:27 +000030import os, sys, socket
mblighbe630eb2008-08-01 16:41:48 +000031from autotest_lib.cli import topic_common, action_common
32
33
34class host(topic_common.atest):
35 """Host class
36 atest host [create|delete|list|stat|mod|jobs] <options>"""
37 usage_action = '[create|delete|list|stat|mod|jobs]'
38 topic = msg_topic = 'host'
39 msg_items = '<hosts>'
40
41
42 def __init__(self):
43 """Add to the parser the options common to all the
44 host actions"""
45 super(host, self).__init__()
46
47 self.parser.add_option('-M', '--mlist',
48 help='File listing the machines',
49 type='string',
50 default=None,
51 metavar='MACHINE_FLIST')
52
53
54 def parse(self, flists=None, req_items='hosts'):
55 """Consume the common host options"""
56 if flists:
57 flists.append(('hosts', 'mlist', '', True))
58 else:
59 flists = [('hosts', 'mlist', '', True)]
60 return self.parse_with_flist(flists, req_items)
61
62
63 def _parse_lock_options(self, options):
64 if options.lock and options.unlock:
65 self.invalid_syntax('Only specify one of '
66 '--lock and --unlock.')
67
68 if options.lock:
69 self.data['locked'] = True
70 self.messages.append('Locked host')
71 elif options.unlock:
72 self.data['locked'] = False
73 self.messages.append('Unlocked host')
74
75
76 def _cleanup_labels(self, labels, platform=None):
77 """Removes the platform label from the overall labels"""
78 if platform:
79 return [label for label in labels
80 if label != platform]
81 else:
82 try:
83 return [label for label in labels
84 if not label['platform']]
85 except TypeError:
86 # This is a hack - the server will soon
87 # do this, so all this code should be removed.
88 return labels
89
90
91 def get_items(self):
92 return self.hosts
93
94
95class host_help(host):
96 """Just here to get the atest logic working.
97 Usage is set by its parent"""
98 pass
99
100
101class host_list(action_common.atest_list, host):
102 """atest host list [--mlist <file>|<hosts>] [--label <label>]
mbligh536a5242008-10-18 14:35:54 +0000103 [--status <status1,status2>] [--acl <ACL>] [--user <user>]"""
mblighbe630eb2008-08-01 16:41:48 +0000104
105 def __init__(self):
106 super(host_list, self).__init__()
107
108 self.parser.add_option('-b', '--label',
mbligh6444c6b2008-10-27 20:55:13 +0000109 default='',
110 help='Only list hosts with all these labels '
111 '(comma separated)')
mblighbe630eb2008-08-01 16:41:48 +0000112 self.parser.add_option('-s', '--status',
mbligh6444c6b2008-10-27 20:55:13 +0000113 default='',
114 help='Only list hosts with any of these '
115 'statuses (comma separated)')
mbligh536a5242008-10-18 14:35:54 +0000116 self.parser.add_option('-a', '--acl',
mbligh6444c6b2008-10-27 20:55:13 +0000117 default='',
mbligh536a5242008-10-18 14:35:54 +0000118 help='Only list hosts within this ACL')
119 self.parser.add_option('-u', '--user',
mbligh6444c6b2008-10-27 20:55:13 +0000120 default='',
mbligh536a5242008-10-18 14:35:54 +0000121 help='Only list hosts available to this user')
mbligh70b9bf42008-11-18 15:00:46 +0000122 self.parser.add_option('-N', '--hostnames-only', help='Only return '
123 'hostnames for the machines queried.',
124 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000125
126
127 def parse(self):
128 """Consume the specific options"""
129 (options, leftover) = super(host_list, self).parse(req_items=None)
mbligh6444c6b2008-10-27 20:55:13 +0000130 self.labels = options.label
mblighbe630eb2008-08-01 16:41:48 +0000131 self.status = options.status
mbligh536a5242008-10-18 14:35:54 +0000132 self.acl = options.acl
133 self.user = options.user
mbligh70b9bf42008-11-18 15:00:46 +0000134 self.hostnames_only = options.hostnames_only
mblighbe630eb2008-08-01 16:41:48 +0000135 return (options, leftover)
136
137
138 def execute(self):
139 filters = {}
140 check_results = {}
141 if self.hosts:
142 filters['hostname__in'] = self.hosts
143 check_results['hostname__in'] = 'hostname'
mbligh6444c6b2008-10-27 20:55:13 +0000144
145 if self.labels:
146 labels = self.labels.split(',')
147 labels = [label.strip() for label in labels if label.strip()]
148
149 filters['multiple_labels'] = labels
150 check_results['multiple_labels'] = None
151
mblighbe630eb2008-08-01 16:41:48 +0000152 if self.status:
mbligh6444c6b2008-10-27 20:55:13 +0000153 statuses = self.status.split(',')
154 statuses = [status.strip() for status in statuses
155 if status.strip()]
156
157 filters['status__in'] = statuses
mblighcd8eb972008-08-25 19:20:39 +0000158 check_results['status__in'] = None
mbligh6444c6b2008-10-27 20:55:13 +0000159
mbligh536a5242008-10-18 14:35:54 +0000160 if self.acl:
161 filters['acl_group__name'] = self.acl
162 check_results['acl_group__name'] = None
163 if self.user:
164 filters['acl_group__users__login'] = self.user
165 check_results['acl_group__users__login'] = None
mblighbe630eb2008-08-01 16:41:48 +0000166 return super(host_list, self).execute(op='get_hosts',
167 filters=filters,
168 check_results=check_results)
169
170
171 def output(self, results):
172 if results:
173 # Remove the platform from the labels.
174 for result in results:
175 result['labels'] = self._cleanup_labels(result['labels'],
176 result['platform'])
mbligh70b9bf42008-11-18 15:00:46 +0000177 if self.hostnames_only:
mblighdf75f8b2008-11-18 19:07:42 +0000178 self.print_list(results, key='hostname')
mbligh70b9bf42008-11-18 15:00:46 +0000179 else:
180 super(host_list, self).output(results, keys=['hostname', 'status',
181 'locked', 'platform', 'labels'])
mblighbe630eb2008-08-01 16:41:48 +0000182
183
184class host_stat(host):
185 """atest host stat --mlist <file>|<hosts>"""
186 usage_action = 'stat'
187
188 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000189 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000190 results = []
191 # Convert wildcards into real host stats.
192 existing_hosts = []
193 for host in self.hosts:
194 if host.endswith('*'):
195 stats = self.execute_rpc('get_hosts',
196 hostname__startswith=host.rstrip('*'))
197 if len(stats) == 0:
198 self.failure('No hosts matching %s' % host, item=host,
199 what_failed='Failed to stat')
200 continue
201 else:
202 stats = self.execute_rpc('get_hosts', hostname=host)
203 if len(stats) == 0:
204 self.failure('Unknown host %s' % host, item=host,
205 what_failed='Failed to stat')
206 continue
207 existing_hosts.extend(stats)
208
209 for stat in existing_hosts:
210 host = stat['hostname']
211 # The host exists, these should succeed
212 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
213
214 labels = self.execute_rpc('get_labels', host__hostname=host)
215 results.append ([[stat], acls, labels])
216 return results
217
218
219 def output(self, results):
220 for stats, acls, labels in results:
221 print '-'*5
222 self.print_fields(stats,
223 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000224 'status', 'locked', 'locked_by',
225 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000226 self.print_by_ids(acls, 'ACLs', line_before=True)
227 labels = self._cleanup_labels(labels)
228 self.print_by_ids(labels, 'Labels', line_before=True)
229
230
231class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000232 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000233 usage_action = 'jobs'
234
mbligh6996fe82008-08-13 00:32:27 +0000235 def __init__(self):
236 super(host_jobs, self).__init__()
237 self.parser.add_option('-q', '--max-query',
238 help='Limits the number of results '
239 '(20 by default)',
240 type='int', default=20)
241
242
243 def parse(self):
244 """Consume the specific options"""
245 (options, leftover) = super(host_jobs, self).parse(req_items=None)
246 self.max_queries = options.max_query
247 return (options, leftover)
248
249
mblighbe630eb2008-08-01 16:41:48 +0000250 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000251 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000252 results = []
253 real_hosts = []
254 for host in self.hosts:
255 if host.endswith('*'):
256 stats = self.execute_rpc('get_hosts',
257 hostname__startswith=host.rstrip('*'))
258 if len(stats) == 0:
259 self.failure('No host matching %s' % host, item=host,
260 what_failed='Failed to stat')
261 [real_hosts.append(stat['hostname']) for stat in stats]
262 else:
263 real_hosts.append(host)
264
265 for host in real_hosts:
266 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000267 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000268 query_limit=self.max_queries,
269 sort_by=['-job_id'])
mblighbe630eb2008-08-01 16:41:48 +0000270 jobs = []
271 for entry in queue_entries:
272 job = {'job_id': entry['job']['id'],
273 'job_owner': entry['job']['owner'],
274 'job_name': entry['job']['name'],
275 'status': entry['status']}
276 jobs.append(job)
277 results.append((host, jobs))
278 return results
279
280
281 def output(self, results):
282 for host, jobs in results:
283 print '-'*5
284 print 'Hostname: %s' % host
285 self.print_table(jobs, keys_header=['job_id',
286 'job_owner',
287 'job_name',
288 'status'])
289
290
291class host_mod(host):
292 """atest host mod --lock|--unlock|--ready|--dead
293 --mlist <file>|<hosts>"""
294 usage_action = 'mod'
295
296 def __init__(self):
297 """Add the options specific to the mod action"""
298 self.data = {}
299 self.messages = []
300 super(host_mod, self).__init__()
301 self.parser.add_option('-y', '--ready',
302 help='Mark this host ready',
303 action='store_true')
304 self.parser.add_option('-d', '--dead',
305 help='Mark this host dead',
306 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000307 self.parser.add_option('-l', '--lock',
308 help='Lock hosts',
309 action='store_true')
310 self.parser.add_option('-u', '--unlock',
311 help='Unlock hosts',
312 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000313 self.parser.add_option('-p', '--protection', type='choice',
314 help='Set the protection level on a host. Must '
315 'be one of: "Repair filesystem only", '
316 '"No protection", or "Do not repair"',
317 choices=('No protection', 'Do not repair',
318 'Repair filesystem only'))
mblighbe630eb2008-08-01 16:41:48 +0000319
320
321 def parse(self):
322 """Consume the specific options"""
323 (options, leftover) = super(host_mod, self).parse()
324
325 self._parse_lock_options(options)
326
327 if options.ready and options.dead:
328 self.invalid_syntax('Only specify one of '
329 '--ready and --dead')
330
331 if options.ready:
332 self.data['status'] = 'Ready'
333 self.messages.append('Set status to Ready for host')
334 elif options.dead:
335 self.data['status'] = 'Dead'
336 self.messages.append('Set status to Dead for host')
337
mblighe163b032008-10-18 14:30:27 +0000338 if options.protection:
339 self.data['protection'] = options.protection
340
mblighbe630eb2008-08-01 16:41:48 +0000341 if len(self.data) == 0:
342 self.invalid_syntax('No modification requested')
343 return (options, leftover)
344
345
346 def execute(self):
347 successes = []
348 for host in self.hosts:
349 res = self.execute_rpc('modify_host', id=host, **self.data)
350 # TODO: Make the AFE return True or False,
351 # especially for lock
352 if res is None:
353 successes.append(host)
354 else:
355 self.invalid_arg("Unknown host %s" % host)
356 return successes
357
358
359 def output(self, hosts):
360 for msg in self.messages:
361 self.print_wrapped(msg, hosts)
362
363
364
365class host_create(host):
366 """atest host create [--lock|--unlock --platform <arch>
367 --labels <labels>|--blist <label_file>
368 --acls <acls>|--alist <acl_file>
369 --mlist <mach_file>] <hosts>"""
370 usage_action = 'create'
371
372 def __init__(self):
373 self.messages = []
374 super(host_create, self).__init__()
375 self.parser.add_option('-l', '--lock',
376 help='Create the hosts as locked',
mbligh719e14a2008-12-04 01:17:08 +0000377 action='store_true', default=False)
mblighbe630eb2008-08-01 16:41:48 +0000378 self.parser.add_option('-u', '--unlock',
379 help='Create the hosts as '
380 'unlocked (default)',
381 action='store_true')
382 self.parser.add_option('-t', '--platform',
383 help='Sets the platform label')
384 self.parser.add_option('-b', '--labels',
385 help='Comma separated list of labels')
386 self.parser.add_option('-B', '--blist',
387 help='File listing the labels',
388 type='string',
389 metavar='LABEL_FLIST')
390 self.parser.add_option('-a', '--acls',
391 help='Comma separated list of ACLs')
392 self.parser.add_option('-A', '--alist',
393 help='File listing the acls',
394 type='string',
395 metavar='ACL_FLIST')
396
397
398 def parse(self):
399 flists = [('labels', 'blist', 'labels', False),
400 ('acls', 'alist', 'acls', False)]
401 (options, leftover) = super(host_create, self).parse(flists)
402
403 self._parse_lock_options(options)
mbligh719e14a2008-12-04 01:17:08 +0000404 self.locked = options.lock
mblighbe630eb2008-08-01 16:41:48 +0000405 self.platform = getattr(options, 'platform', None)
406 return (options, leftover)
407
408
409 def _execute_add_one_host(self, host):
mbligh719e14a2008-12-04 01:17:08 +0000410 # Always add the hosts as locked to avoid the host
411 # being picked up by the scheduler before it's ACL'ed
412 self.data['locked'] = True
mblighbe630eb2008-08-01 16:41:48 +0000413 self.execute_rpc('add_host', hostname=host,
414 status="Ready", **self.data)
415
416 # Now add the platform label
417 labels = self.labels[:]
418 if self.platform:
419 labels.append(self.platform)
420 if len (labels):
421 self.execute_rpc('host_add_labels', id=host, labels=labels)
422
423
424 def execute(self):
425 # We need to check if these labels & ACLs exist,
426 # and create them if not.
427 if self.platform:
428 self.check_and_create_items('get_labels', 'add_label',
429 [self.platform],
430 platform=True)
431
432 if self.labels:
433 self.check_and_create_items('get_labels', 'add_label',
434 self.labels,
435 platform=False)
436
437 if self.acls:
438 self.check_and_create_items('get_acl_groups',
439 'add_acl_group',
440 self.acls)
441
442 success = self.site_create_hosts_hook()
443
444 if len(success):
445 for acl in self.acls:
446 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
mbligh719e14a2008-12-04 01:17:08 +0000447
448 if not self.locked:
449 for host in success:
450 self.execute_rpc('modify_host', id=host, locked=False)
mblighbe630eb2008-08-01 16:41:48 +0000451 return success
452
453
454 def site_create_hosts_hook(self):
455 success = []
456 for host in self.hosts:
457 try:
458 self._execute_add_one_host(host)
459 success.append(host)
460 except topic_common.CliError:
461 pass
462
463 return success
464
465
466 def output(self, hosts):
467 self.print_wrapped('Added host', hosts)
468
469
470class host_delete(action_common.atest_delete, host):
471 """atest host delete [--mlist <mach_file>] <hosts>"""
472 pass