summaryrefslogtreecommitdiff
path: root/frontend/afe/rpc_interface.py
blob: 8015912639352fa69556bbbfca5bd5bb146216f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
"""\
Functions to expose over the RPC interface.

For all modify* and delete* functions that ask for an 'id' parameter to
identify the object to operate on, the id may be either
 * the database row ID
 * the name of the object (label name, hostname, user login, etc.)
 * a dictionary containing uniquely identifying field (this option should seldom
   be used)

When specifying foreign key fields (i.e. adding hosts to a label, or adding
users to an ACL group), the given value may be either the database row ID or the
name of the object.

All get* functions return lists of dictionaries.  Each dictionary represents one
object and maps field names to values.

Some examples:
modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
modify_test('sleeptest', test_type='Client', params=', seconds=60')
delete_acl_group(1) # delete by ID
delete_acl_group('Everyone') # delete by name
acl_group_add_users('Everyone', ['mbligh', 'showard'])
get_jobs(owner='showard', status='Queued')

See doctests/rpc_test.txt for (lots) more examples.
"""

__author__ = 'showard@google.com (Steve Howard)'

import models, control_file, rpc_utils

# labels

def add_label(name, kernel_config=None, platform=None):
    return models.Label.add_object(name=name, kernel_config=kernel_config,
                                   platform=platform).id


def modify_label(id, **data):
    models.Label.smart_get(id).update_object(data)


def delete_label(id):
    models.Label.smart_get(id).delete()


def label_add_hosts(id, hosts):
    host_objs = [models.Host.smart_get(host) for host in hosts]
    models.Label.smart_get(id).host_set.add(*host_objs)


def label_remove_hosts(id, hosts):
    host_objs = [models.Host.smart_get(host) for host in hosts]
    models.Label.smart_get(id).host_set.remove(*host_objs)


def get_labels(**filter_data):
    return rpc_utils.prepare_for_serialization(
        models.Label.list_objects(filter_data))


# hosts

def add_host(hostname, status=None, locked=None):
    return models.Host.add_object(hostname=hostname, status=status,
                                  locked=locked).id


def modify_host(id, **data):
    models.Host.smart_get(id).update_object(data)


def host_add_labels(id, labels):
    labels = [models.Label.smart_get(label) for label in labels]
    models.Host.smart_get(id).labels.add(*labels)


def host_remove_labels(id, labels):
    labels = [models.Label.smart_get(label) for label in labels]
    models.Host.smart_get(id).labels.remove(*labels)


def delete_host(id):
    models.Host.smart_get(id).delete()


def get_hosts(multiple_labels=[], **filter_data):
    """\
    multiple_labels: match hosts in all of the labels given.  Should be a
    list of label names.
    """
    filter_data['extra_args'] = (
        rpc_utils.extra_host_filters(multiple_labels))
    hosts = models.Host.list_objects(filter_data)
    for host in hosts:
        host_obj = models.Host.objects.get(id=host['id'])
        host['labels'] = [label.name
                          for label in host_obj.labels.all()]
        platform = host_obj.platform()
        host['platform'] = platform and platform.name or None
    return rpc_utils.prepare_for_serialization(hosts)


def get_num_hosts(multiple_labels=[], **filter_data):
    filter_data['extra_args'] = (
        rpc_utils.extra_host_filters(multiple_labels))
    return models.Host.query_count(filter_data)


# tests

def add_test(name, test_type, path, test_class=None, description=None):
    return models.Test.add_object(name=name, test_type=test_type, path=path,
                                  test_class=test_class,
                                  description=description).id


def modify_test(id, **data):
    models.Test.smart_get(id).update_object(data)


def delete_test(id):
    models.Test.smart_get(id).delete()


def get_tests(**filter_data):
    return rpc_utils.prepare_for_serialization(
        models.Test.list_objects(filter_data))


# users

def add_user(login, access_level=None):
    return models.User.add_object(login=login, access_level=access_level).id


def modify_user(id, **data):
    models.User.smart_get(id).update_object(data)


def delete_user(id):
    models.User.smart_get(id).delete()


def get_users(**filter_data):
    return rpc_utils.prepare_for_serialization(
        models.User.list_objects(filter_data))


# acl groups

def add_acl_group(name, description=None):
    return models.AclGroup.add_object(name=name, description=description).id


def modify_acl_group(id, **data):
    models.AclGroup.smart_get(id).update_object(data)


def acl_group_add_users(id, users):
    users = [models.User.smart_get(user) for user in users]
    group = models.AclGroup.smart_get(id)
    group.users.add(*users)


def acl_group_remove_users(id, users):
    users = [models.User.smart_get(user) for user in users]
    group = models.AclGroup.smart_get(id)
    group.users.remove(*users)


def acl_group_add_hosts(id, hosts):
    hosts = [models.Host.smart_get(host) for host in hosts]
    group = models.AclGroup.smart_get(id)
    group.hosts.add(*hosts)


def acl_group_remove_hosts(id, hosts):
    hosts = [models.Host.smart_get(host) for host in hosts]
    group = models.AclGroup.smart_get(id)
    group.hosts.remove(*hosts)


def delete_acl_group(id):
    models.AclGroup.smart_get(id).delete()


def get_acl_groups(**filter_data):
    acl_groups = models.AclGroup.list_objects(filter_data)
    for acl_group in acl_groups:
        acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
        acl_group['users'] = [user.login
                              for user in acl_group_obj.users.all()]
        acl_group['hosts'] = [host.hostname
                              for host in acl_group_obj.hosts.all()]
    return rpc_utils.prepare_for_serialization(acl_groups)


# jobs

def generate_control_file(tests, kernel=None, label=None):
    """\
    Generates a client-side control file to load a kernel and run a set of
    tests.  Returns a tuple (control_file, is_server, is_synchronous):
    control_file - the control file text
    is_server - is the control file a server-side control file?
    is_synchronous - should the control file be run synchronously?

    tests: list of tests to run
    kernel: kernel to install in generated control file
    label: name of label to grab kernel config from
    """
    if not tests:
        return '', False, False

    is_server, is_synchronous, test_objects, label = (
        rpc_utils.prepare_generate_control_file(tests, kernel, label))
    cf_text = control_file.generate_control(test_objects, kernel, label,
                                            is_server)
    return cf_text, is_server, is_synchronous


def create_job(name, priority, control_file, control_type, is_synchronous=None,
               hosts=None, meta_hosts=None):
    """\
    Create and enqueue a job.

    priority: Low, Medium, High, Urgent
    control_file: contents of control file
    control_type: type of control file, Client or Server
    is_synchronous: boolean indicating if a job is synchronous
    hosts: list of hosts to run job on
    meta_hosts: list where each entry is a label name, and for each entry
                one host will be chosen from that label to run the job
                on.
    """
    owner = rpc_utils.get_user().login
    # input validation
    if not hosts and not meta_hosts:
        raise models.ValidationError({
            'arguments' : "You must pass at least one of 'hosts' or "
                          "'meta_hosts'"
            })

    # convert hostnames & meta hosts to host/label objects
    host_objects = []
    for host in hosts or []:
        this_host = models.Host.smart_get(host)
        host_objects.append(this_host)
    for label in meta_hosts or []:
        this_label = models.Label.smart_get(label)
        host_objects.append(this_label)

    # default is_synchronous to some appropriate value
    ControlType = models.Job.ControlType
    control_type = ControlType.get_value(control_type)
    if is_synchronous is None:
        is_synchronous = (control_type == ControlType.SERVER)
    # convert the synch flag to an actual type
    if is_synchronous:
        synch_type = models.Test.SynchType.SYNCHRONOUS
    else:
        synch_type = models.Test.SynchType.ASYNCHRONOUS

    job = models.Job.create(owner=owner, name=name, priority=priority,
                            control_file=control_file,
                            control_type=control_type,
                            synch_type=synch_type,
                            hosts=host_objects)
    job.queue(host_objects)
    return job.id


def requeue_job(id):
    """\
    Create and enqueue a copy of the given job.
    """
    job = models.Job.objects.get(id=id)
    new_job = job.requeue(rpc_utils.get_user().login)
    return new_job.id


def abort_job(id):
    """\
    Abort the job with the given id number.
    """
    job = models.Job.objects.get(id=id)
    job.abort()


def get_jobs(not_yet_run=False, running=False, finished=False, **filter_data):
    """\
    Extra filter args for get_jobs:
    -not_yet_run: Include only jobs that have not yet started running.
    -running: Include only jobs that have start running but for which not
    all hosts have completed.
    -finished: Include only jobs for which all hosts have completed (or
    aborted).
    At most one of these three fields should be specified.
    """
    filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run,
                                                            running,
                                                            finished)
    return rpc_utils.prepare_for_serialization(
        models.Job.list_objects(filter_data))


def get_num_jobs(not_yet_run=False, running=False, finished=False,
                 **filter_data):
    """\
    See get_jobs() for documentation of extra filter parameters.
    """
    filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run,
                                                            running,
                                                            finished)
    return models.Job.query_count(filter_data)


def get_jobs_summary(**filter_data):
    """\
    Like get_jobs(), but adds a 'stauts_counts' field, which is a dictionary
    mapping status strings to the number of hosts currently with that
    status, i.e. {'Queued' : 4, 'Running' : 2}.
    """
    jobs = get_jobs(**filter_data)
    ids = [job['id'] for job in jobs]
    all_status_counts = models.Job.objects.get_status_counts(ids)
    for job in jobs:
        job['status_counts'] = all_status_counts[job['id']]
    return rpc_utils.prepare_for_serialization(jobs)


# host queue entries

def get_host_queue_entries(**filter_data):
    """\
    TODO
    """
    query = models.HostQueueEntry.query_objects(filter_data)
    all_dicts = []
    for queue_entry in query.select_related():
        entry_dict = queue_entry.get_object_dict()
        if entry_dict['host'] is not None:
            entry_dict['host'] = queue_entry.host.get_object_dict()
        entry_dict['job'] = queue_entry.job.get_object_dict()
        all_dicts.append(entry_dict)
    return rpc_utils.prepare_for_serialization(all_dicts)


def get_num_host_queue_entries(**filter_data):
    """\
    Get the number of host queue entries associated with this job.
    """
    return models.HostQueueEntry.query_count(filter_data)


# other

def get_static_data():
    """\
    Returns a dictionary containing a bunch of data that shouldn't change
    often and is otherwise inaccessible.  This includes:
    priorities: list of job priority choices
    default_priority: default priority value for new jobs
    users: sorted list of all users
    labels: sorted list of all labels
    tests: sorted list of all tests
    user_login: logged-in username
    host_statuses: sorted list of possible Host statuses
    job_statuses: sorted list of possible HostQueueEntry statuses
    """
    result = {}
    result['priorities'] = models.Job.Priority.choices()
    default_priority = models.Job.get_field_dict()['priority'].default
    default_string = models.Job.Priority.get_string(default_priority)
    result['default_priority'] = default_string
    result['users'] = get_users(sort_by=['login'])
    result['labels'] = get_labels(sort_by=['-platform', 'name'])
    result['tests'] = get_tests(sort_by=['name'])
    result['user_login'] = rpc_utils.get_user().login
    result['host_statuses'] = rpc_utils.sorted(models.Host.Status.names)
    result['job_statuses'] = rpc_utils.sorted(models.Job.Status.names)
    return result