1# setup (you can ignore this)
2# ###########################
3
4# a bit of setup to allow overriding rpc_interace with an RPC proxy
5# (to use RPC, we would say
6#   import rpc_client_lib
7#   rpc_interface = rpc_client_lib.get_proxy(
8#                             'http://hostname:8000/afe/server/noauth/rpc/')
9# )
10>>> if 'rpc_interface' not in globals():
11...   from autotest_lib.frontend.afe import rpc_interface, models
12...   from autotest_lib.frontend import thread_local
13...   # set up a user for us to "login" as
14...   user = models.User(login='debug_user')
15...   user.access_level = 100
16...   user.save()
17...   thread_local.set_user(user)
18...   user2 = models.User(login='showard')
19...   user2.access_level = 1
20...   user2.save()
21...
22>>> from autotest_lib.frontend.afe import model_logic
23
24# get directory of this test file; we'll need it later
25>>> import common
26>>> from autotest_lib.frontend.afe import test
27>>> import os, datetime
28>>> test_path = os.path.join(os.path.dirname(test.__file__),
29...                                          'doctests')
30>>> test_path = os.path.abspath(test_path)
31
32# disable logging
33>>> from autotest_lib.client.common_lib import logging_manager
34>>> logging_manager.logger.setLevel(100)
35
36>>> drone_set = models.DroneSet.default_drone_set_name()
37>>> if drone_set:
38...     _ = models.DroneSet.objects.create(name=drone_set)
39
40# mock up tko rpc_interface
41>>> from autotest_lib.client.common_lib.test_utils import mock
42>>> mock.mock_god().stub_function_to_return(rpc_interface.tko_rpc_interface,
43...                                         'get_status_counts',
44...                                         None)
45
46# basic interface test
47######################
48
49# echo a comment
50>>> rpc_interface.echo('test string to echo')
51'test string to echo'
52
53# basic object management
54# #######################
55
56# create a label
57>>> rpc_interface.add_label(name='test_label')
581
59
60# we can modify the label by referencing its ID...
61>>> rpc_interface.modify_label(1, kernel_config='/my/kernel/config')
62
63# ...or by referencing it's name
64>>> rpc_interface.modify_label('test_label', platform=True)
65
66# we use get_labels to retrieve object data
67>>> data = rpc_interface.get_labels(name='test_label')
68>>> data == [{'id': 1,
69...           'name': 'test_label',
70...           'platform': 1,
71...           'kernel_config': '/my/kernel/config',
72...           'only_if_needed' : False,
73...           'invalid': 0,
74...           'atomic_group': None}]
75True
76
77# get_labels return multiple matches as lists of dictionaries
78>>> rpc_interface.add_label(name='label1', platform=False)
792
80>>> rpc_interface.add_label(name='label2', platform=True)
813
82>>> rpc_interface.add_label(name='label3', platform=False)
834
84>>> data = rpc_interface.get_labels(platform=False)
85>>> data == [{'id': 2, 'name': 'label1', 'platform': 0, 'kernel_config': '',
86...           'only_if_needed': False, 'invalid': 0, 'atomic_group': None},
87...          {'id': 4, 'name': 'label3', 'platform': 0, 'kernel_config': '',
88...           'only_if_needed': False, 'invalid': 0, 'atomic_group': None}]
89True
90
91# delete_label takes an ID or a name as well
92>>> rpc_interface.delete_label(3)
93>>> rpc_interface.get_labels(name='label2')
94[]
95>>> rpc_interface.delete_label('test_label')
96>>> rpc_interface.delete_label('label1')
97>>> rpc_interface.delete_label('label3')
98>>> rpc_interface.get_labels()
99[]
100
101# all the add*, modify*, delete*, and get* methods work the same way
102# hosts...
103>>> rpc_interface.add_host(hostname='ipaj1', locked=True, lock_reason='Locked device on creation')
1041
105>>> data = rpc_interface.get_hosts()
106
107# delete the lock_time field, since that can't be reliably checked
108>>> del data[0]['lock_time']
109>>> data == [{'id': 1,
110...           'hostname': 'ipaj1',
111...           'locked': 1,
112...           'synch_id': None,
113...           'status': 'Ready',
114...           'labels': [],
115...           'acls': ['Everyone'],
116...           'platform': None,
117...           'attributes': {},
118...           'invalid': 0,
119...           'protection': 'No protection',
120...           'locked_by': 'debug_user',
121...           'dirty': True,
122...           'leased': 1,
123...           'shard': None,
124...           'lock_reason': 'Locked device on creation'}]
125True
126>>> rpc_interface.modify_host(id='ipaj1', status='Hello')
127Traceback (most recent call last):
128ValidationError: {'status': 'Host status can not be modified by the frontend.'}
129>>> rpc_interface.modify_host(id='ipaj1', hostname='ipaj1000')
130>>> rpc_interface.modify_hosts(
131...     host_filter_data={'hostname': 'ipaj1000'},
132...     update_data={'locked': False})
133>>> data = rpc_interface.get_hosts()
134>>> bool(data[0]['locked'])
135False
136
137# test already locked/unlocked failures
138>>> rpc_interface.modify_host(id='ipaj1000', locked=False)
139Traceback (most recent call last):
140ValidationError: {'locked': u'Host ipaj1000 already unlocked.'}
141>>> rpc_interface.modify_host(id='ipaj1000', locked=True, lock_reason='Locking a locked device')
142>>> try:
143...     rpc_interface.modify_host(id='ipaj1000', locked=True)
144... except model_logic.ValidationError, err:
145...     pass
146>>> assert ('locked' in err.message_dict
147...         and err.message_dict['locked'].startswith('Host ipaj1000 already locked'))
148>>> rpc_interface.delete_host(id='ipaj1000')
149>>> rpc_interface.get_hosts() == []
150True
151
152# tests...
153>>> rpc_interface.get_tests() == []
154True
155
156# profilers...
157>>> rpc_interface.add_profiler(name='oprofile')
1581
159>>> rpc_interface.modify_profiler('oprofile', description='Oh profile!')
160>>> data = rpc_interface.get_profilers()
161>>> data == [{'id': 1,
162...           'name': 'oprofile',
163...           'description': 'Oh profile!'}]
164True
165>>> rpc_interface.delete_profiler('oprofile')
166>>> rpc_interface.get_profilers() == []
167True
168
169
170# users...
171>>> data = rpc_interface.get_users(login='showard')
172>>> data == [{'id': 2,
173...           'login': 'showard',
174...           'access_level': 1,
175...           'reboot_before': 'If dirty',
176...           'reboot_after': 'Never',
177...           'drone_set': None,
178...           'show_experimental': False}]
179True
180
181# acl groups...
182# 1 ACL group already exists, named "Everyone" (ID 1)
183>>> rpc_interface.add_acl_group(name='my_group')
1842
185>>> rpc_interface.modify_acl_group('my_group', description='my new acl group')
186>>> data = rpc_interface.get_acl_groups(name='my_group')
187>>> data == [{'id': 2,
188...           'name': 'my_group',
189...           'description': 'my new acl group',
190...           'users': ['debug_user'],
191...           'hosts': []}]
192True
193>>> rpc_interface.delete_acl_group('my_group')
194>>> data = rpc_interface.get_acl_groups()
195>>> data == [{'id': 1,
196...           'name': 'Everyone',
197...           'description': '',
198...           'users': ['debug_user', 'showard'],
199...           'hosts': []}]
200True
201
202
203# managing many-to-many relationships
204# ###################################
205
206# first, create some hosts and labels to play around with
207>>> rpc_interface.add_host(hostname='host1')
2082
209>>> rpc_interface.add_host(hostname='host2')
2103
211>>> rpc_interface.add_label(name='label1')
2122
213>>> rpc_interface.add_label(name='label2', platform=True)
2143
215
216# add hosts to labels
217>>> rpc_interface.host_add_labels(id='host1', labels=['label1'])
218>>> rpc_interface.host_add_labels(id='host2', labels=['label1', 'label2'])
219
220# check labels for hosts
221>>> data = rpc_interface.get_hosts(hostname='host1')
222>>> data[0]['labels']
223[u'label1']
224>>> data = rpc_interface.get_hosts(hostname='host2')
225>>> data[0]['labels']
226[u'label1', u'label2']
227>>> data[0]['platform']
228u'label2'
229
230# check host lists for labels -- use double underscore to specify fields of
231# related objects
232>>> data = rpc_interface.get_hosts(labels__name='label1')
233>>> [host['hostname'] for host in data]
234[u'host1', u'host2']
235>>> data = rpc_interface.get_hosts(labels__name='label2')
236>>> [host['hostname'] for host in data]
237[u'host2']
238
239# remove a host from a label
240>>> rpc_interface.host_remove_labels(id='host2', labels=['label2'])
241>>> data = rpc_interface.get_hosts(hostname='host1')
242>>> data[0]['labels']
243[u'label1']
244>>> rpc_interface.get_hosts(labels__name='label2')
245[]
246
247# Cleanup
248>>> rpc_interface.host_remove_labels(id='host2', labels=['label1'])
249>>> rpc_interface.host_remove_labels(id='host1', labels=['label1'])
250
251
252# Other interface for new CLI
253# add hosts to labels
254>>> rpc_interface.label_add_hosts(id='label1', hosts=['host1'])
255>>> rpc_interface.label_add_hosts(id='label2', hosts=['host1', 'host2'])
256
257# check labels for hosts
258>>> data = rpc_interface.get_hosts(hostname='host1')
259>>> data[0]['labels']
260[u'label1', u'label2']
261>>> data = rpc_interface.get_hosts(hostname='host2')
262>>> data[0]['labels']
263[u'label2']
264>>> data[0]['platform']
265u'label2'
266
267# check host lists for labels -- use double underscore to specify fields of
268# related objects
269>>> data = rpc_interface.get_hosts(labels__name='label1')
270>>> [host['hostname'] for host in data]
271[u'host1']
272>>> data = rpc_interface.get_hosts(labels__name='label2')
273>>> [host['hostname'] for host in data]
274[u'host1', u'host2']
275
276# remove a host from a label
277>>> rpc_interface.label_remove_hosts(id='label2', hosts=['host2'])
278>>> data = rpc_interface.get_hosts(hostname='host1')
279>>> data[0]['labels']
280[u'label1', u'label2']
281>>> data = rpc_interface.get_hosts(labels__name='label2')
282>>> [host['hostname'] for host in data]
283[u'host1']
284
285# Remove multiple hosts from a label
286>>> rpc_interface.label_add_hosts(id='label2', hosts=['host2'])
287>>> data = rpc_interface.get_hosts(labels__name='label2')
288>>> [host['hostname'] for host in data]
289[u'host1', u'host2']
290>>> rpc_interface.label_remove_hosts(id='label2', hosts=['host2', 'host1'])
291>>> rpc_interface.get_hosts(labels__name='label2')
292[]
293
294
295# ACL group relationships work similarly
296# note that all users are a member of 'Everyone' by default, and that hosts are
297# automatically made a member of 'Everyone' only when they are a member of no
298# other group
299>>> data = rpc_interface.get_acl_groups(hosts__hostname='host1')
300>>> [acl_group['name'] for acl_group in data]
301[u'Everyone']
302
303>>> rpc_interface.add_acl_group(name='my_group')
3042
305
306>>> rpc_interface.acl_group_add_users('my_group', ['showard'])
307>>> rpc_interface.acl_group_add_hosts('my_group', ['host1'])
308>>> data = rpc_interface.get_acl_groups(name='my_group')
309>>> data[0]['users']
310[u'debug_user', u'showard']
311>>> data[0]['hosts']
312[u'host1']
313>>> data = rpc_interface.get_acl_groups(users__login='showard')
314>>> [acl_group['name'] for acl_group in data]
315[u'Everyone', u'my_group']
316
317# note host has been automatically removed from 'Everyone'
318>>> data = rpc_interface.get_acl_groups(hosts__hostname='host1')
319>>> [acl_group['name'] for acl_group in data]
320[u'my_group']
321
322>>> rpc_interface.acl_group_remove_users('my_group', ['showard'])
323>>> rpc_interface.acl_group_remove_hosts('my_group', ['host1'])
324>>> data = rpc_interface.get_acl_groups(name='my_group')
325>>> data[0]['users'], data[0]['hosts']
326([u'debug_user'], [])
327>>> data = rpc_interface.get_acl_groups(users__login='showard')
328>>> [acl_group['name'] for acl_group in data]
329[u'Everyone']
330
331# note host has been automatically added back to 'Everyone'
332>>> data = rpc_interface.get_acl_groups(hosts__hostname='host1')
333>>> [acl_group['name'] for acl_group in data]
334[u'Everyone']
335
336
337# host attributes
338
339>>> rpc_interface.set_host_attribute('color', 'red', hostname='host1')
340>>> data = rpc_interface.get_hosts(hostname='host1')
341>>> data[0]['attributes']
342{u'color': u'red'}
343
344>>> rpc_interface.set_host_attribute('color', None, hostname='host1')
345>>> data = rpc_interface.get_hosts(hostname='host1')
346>>> data[0]['attributes']
347{}
348
349
350# host bulk modify
351##################
352
353>>> rpc_interface.modify_hosts(
354...     host_filter_data={'hostname__in': ['host1', 'host2']},
355...     update_data={'locked': True, 'lock_reason': 'Locked for testing'})
356>>> data = rpc_interface.get_hosts(hostname__in=['host1', 'host2'])
357
358>>> data[0]['locked']
359True
360>>> data[1]['locked']
361True
362
363>>> rpc_interface.modify_hosts(
364...     host_filter_data={'id': 2},
365...     update_data={'locked': False})
366>>> data = rpc_interface.get_hosts(hostname__in=['host1', 'host2'])
367
368>>> data[0]['locked']
369False
370>>> data[1]['locked']
371True
372
373
374# job management
375# ############
376
377# note that job functions require job IDs to identify jobs, since job names are
378# not unique
379
380# add some entries to play with
381>>> rpc_interface.add_label(name='my_label', kernel_config='my_kernel_config')
3825
383>>> rpc_interface.add_host(hostname='my_label_host1')
3844
385>>> rpc_interface.add_host(hostname='my_label_host2')
3865
387>>> rpc_interface.label_add_hosts(id='my_label', hosts=['my_label_host1', 'my_label_host2'])
388
389# generate a control file from existing body text.
390>>> cf_info_pi = rpc_interface.generate_control_file(
391...     client_control_file='print "Hi"\n')
392>>> print cf_info_pi['control_file'] #doctest: +NORMALIZE_WHITESPACE
393def step_init():
394    job.next_step('step0')
395def step0():
396    print "Hi"
397    return locals()
398
399# create a job to run on host1, host2, and any two machines in my_label
400>>> rpc_interface.create_job(name='my_job',
401...                          priority=10,
402...                          control_file=cf_info_pi['control_file'],
403...                          control_type='Client',
404...                          hosts=['host1', 'host2'],
405...                          meta_hosts=['my_label', 'my_label'])
4061
407
408# get job info - this does not include status info for particular hosts
409>>> data = rpc_interface.get_jobs()
410>>> data = data[0]
411>>> data['id'], data['owner'], data['name'], data['priority']
412(1, u'debug_user', u'my_job', 10)
413>>> data['control_file'] == cf_info_pi['control_file']
414True
415>>> data['control_type']
416'Client'
417
418>>> today = datetime.date.today()
419>>> data['created_on'].startswith(
420...         '%d-%02d-%02d' % (today.year, today.month, today.day))
421True
422
423# get_num_jobs - useful when dealing with large numbers of jobs
424>>> rpc_interface.get_num_jobs(name='my_job')
4251
426
427# check host queue entries for a job
428>>> data = rpc_interface.get_host_queue_entries(job=1)
429>>> len(data)
4304
431
432# get rid of created_on, it's nondeterministic
433>>> data[0]['job']['created_on'] = data[2]['job']['created_on'] = None
434
435# get_host_queue_entries returns full info about the job within each queue entry
436>>> job = data[0]['job']
437>>> job == {'control_file': cf_info_pi['control_file'], # the control file we used
438...         'control_type': 'Client',
439...         'created_on': None,
440...         'id': 1,
441...         'name': 'my_job',
442...         'owner': 'debug_user',
443...         'priority': 10,
444...         'synch_count': 0,
445...         'timeout': 24,
446...         'timeout_mins': 1440,
447...         'max_runtime_mins': 1440,
448...         'max_runtime_hrs' : 72,
449...         'run_verify': False,
450...         'run_reset': True,
451...         'email_list': '',
452...         'reboot_before': 'If dirty',
453...         'reboot_after': 'Never',
454...         'parse_failed_repair': True,
455...         'drone_set': drone_set,
456...         'parameterized_job': None,
457...         'test_retry': 0,
458...         'parent_job': None,
459...         'shard': None,
460...         'require_ssp': None}
461True
462
463# get_host_queue_entries returns a lot of data, so let's only check a couple
464>>> data[0] == (
465... {'active': 0,
466...  'complete': 0,
467...  'host': {'hostname': 'host1', # full host info here
468...           'id': 2,
469...           'invalid': 0,
470...           'locked': 0,
471...           'status': 'Ready',
472...           'synch_id': None,
473...           'protection': 'No protection',
474...           'locked_by': None,
475...           'lock_time': None,
476...           'lock_reason': 'Locked for testing',
477...           'dirty': True,
478...           'leased': 1,
479...           'shard': None},
480...  'id': 1,
481...  'job': job, # full job info here
482...  'meta_host': None,
483...  'status': 'Queued',
484...  'deleted': 0,
485...  'execution_subdir': '',
486...  'atomic_group': None,
487...  'aborted': False,
488...  'started_on': None,
489...  'finished_on': None,
490...  'full_status': 'Queued'})
491True
492>>> data[2] == (
493... {'active': 0,
494...  'complete': 0,
495...  'host': None,
496...  'id': 3,
497...  'job': job,
498...  'meta_host': 'my_label',
499...  'status': 'Queued',
500...  'deleted': 0,
501...  'execution_subdir': '',
502...  'atomic_group': None,
503...  'aborted': False,
504...  'started_on': None,
505...  'finished_on': None,
506...  'full_status': 'Queued'})
507True
508>>> rpc_interface.get_num_host_queue_entries(job=1)
5094
510>>> rpc_interface.get_hqe_percentage_complete(job=1)
5110.0
512
513# get_jobs_summary adds status counts to the rest of the get_jobs info
514>>> data = rpc_interface.get_jobs_summary()
515>>> counts = data[0]['status_counts']
516>>> counts
517{u'Queued': 4}
518
519# abort the job
520>>> data = rpc_interface.abort_host_queue_entries(job__id=1)
521>>> data = rpc_interface.get_jobs_summary(id=1)
522>>> data[0]['status_counts']
523{u'Aborted (Queued)': 4}
524
525# Remove the two hosts in my_label
526>>> rpc_interface.delete_host(id='my_label_host1')
527>>> rpc_interface.delete_host(id='my_label_host2')
528
529
530# extra querying parameters
531# #########################
532
533# get_* methods can take query_start and query_limit arguments to implement
534# paging and a sort_by argument to specify the sort column
535>>> data = rpc_interface.get_hosts(query_limit=1)
536>>> [host['hostname'] for host in data]
537[u'host1']
538>>> data = rpc_interface.get_hosts(query_start=1, query_limit=1)
539>>> [host['hostname'] for host in data]
540[u'host2']
541
542# sort_by = ['-hostname'] indicates sorting in descending order by hostname
543>>> data = rpc_interface.get_hosts(sort_by=['-hostname'])
544>>> [host['hostname'] for host in data]
545[u'host2', u'host1']
546
547
548# cloning a job
549# #############
550
551>>> job_id = rpc_interface.create_job(name='my_job_to_clone',
552...                                   priority=50,
553...                                   control_file=cf_info_pi['control_file'],
554...                                   control_type='Client',
555...                                   hosts=['host2'],
556...                                   synch_count=1)
557>>> info = rpc_interface.get_info_for_clone(job_id, False)
558>>> info['meta_host_counts']
559{}
560>>> info['job']['dependencies']
561[]
562>>> info['job']['priority']
56350
564
565
566# advanced usage
567# ##############
568
569# synch_count
570>>> job_id = rpc_interface.create_job(name='my_job',
571...                          priority=10,
572...                          control_file=cf_info_pi['control_file'],
573...                          control_type='Server',
574...                          synch_count=2,
575...                          hosts=['host1', 'host2'])
576
577>>> data = rpc_interface.get_jobs(id=job_id)
578>>> data[0]['synch_count']
5792
580
581# get hosts ACL'd to a user
582>>> hosts = rpc_interface.get_hosts(aclgroup__users__login='debug_user')
583>>> sorted([host['hostname'] for host in hosts])
584[u'host1', u'host2']
585
586>>> rpc_interface.add_acl_group(name='mygroup')
5873
588>>> rpc_interface.acl_group_add_users('mygroup', ['debug_user'])
589>>> rpc_interface.acl_group_add_hosts('mygroup', ['host1'])
590>>> data = rpc_interface.get_acl_groups(name='Everyone')[0]
591>>> data['users'], data['hosts']
592([u'debug_user', u'showard'], [u'host2'])
593>>> data = rpc_interface.get_acl_groups(name='mygroup')[0]
594>>> data['users'], data['hosts']
595([u'debug_user'], [u'host1'])
596
597>>> hosts = rpc_interface.get_hosts(aclgroup__users__login='debug_user')
598>>> sorted([host['hostname'] for host in hosts])
599[u'host1', u'host2']
600>>> hosts = rpc_interface.get_hosts(aclgroup__users__login='showard')
601>>> [host['hostname'] for host in hosts]
602[u'host2']
603
604>>> rpc_interface.delete_acl_group('mygroup')
605>>> data = rpc_interface.get_acl_groups(name='Everyone')[0]
606>>> sorted(data['hosts'])
607[u'host1', u'host2']
608