blob: 06826edf4613d0ead9f58ee03c86427feefd3c52 [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(',')
133 check_results['status__in'] = 'status'
134 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',
191 'status', 'locked', 'locked_by'])
192 self.print_by_ids(acls, 'ACLs', line_before=True)
193 labels = self._cleanup_labels(labels)
194 self.print_by_ids(labels, 'Labels', line_before=True)
195
196
197class host_jobs(host):
198 """atest host jobs --mlist <file>|<hosts>"""
199 usage_action = 'jobs'
200
mbligh6996fe82008-08-13 00:32:27 +0000201 def __init__(self):
202 super(host_jobs, self).__init__()
203 self.parser.add_option('-q', '--max-query',
204 help='Limits the number of results '
205 '(20 by default)',
206 type='int', default=20)
207
208
209 def parse(self):
210 """Consume the specific options"""
211 (options, leftover) = super(host_jobs, self).parse(req_items=None)
212 self.max_queries = options.max_query
213 return (options, leftover)
214
215
mblighbe630eb2008-08-01 16:41:48 +0000216 def execute(self):
mbligh6996fe82008-08-13 00:32:27 +0000217 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT)
mblighbe630eb2008-08-01 16:41:48 +0000218 results = []
219 real_hosts = []
220 for host in self.hosts:
221 if host.endswith('*'):
222 stats = self.execute_rpc('get_hosts',
223 hostname__startswith=host.rstrip('*'))
224 if len(stats) == 0:
225 self.failure('No host matching %s' % host, item=host,
226 what_failed='Failed to stat')
227 [real_hosts.append(stat['hostname']) for stat in stats]
228 else:
229 real_hosts.append(host)
230
231 for host in real_hosts:
232 queue_entries = self.execute_rpc('get_host_queue_entries',
mbligh6996fe82008-08-13 00:32:27 +0000233 host__hostname=host,
234 query_limit=self.max_queries)
mblighbe630eb2008-08-01 16:41:48 +0000235 queue_entries.sort(key=lambda qe: qe['job']['id'])
236 queue_entries.reverse()
237 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')
274
275 self.parser.add_option('-l', '--lock',
276 help='Lock hosts',
277 action='store_true')
278 self.parser.add_option('-u', '--unlock',
279 help='Unlock hosts',
280 action='store_true')
281
282
283 def parse(self):
284 """Consume the specific options"""
285 (options, leftover) = super(host_mod, self).parse()
286
287 self._parse_lock_options(options)
288
289 if options.ready and options.dead:
290 self.invalid_syntax('Only specify one of '
291 '--ready and --dead')
292
293 if options.ready:
294 self.data['status'] = 'Ready'
295 self.messages.append('Set status to Ready for host')
296 elif options.dead:
297 self.data['status'] = 'Dead'
298 self.messages.append('Set status to Dead for host')
299
300 if len(self.data) == 0:
301 self.invalid_syntax('No modification requested')
302 return (options, leftover)
303
304
305 def execute(self):
306 successes = []
307 for host in self.hosts:
308 res = self.execute_rpc('modify_host', id=host, **self.data)
309 # TODO: Make the AFE return True or False,
310 # especially for lock
311 if res is None:
312 successes.append(host)
313 else:
314 self.invalid_arg("Unknown host %s" % host)
315 return successes
316
317
318 def output(self, hosts):
319 for msg in self.messages:
320 self.print_wrapped(msg, hosts)
321
322
323
324class host_create(host):
325 """atest host create [--lock|--unlock --platform <arch>
326 --labels <labels>|--blist <label_file>
327 --acls <acls>|--alist <acl_file>
328 --mlist <mach_file>] <hosts>"""
329 usage_action = 'create'
330
331 def __init__(self):
332 self.messages = []
333 super(host_create, self).__init__()
334 self.parser.add_option('-l', '--lock',
335 help='Create the hosts as locked',
336 action='store_true')
337 self.parser.add_option('-u', '--unlock',
338 help='Create the hosts as '
339 'unlocked (default)',
340 action='store_true')
341 self.parser.add_option('-t', '--platform',
342 help='Sets the platform label')
343 self.parser.add_option('-b', '--labels',
344 help='Comma separated list of labels')
345 self.parser.add_option('-B', '--blist',
346 help='File listing the labels',
347 type='string',
348 metavar='LABEL_FLIST')
349 self.parser.add_option('-a', '--acls',
350 help='Comma separated list of ACLs')
351 self.parser.add_option('-A', '--alist',
352 help='File listing the acls',
353 type='string',
354 metavar='ACL_FLIST')
355
356
357 def parse(self):
358 flists = [('labels', 'blist', 'labels', False),
359 ('acls', 'alist', 'acls', False)]
360 (options, leftover) = super(host_create, self).parse(flists)
361
362 self._parse_lock_options(options)
363 self.platform = getattr(options, 'platform', None)
364 return (options, leftover)
365
366
367 def _execute_add_one_host(self, host):
368 self.execute_rpc('add_host', hostname=host,
369 status="Ready", **self.data)
370
371 # Now add the platform label
372 labels = self.labels[:]
373 if self.platform:
374 labels.append(self.platform)
375 if len (labels):
376 self.execute_rpc('host_add_labels', id=host, labels=labels)
377
378
379 def execute(self):
380 # We need to check if these labels & ACLs exist,
381 # and create them if not.
382 if self.platform:
383 self.check_and_create_items('get_labels', 'add_label',
384 [self.platform],
385 platform=True)
386
387 if self.labels:
388 self.check_and_create_items('get_labels', 'add_label',
389 self.labels,
390 platform=False)
391
392 if self.acls:
393 self.check_and_create_items('get_acl_groups',
394 'add_acl_group',
395 self.acls)
396
397 success = self.site_create_hosts_hook()
398
399 if len(success):
400 for acl in self.acls:
401 self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
402 return success
403
404
405 def site_create_hosts_hook(self):
406 success = []
407 for host in self.hosts:
408 try:
409 self._execute_add_one_host(host)
410 success.append(host)
411 except topic_common.CliError:
412 pass
413
414 return success
415
416
417 def output(self, hosts):
418 self.print_wrapped('Added host', hosts)
419
420
421class host_delete(action_common.atest_delete, host):
422 """atest host delete [--mlist <mach_file>] <hosts>"""
423 pass