blob: 626abbd5bb24ae9ac75f179cab840ed620b13bb4 [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>]
103 [--status <status1,status2>]"""
104
105 def __init__(self):
106 super(host_list, self).__init__()
107
108 self.parser.add_option('-b', '--label',
109 help='Only list hosts with this label')
110 self.parser.add_option('-s', '--status',
111 help='Only list hosts with this status')
112
113
114 def parse(self):
115 """Consume the specific options"""
116 (options, leftover) = super(host_list, self).parse(req_items=None)
117 self.label = options.label
118 self.status = options.status
119 return (options, leftover)
120
121
122 def execute(self):
123 filters = {}
124 check_results = {}
125 if self.hosts:
126 filters['hostname__in'] = self.hosts
127 check_results['hostname__in'] = 'hostname'
128 if self.label:
129 filters['labels__name'] = self.label
130 check_results['labels__name'] = None
131 if self.status:
132 filters['status__in'] = self.status.split(',')
mblighcd8eb972008-08-25 19:20:39 +0000133 check_results['status__in'] = None
mblighbe630eb2008-08-01 16:41:48 +0000134 return super(host_list, self).execute(op='get_hosts',
135 filters=filters,
136 check_results=check_results)
137
138
139 def output(self, results):
140 if results:
141 # Remove the platform from the labels.
142 for result in results:
143 result['labels'] = self._cleanup_labels(result['labels'],
144 result['platform'])
145 super(host_list, self).output(results,
146 keys=['hostname', 'status',
147 'locked', 'platform',
148 'labels'])
149
150
151class host_stat(host):
152 """atest host stat --mlist <file>|<hosts>"""
153 usage_action = 'stat'
154
155 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000156 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000157 results = []
158 # Convert wildcards into real host stats.
159 existing_hosts = []
160 for host in self.hosts:
161 if host.endswith('*'):
162 stats = self.execute_rpc('get_hosts',
163 hostname__startswith=host.rstrip('*'))
164 if len(stats) == 0:
165 self.failure('No hosts matching %s' % host, item=host,
166 what_failed='Failed to stat')
167 continue
168 else:
169 stats = self.execute_rpc('get_hosts', hostname=host)
170 if len(stats) == 0:
171 self.failure('Unknown host %s' % host, item=host,
172 what_failed='Failed to stat')
173 continue
174 existing_hosts.extend(stats)
175
176 for stat in existing_hosts:
177 host = stat['hostname']
178 # The host exists, these should succeed
179 acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
180
181 labels = self.execute_rpc('get_labels', host__hostname=host)
182 results.append ([[stat], acls, labels])
183 return results
184
185
186 def output(self, results):
187 for stats, acls, labels in results:
188 print '-'*5
189 self.print_fields(stats,
190 keys=['hostname', 'platform',
mblighe163b032008-10-18 14:30:27 +0000191 'status', 'locked', 'locked_by',
192 'lock_time', 'protection',])
mblighbe630eb2008-08-01 16:41:48 +0000193 self.print_by_ids(acls, 'ACLs', line_before=True)
194 labels = self._cleanup_labels(labels)
195 self.print_by_ids(labels, 'Labels', line_before=True)
196
197
198class host_jobs(host):
mbligh1494eca2008-08-13 21:24:22 +0000199 """atest host jobs [--max-query] --mlist <file>|<hosts>"""
mblighbe630eb2008-08-01 16:41:48 +0000200 usage_action = 'jobs'
201
mbligh6996fe82008-08-13 00:32:27 +0000202 def __init__(self):
203 super(host_jobs, self).__init__()
204 self.parser.add_option('-q', '--max-query',
205 help='Limits the number of results '
206 '(20 by default)',
207 type='int', default=20)
208
209
210 def parse(self):
211 """Consume the specific options"""
212 (options, leftover) = super(host_jobs, self).parse(req_items=None)
213 self.max_queries = options.max_query
214 return (options, leftover)
215
216
mblighbe630eb2008-08-01 16:41:48 +0000217 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000218 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000219 results = []
220 real_hosts = []
221 for host in self.hosts:
222 if host.endswith('*'):
223 stats = self.execute_rpc('get_hosts',
224 hostname__startswith=host.rstrip('*'))
225 if len(stats) == 0:
226 self.failure('No host matching %s' % host, item=host,
227 what_failed='Failed to stat')
228 [real_hosts.append(stat['hostname']) for stat in stats]
229 else:
230 real_hosts.append(host)
231
232 for host in real_hosts:
233 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000234 host__hostname=host,
mbligh1494eca2008-08-13 21:24:22 +0000235 query_limit=self.max_queries,
236 sort_by=['-job_id'])
mblighbe630eb2008-08-01 16:41:48 +0000237 jobs = []
238 for entry in queue_entries:
239 job = {'job_id': entry['job']['id'],
240 'job_owner': entry['job']['owner'],
241 'job_name': entry['job']['name'],
242 'status': entry['status']}
243 jobs.append(job)
244 results.append((host, jobs))
245 return results
246
247
248 def output(self, results):
249 for host, jobs in results:
250 print '-'*5
251 print 'Hostname: %s' % host
252 self.print_table(jobs, keys_header=['job_id',
253 'job_owner',
254 'job_name',
255 'status'])
256
257
258class host_mod(host):
259 """atest host mod --lock|--unlock|--ready|--dead
260 --mlist <file>|<hosts>"""
261 usage_action = 'mod'
262
263 def __init__(self):
264 """Add the options specific to the mod action"""
265 self.data = {}
266 self.messages = []
267 super(host_mod, self).__init__()
268 self.parser.add_option('-y', '--ready',
269 help='Mark this host ready',
270 action='store_true')
271 self.parser.add_option('-d', '--dead',
272 help='Mark this host dead',
273 action='store_true')
mblighbe630eb2008-08-01 16:41:48 +0000274 self.parser.add_option('-l', '--lock',
275 help='Lock hosts',
276 action='store_true')
277 self.parser.add_option('-u', '--unlock',
278 help='Unlock hosts',
279 action='store_true')
mblighe163b032008-10-18 14:30:27 +0000280 self.parser.add_option('-p', '--protection', type='choice',
281 help='Set the protection level on a host. Must '
282 'be one of: "Repair filesystem only", '
283 '"No protection", or "Do not repair"',
284 choices=('No protection', 'Do not repair',
285 'Repair filesystem only'))
mblighbe630eb2008-08-01 16:41:48 +0000286
287
288 def parse(self):
289 """Consume the specific options"""
290 (options, leftover) = super(host_mod, self).parse()
291
292 self._parse_lock_options(options)
293
294 if options.ready and options.dead:
295 self.invalid_syntax('Only specify one of '
296 '--ready and --dead')
297
298 if options.ready:
299 self.data['status'] = 'Ready'
300 self.messages.append('Set status to Ready for host')
301 elif options.dead:
302 self.data['status'] = 'Dead'
303 self.messages.append('Set status to Dead for host')
304
mblighe163b032008-10-18 14:30:27 +0000305 if options.protection:
306 self.data['protection'] = options.protection
307
mblighbe630eb2008-08-01 16:41:48 +0000308 if len(self.data) == 0:
309 self.invalid_syntax('No modification requested')
310 return (options, leftover)
311
312
313 def execute(self):
314 successes = []
315 for host in self.hosts:
316 res = self.execute_rpc('modify_host', id=host, **self.data)
317 # TODO: Make the AFE return True or False,
318 # especially for lock
319 if res is None:
320 successes.append(host)
321 else:
322 self.invalid_arg("Unknown host %s" % host)
323 return successes
324
325
326 def output(self, hosts):
327 for msg in self.messages:
328 self.print_wrapped(msg, hosts)
329
330
331
332class host_create(host):
333 """atest host create [--lock|--unlock --platform <arch>
334 --labels <labels>|--blist <label_file>
335 --acls <acls>|--alist <acl_file>
336 --mlist <mach_file>] <hosts>"""
337 usage_action = 'create'
338
339 def __init__(self):
340 self.messages = []
341 super(host_create, self).__init__()
342 self.parser.add_option('-l', '--lock',
343 help='Create the hosts as locked',
344 action='store_true')
345 self.parser.add_option('-u', '--unlock',
346 help='Create the hosts as '
347 'unlocked (default)',
348 action='store_true')
349 self.parser.add_option('-t', '--platform',
350 help='Sets the platform label')
351 self.parser.add_option('-b', '--labels',
352 help='Comma separated list of labels')
353 self.parser.add_option('-B', '--blist',
354 help='File listing the labels',
355 type='string',
356 metavar='LABEL_FLIST')
357 self.parser.add_option('-a', '--acls',
358 help='Comma separated list of ACLs')
359 self.parser.add_option('-A', '--alist',
360 help='File listing the acls',
361 type='string',
362 metavar='ACL_FLIST')
363
364
365 def parse(self):
366 flists = [('labels', 'blist', 'labels', False),
367 ('acls', 'alist', 'acls', False)]
368 (options, leftover) = super(host_create, self).parse(flists)
369
370 self._parse_lock_options(options)
371 self.platform = getattr(options, 'platform', None)
372 return (options, leftover)
373
374
375 def _execute_add_one_host(self, host):
376 self.execute_rpc('add_host', hostname=host,
377 status="Ready", **self.data)
378
379 # Now add the platform label
380 labels = self.labels[:]
381 if self.platform:
382 labels.append(self.platform)
383 if len (labels):
384 self.execute_rpc('host_add_labels', id=host, labels=labels)
385
386
387 def execute(self):
388 # We need to check if these labels & ACLs exist,
389 # and create them if not.
390 if self.platform:
391 self.check_and_create_items('get_labels', 'add_label',
392 [self.platform],
393 platform=True)
394
395 if self.labels:
396 self.check_and_create_items('get_labels', 'add_label',
397 self.labels,
398 platform=False)
399
400 if self.acls:
401 self.check_and_create_items('get_acl_groups',
402 'add_acl_group',
403 self.acls)
404
405 success = self.site_create_hosts_hook()
406
407 if len(success):
408 for acl in self.acls:
409 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
410 return success
411
412
413 def site_create_hosts_hook(self):
414 success = []
415 for host in self.hosts:
416 try:
417 self._execute_add_one_host(host)
418 success.append(host)
419 except topic_common.CliError:
420 pass
421
422 return success
423
424
425 def output(self, hosts):
426 self.print_wrapped('Added host', hosts)
427
428
429class host_delete(action_common.atest_delete, host):
430 """atest host delete [--mlist <mach_file>] <hosts>"""
431 pass