rpc_interface.py revision 3bb499f74c04acec1f802a531cdfcba8f5ac0164
1"""\ 2Functions to expose over the RPC interface. 3 4For all modify* and delete* functions that ask for an 'id' parameter to 5identify the object to operate on, the id may be either 6 * the database row ID 7 * the name of the object (label name, hostname, user login, etc.) 8 * a dictionary containing uniquely identifying field (this option should seldom 9 be used) 10 11When specifying foreign key fields (i.e. adding hosts to a label, or adding 12users to an ACL group), the given value may be either the database row ID or the 13name of the object. 14 15All get* functions return lists of dictionaries. Each dictionary represents one 16object and maps field names to values. 17 18Some examples: 19modify_host(2, hostname='myhost') # modify hostname of host with database ID 2 20modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2' 21modify_test('sleeptest', test_type='Client', params=', seconds=60') 22delete_acl_group(1) # delete by ID 23delete_acl_group('Everyone') # delete by name 24acl_group_add_users('Everyone', ['mbligh', 'showard']) 25get_jobs(owner='showard', status='Queued') 26 27See doctests/rpc_test.txt for (lots) more examples. 28""" 29 30__author__ = 'showard@google.com (Steve Howard)' 31 32import models, model_logic, control_file, rpc_utils 33from autotest_lib.client.common_lib import global_config 34 35 36# labels 37 38def add_label(name, kernel_config=None, platform=None): 39 return models.Label.add_object(name=name, kernel_config=kernel_config, 40 platform=platform).id 41 42 43def modify_label(id, **data): 44 models.Label.smart_get(id).update_object(data) 45 46 47def delete_label(id): 48 models.Label.smart_get(id).delete() 49 50 51def label_add_hosts(id, hosts): 52 host_objs = [models.Host.smart_get(host) for host in hosts] 53 models.Label.smart_get(id).host_set.add(*host_objs) 54 55 56def label_remove_hosts(id, hosts): 57 host_objs = [models.Host.smart_get(host) for host in hosts] 58 models.Label.smart_get(id).host_set.remove(*host_objs) 59 60 61def get_labels(**filter_data): 62 return rpc_utils.prepare_for_serialization( 63 models.Label.list_objects(filter_data)) 64 65 66# hosts 67 68def add_host(hostname, status=None, locked=None): 69 return models.Host.add_object(hostname=hostname, status=status, 70 locked=locked).id 71 72 73def modify_host(id, **data): 74 models.Host.smart_get(id).update_object(data) 75 76 77def host_add_labels(id, labels): 78 labels = [models.Label.smart_get(label) for label in labels] 79 models.Host.smart_get(id).labels.add(*labels) 80 81 82def host_remove_labels(id, labels): 83 labels = [models.Label.smart_get(label) for label in labels] 84 models.Host.smart_get(id).labels.remove(*labels) 85 86 87def delete_host(id): 88 models.Host.smart_get(id).delete() 89 90 91def get_hosts(multiple_labels=[], **filter_data): 92 """\ 93 multiple_labels: match hosts in all of the labels given. Should be a 94 list of label names. 95 """ 96 filter_data['extra_args'] = ( 97 rpc_utils.extra_host_filters(multiple_labels)) 98 hosts = models.Host.list_objects(filter_data) 99 for host in hosts: 100 host_obj = models.Host.objects.get(id=host['id']) 101 host['labels'] = [label.name 102 for label in host_obj.labels.all()] 103 platform = host_obj.platform() 104 host['platform'] = platform and platform.name or None 105 return rpc_utils.prepare_for_serialization(hosts) 106 107 108def get_num_hosts(multiple_labels=[], **filter_data): 109 filter_data['extra_args'] = ( 110 rpc_utils.extra_host_filters(multiple_labels)) 111 return models.Host.query_count(filter_data) 112 113 114# tests 115 116def add_test(name, test_type, path, test_class=None, description=None): 117 return models.Test.add_object(name=name, test_type=test_type, path=path, 118 test_class=test_class, 119 description=description).id 120 121 122def modify_test(id, **data): 123 models.Test.smart_get(id).update_object(data) 124 125 126def delete_test(id): 127 models.Test.smart_get(id).delete() 128 129 130def get_tests(**filter_data): 131 return rpc_utils.prepare_for_serialization( 132 models.Test.list_objects(filter_data)) 133 134 135# profilers 136 137def add_profiler(name, description=None): 138 return models.Profiler.add_object(name=name, description=description).id 139 140 141def modify_profiler(id, **data): 142 models.Profiler.smart_get(id).update_object(data) 143 144 145def delete_profiler(id): 146 models.Profiler.smart_get(id).delete() 147 148 149def get_profilers(**filter_data): 150 return rpc_utils.prepare_for_serialization( 151 models.Profiler.list_objects(filter_data)) 152 153 154# users 155 156def add_user(login, access_level=None): 157 return models.User.add_object(login=login, access_level=access_level).id 158 159 160def modify_user(id, **data): 161 models.User.smart_get(id).update_object(data) 162 163 164def delete_user(id): 165 models.User.smart_get(id).delete() 166 167 168def get_users(**filter_data): 169 return rpc_utils.prepare_for_serialization( 170 models.User.list_objects(filter_data)) 171 172 173# acl groups 174 175def add_acl_group(name, description=None): 176 return models.AclGroup.add_object(name=name, description=description).id 177 178 179def modify_acl_group(id, **data): 180 models.AclGroup.smart_get(id).update_object(data) 181 182 183def acl_group_add_users(id, users): 184 users = [models.User.smart_get(user) for user in users] 185 group = models.AclGroup.smart_get(id) 186 group.users.add(*users) 187 188 189def acl_group_remove_users(id, users): 190 users = [models.User.smart_get(user) for user in users] 191 group = models.AclGroup.smart_get(id) 192 group.users.remove(*users) 193 194 195def acl_group_add_hosts(id, hosts): 196 hosts = [models.Host.smart_get(host) for host in hosts] 197 group = models.AclGroup.smart_get(id) 198 group.hosts.add(*hosts) 199 group.on_host_membership_change() 200 201 202def acl_group_remove_hosts(id, hosts): 203 hosts = [models.Host.smart_get(host) for host in hosts] 204 group = models.AclGroup.smart_get(id) 205 group.hosts.remove(*hosts) 206 group.on_host_membership_change() 207 208 209def delete_acl_group(id): 210 models.AclGroup.smart_get(id).delete() 211 212 213def get_acl_groups(**filter_data): 214 acl_groups = models.AclGroup.list_objects(filter_data) 215 for acl_group in acl_groups: 216 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id']) 217 acl_group['users'] = [user.login 218 for user in acl_group_obj.users.all()] 219 acl_group['hosts'] = [host.hostname 220 for host in acl_group_obj.hosts.all()] 221 return rpc_utils.prepare_for_serialization(acl_groups) 222 223 224# jobs 225 226def generate_control_file(tests, kernel=None, label=None, profilers=[]): 227 """\ 228 Generates a client-side control file to load a kernel and run a set of 229 tests. Returns a tuple (control_file, is_server, is_synchronous): 230 control_file - the control file text 231 is_server - is the control file a server-side control file? 232 is_synchronous - should the control file be run synchronously? 233 234 tests: list of tests to run 235 kernel: kernel to install in generated control file 236 label: name of label to grab kernel config from 237 profilers: list of profilers to activate during the job 238 """ 239 if not tests: 240 return '', False, False 241 242 is_server, is_synchronous, test_objects, profiler_objects, label = ( 243 rpc_utils.prepare_generate_control_file(tests, kernel, label, 244 profilers)) 245 cf_text = control_file.generate_control(tests=test_objects, kernel=kernel, 246 platform=label, 247 profilers=profiler_objects, 248 is_server=is_server) 249 return cf_text, is_server, is_synchronous 250 251 252def create_job(name, priority, control_file, control_type, timeout=None, 253 is_synchronous=None, hosts=None, meta_hosts=None): 254 """\ 255 Create and enqueue a job. 256 257 priority: Low, Medium, High, Urgent 258 control_file: contents of control file 259 control_type: type of control file, Client or Server 260 is_synchronous: boolean indicating if a job is synchronous 261 hosts: list of hosts to run job on 262 meta_hosts: list where each entry is a label name, and for each entry 263 one host will be chosen from that label to run the job 264 on. 265 timeout: hours until job times out 266 """ 267 268 if timeout is None: 269 timeout=global_config.global_config.get_config_value( 270 'AUTOTEST_WEB', 'job_timeout_default') 271 272 owner = rpc_utils.get_user().login 273 # input validation 274 if not hosts and not meta_hosts: 275 raise model_logic.ValidationError({ 276 'arguments' : "You must pass at least one of 'hosts' or " 277 "'meta_hosts'" 278 }) 279 280 requested_host_counts = {} 281 282 # convert hostnames & meta hosts to host/label objects 283 host_objects = [] 284 for host in hosts or []: 285 this_host = models.Host.smart_get(host) 286 host_objects.append(this_host) 287 for label in meta_hosts or []: 288 this_label = models.Label.smart_get(label) 289 host_objects.append(this_label) 290 requested_host_counts.setdefault(this_label.name, 0) 291 requested_host_counts[this_label.name] += 1 292 293 # check that each metahost request has enough hosts under the label 294 if meta_hosts: 295 labels = models.Label.query_objects( 296 {'name__in': requested_host_counts.keys()}) 297 for label in labels: 298 count = label.host_set.count() 299 if requested_host_counts[label.name] > count: 300 error = ("You have requested %d %s's, but there are only %d." 301 % (requested_host_counts[label.name], 302 label.name, count)) 303 raise model_logic.ValidationError({'arguments' : error}) 304 305 # default is_synchronous to some appropriate value 306 ControlType = models.Job.ControlType 307 control_type = ControlType.get_value(control_type) 308 if is_synchronous is None: 309 is_synchronous = (control_type == ControlType.SERVER) 310 # convert the synch flag to an actual type 311 if is_synchronous: 312 synch_type = models.Test.SynchType.SYNCHRONOUS 313 else: 314 synch_type = models.Test.SynchType.ASYNCHRONOUS 315 316 job = models.Job.create(owner=owner, name=name, priority=priority, 317 control_file=control_file, 318 control_type=control_type, 319 synch_type=synch_type, 320 hosts=host_objects, 321 timeout=timeout) 322 job.queue(host_objects) 323 return job.id 324 325 326def requeue_job(id): 327 """\ 328 Create and enqueue a copy of the given job. 329 """ 330 job = models.Job.objects.get(id=id) 331 new_job = job.requeue(rpc_utils.get_user().login) 332 return new_job.id 333 334 335def abort_job(id): 336 """\ 337 Abort the job with the given id number. 338 """ 339 job = models.Job.objects.get(id=id) 340 job.abort() 341 342 343def get_jobs(not_yet_run=False, running=False, finished=False, **filter_data): 344 """\ 345 Extra filter args for get_jobs: 346 -not_yet_run: Include only jobs that have not yet started running. 347 -running: Include only jobs that have start running but for which not 348 all hosts have completed. 349 -finished: Include only jobs for which all hosts have completed (or 350 aborted). 351 At most one of these three fields should be specified. 352 """ 353 filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run, 354 running, 355 finished) 356 return rpc_utils.prepare_for_serialization( 357 models.Job.list_objects(filter_data)) 358 359 360def get_num_jobs(not_yet_run=False, running=False, finished=False, 361 **filter_data): 362 """\ 363 See get_jobs() for documentation of extra filter parameters. 364 """ 365 filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run, 366 running, 367 finished) 368 return models.Job.query_count(filter_data) 369 370 371def get_jobs_summary(**filter_data): 372 """\ 373 Like get_jobs(), but adds a 'stauts_counts' field, which is a dictionary 374 mapping status strings to the number of hosts currently with that 375 status, i.e. {'Queued' : 4, 'Running' : 2}. 376 """ 377 jobs = get_jobs(**filter_data) 378 ids = [job['id'] for job in jobs] 379 all_status_counts = models.Job.objects.get_status_counts(ids) 380 for job in jobs: 381 job['status_counts'] = all_status_counts[job['id']] 382 return rpc_utils.prepare_for_serialization(jobs) 383 384 385# host queue entries 386 387def get_host_queue_entries(**filter_data): 388 """\ 389 TODO 390 """ 391 query = models.HostQueueEntry.query_objects(filter_data) 392 all_dicts = [] 393 for queue_entry in query.select_related(): 394 entry_dict = queue_entry.get_object_dict() 395 if entry_dict['host'] is not None: 396 entry_dict['host'] = queue_entry.host.get_object_dict() 397 entry_dict['job'] = queue_entry.job.get_object_dict() 398 all_dicts.append(entry_dict) 399 return rpc_utils.prepare_for_serialization(all_dicts) 400 401 402def get_num_host_queue_entries(**filter_data): 403 """\ 404 Get the number of host queue entries associated with this job. 405 """ 406 return models.HostQueueEntry.query_count(filter_data) 407 408 409# other 410 411def get_static_data(): 412 """\ 413 Returns a dictionary containing a bunch of data that shouldn't change 414 often and is otherwise inaccessible. This includes: 415 priorities: list of job priority choices 416 default_priority: default priority value for new jobs 417 users: sorted list of all users 418 labels: sorted list of all labels 419 tests: sorted list of all tests 420 profilers: sorted list of all profilers 421 user_login: logged-in username 422 host_statuses: sorted list of possible Host statuses 423 job_statuses: sorted list of possible HostQueueEntry statuses 424 """ 425 result = {} 426 result['priorities'] = models.Job.Priority.choices() 427 default_priority = models.Job.get_field_dict()['priority'].default 428 default_string = models.Job.Priority.get_string(default_priority) 429 result['default_priority'] = default_string 430 result['users'] = get_users(sort_by=['login']) 431 result['labels'] = get_labels(sort_by=['-platform', 'name']) 432 result['tests'] = get_tests(sort_by=['name']) 433 result['profilers'] = get_profilers(sort_by=['name']) 434 result['user_login'] = rpc_utils.get_user().login 435 result['host_statuses'] = sorted(models.Host.Status.names) 436 result['job_statuses'] = sorted(models.Job.Status.names) 437 result['job_timeout_default'] = global_config.global_config.get_config_value( 438 'AUTOTEST_WEB', 'job_timeout_default') 439 return result 440