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') 58 1 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}] 75 True 76 77 # get_labels return multiple matches as lists of dictionaries 78 >>> rpc_interface.add_label(name='label1', platform=False) 79 2 80 >>> rpc_interface.add_label(name='label2', platform=True) 81 3 82 >>> rpc_interface.add_label(name='label3', platform=False) 83 4 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}] 89 True 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') 104 1 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'}] 125 True 126 >>> rpc_interface.modify_host(id='ipaj1', status='Hello') 127 Traceback (most recent call last): 128 ValidationError: {'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']) 135 False 136 137 # test already locked/unlocked failures 138 >>> rpc_interface.modify_host(id='ipaj1000', locked=False) 139 Traceback (most recent call last): 140 ValidationError: {'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() == [] 150 True 151 152 # tests... 153 >>> rpc_interface.get_tests() == [] 154 True 155 156 # profilers... 157 >>> rpc_interface.add_profiler(name='oprofile') 158 1 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!'}] 164 True 165 >>> rpc_interface.delete_profiler('oprofile') 166 >>> rpc_interface.get_profilers() == [] 167 True 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}] 179 True 180 181 # acl groups... 182 # 1 ACL group already exists, named "Everyone" (ID 1) 183 >>> rpc_interface.add_acl_group(name='my_group') 184 2 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': []}] 192 True 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': []}] 200 True 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') 208 2 209 >>> rpc_interface.add_host(hostname='host2') 210 3 211 >>> rpc_interface.add_label(name='label1') 212 2 213 >>> rpc_interface.add_label(name='label2', platform=True) 214 3 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'] 228 u'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'] 265 u'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') 304 2 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'] 359 True 360 >>> data[1]['locked'] 361 True 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'] 369 False 370 >>> data[1]['locked'] 371 True 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') 382 5 383 >>> rpc_interface.add_host(hostname='my_label_host1') 384 4 385 >>> rpc_interface.add_host(hostname='my_label_host2') 386 5 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 393 def step_init(): 394 job.next_step('step0') 395 def 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']) 406 1 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'] 414 True 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)) 421 True 422 423 # get_num_jobs - useful when dealing with large numbers of jobs 424 >>> rpc_interface.get_num_jobs(name='my_job') 425 1 426 427 # check host queue entries for a job 428 >>> data = rpc_interface.get_host_queue_entries(job=1) 429 >>> len(data) 430 4 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} 461 True 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'}) 491 True 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'}) 507 True 508 >>> rpc_interface.get_num_host_queue_entries(job=1) 509 4 510 >>> rpc_interface.get_hqe_percentage_complete(job=1) 511 0.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'] 563 50 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'] 579 2 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') 587 3 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