blob: 9334a09e4ce1ed3eb38ebebdd8743477bcf96e6f [file] [log] [blame]
jamesren9a6f5f62010-05-05 22:55:54 +00001import re
showard26b7ec72009-12-21 22:43:57 +00002from django.db import models as dbmodels
3import common
4from autotest_lib.frontend.afe import models as afe_models
5from autotest_lib.frontend.afe import model_logic, rpc_utils
showard250d84d2010-01-12 21:59:48 +00006from autotest_lib.frontend.tko import models as tko_models
jamesren3e9f6092010-03-11 21:32:10 +00007from autotest_lib.frontend.planner import model_attributes
8from autotest_lib.client.common_lib import utils
showard26b7ec72009-12-21 22:43:57 +00009
10
jamesrenc3940222010-02-19 21:57:37 +000011class Plan(dbmodels.Model, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +000012 """A test plan
13
14 Required:
15 name: Plan name, unique
16 complete: True if the plan is completed
17 dirty: True if the plan has been changed since the execution engine has
18 last seen it
19 initialized: True if the plan has started
20
21 Optional:
22 label_override: A label to apply to each Autotest job.
jamesrendbeebf82010-04-08 22:58:26 +000023 support: The global support script to apply to this plan
showard26b7ec72009-12-21 22:43:57 +000024 """
25 name = dbmodels.CharField(max_length=255, unique=True)
26 label_override = dbmodels.CharField(max_length=255, null=True, blank=True)
27 support = dbmodels.TextField(blank=True)
28 complete = dbmodels.BooleanField(default=False)
29 dirty = dbmodels.BooleanField(default=False)
30 initialized = dbmodels.BooleanField(default=False)
31
32 owners = dbmodels.ManyToManyField(afe_models.User,
33 db_table='planner_plan_owners')
34 hosts = dbmodels.ManyToManyField(afe_models.Host, through='Host')
jamesrenc3940222010-02-19 21:57:37 +000035 host_labels = dbmodels.ManyToManyField(afe_models.Label,
36 db_table='planner_plan_host_labels')
37
38 name_field = 'name'
showard26b7ec72009-12-21 22:43:57 +000039
40 class Meta:
41 db_table = 'planner_plans'
42
43
44 def __unicode__(self):
45 return unicode(self.name)
46
47
48class ModelWithPlan(dbmodels.Model):
49 """Superclass for models that have a plan_id
50
51 Required:
52 plan: The associated test plan
53 """
54 plan = dbmodels.ForeignKey(Plan)
55
56 class Meta:
57 abstract = True
58
59
60 def __unicode__(self):
61 return u'%s (%s)' % (self._get_details_unicode(), self.plan.name)
62
63
64 def _get_details_unicode(self):
65 """Gets the first part of the unicode string
66
67 subclasses must override this method
68 """
69 raise NotImplementedError(
70 'Subclasses must override _get_details_unicode()')
71
72
jamesrenc3940222010-02-19 21:57:37 +000073class Host(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +000074 """A plan host
75
76 Required:
77 host: The AFE host
78 complete: True if and only if this host is finished in the test plan
79 blocked: True if and only if the host is blocked (not executing tests)
jamesren3e9f6092010-03-11 21:32:10 +000080 added_by_label: True if and only if the host was added because of a host
81 label (as opposed to being explicitly added)
showard26b7ec72009-12-21 22:43:57 +000082 """
83 host = dbmodels.ForeignKey(afe_models.Host)
84 complete = dbmodels.BooleanField(default=False)
85 blocked = dbmodels.BooleanField(default=False)
jamesren3e9f6092010-03-11 21:32:10 +000086 added_by_label = dbmodels.BooleanField(default=False)
jamesrenc3940222010-02-19 21:57:37 +000087
showard26b7ec72009-12-21 22:43:57 +000088 class Meta:
89 db_table = 'planner_hosts'
90
91
jamesrenc3940222010-02-19 21:57:37 +000092 def status(self):
93 if self.complete:
jamesren3e9f6092010-03-11 21:32:10 +000094 return model_attributes.HostStatus.FINISHED
jamesrenc3940222010-02-19 21:57:37 +000095 if self.blocked:
jamesren3e9f6092010-03-11 21:32:10 +000096 return model_attributes.HostStatus.BLOCKED
97 return model_attributes.HostStatus.RUNNING
jamesrenc3940222010-02-19 21:57:37 +000098
99
showard26b7ec72009-12-21 22:43:57 +0000100 def _get_details_unicode(self):
jamesren128d4182010-02-19 21:57:58 +0000101 return 'Host: %s' % self.host.hostname
showard26b7ec72009-12-21 22:43:57 +0000102
103
jamesren3e9f6092010-03-11 21:32:10 +0000104class ControlFile(model_logic.ModelWithHash,
105 model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000106 """A control file. Immutable once added to the table
107
108 Required:
109 contents: The text of the control file
110
111 Others:
jamesren3e9f6092010-03-11 21:32:10 +0000112 the_hash: The SHA1 hash of the control file, for duplicate detection
113 and fast search
showard26b7ec72009-12-21 22:43:57 +0000114 """
115 contents = dbmodels.TextField()
116
117 class Meta:
118 db_table = 'planner_test_control_files'
119
120
121 @classmethod
122 def _compute_hash(cls, **kwargs):
lmr6f80e7a2010-02-04 03:18:28 +0000123 return utils.hash('sha1', kwargs['contents']).hexdigest()
showard26b7ec72009-12-21 22:43:57 +0000124
125
126 def __unicode__(self):
127 return u'Control file id %s (SHA1: %s)' % (self.id, self.control_hash)
128
129
jamesren3e9f6092010-03-11 21:32:10 +0000130class TestConfig(ModelWithPlan, model_logic.ModelExtensions):
jamesrenb852bce2010-04-07 20:36:13 +0000131 """A configuration for a planned test
showard26b7ec72009-12-21 22:43:57 +0000132
133 Required:
jamesrenc3940222010-02-19 21:57:37 +0000134 alias: The name to give this test within the plan. Unique with plan id
jamesren9a6f5f62010-05-05 22:55:54 +0000135 control_file: The control file to run
jamesren3e9f6092010-03-11 21:32:10 +0000136 is_server: True if this control file is a server-side test
showard26b7ec72009-12-21 22:43:57 +0000137 execution_order: An integer describing when this test should be run in
138 the test plan
jamesrenc3940222010-02-19 21:57:37 +0000139 estimated_runtime: Time in hours that the test is expected to run. Will
140 be automatically generated (on the frontend) for
141 tests in Autotest.
jamesrendbeebf82010-04-08 22:58:26 +0000142 skipped_hosts: Hosts that are going to skip this test.
showard26b7ec72009-12-21 22:43:57 +0000143 """
jamesrenc3940222010-02-19 21:57:37 +0000144 alias = dbmodels.CharField(max_length=255)
showard26b7ec72009-12-21 22:43:57 +0000145 control_file = dbmodels.ForeignKey(ControlFile)
jamesren3e9f6092010-03-11 21:32:10 +0000146 is_server = dbmodels.BooleanField(default=True)
showard26b7ec72009-12-21 22:43:57 +0000147 execution_order = dbmodels.IntegerField(blank=True)
jamesrenc3940222010-02-19 21:57:37 +0000148 estimated_runtime = dbmodels.IntegerField()
jamesrendbeebf82010-04-08 22:58:26 +0000149 skipped_hosts = dbmodels.ManyToManyField(
150 afe_models.Host, db_table='planner_test_configs_skipped_hosts')
showard26b7ec72009-12-21 22:43:57 +0000151
152 class Meta:
jamesren3e9f6092010-03-11 21:32:10 +0000153 db_table = 'planner_test_configs'
showard26b7ec72009-12-21 22:43:57 +0000154 ordering = ('execution_order',)
jamesrenc3940222010-02-19 21:57:37 +0000155 unique_together = (('plan', 'alias'),)
showard26b7ec72009-12-21 22:43:57 +0000156
157
158 def _get_details_unicode(self):
jamesren3e9f6092010-03-11 21:32:10 +0000159 return 'Planned test config - Control file id %s' % self.control_file.id
showard26b7ec72009-12-21 22:43:57 +0000160
161
jamesren3e9f6092010-03-11 21:32:10 +0000162class Job(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000163 """Represents an Autotest job initiated for a test plan
164
165 Required:
jamesren3e9f6092010-03-11 21:32:10 +0000166 test: The TestConfig associated with this Job
showard26b7ec72009-12-21 22:43:57 +0000167 afe_job: The Autotest job
168 """
jamesren3e9f6092010-03-11 21:32:10 +0000169 test_config = dbmodels.ForeignKey(TestConfig)
showard26b7ec72009-12-21 22:43:57 +0000170 afe_job = dbmodels.ForeignKey(afe_models.Job)
jamesren4be631f2010-04-08 23:01:22 +0000171 requires_rerun = dbmodels.BooleanField(default=False)
showard26b7ec72009-12-21 22:43:57 +0000172
173 class Meta:
174 db_table = 'planner_test_jobs'
175
176
177 def _get_details_unicode(self):
jamesren128d4182010-02-19 21:57:58 +0000178 return 'AFE job %s' % self.afe_job.id
showard26b7ec72009-12-21 22:43:57 +0000179
180
181class Bug(dbmodels.Model):
182 """Represents a bug ID
183
184 Required:
185 external_uid: External unique ID for the bug
186 """
187 external_uid = dbmodels.CharField(max_length=255, unique=True)
188
189 class Meta:
190 db_table = 'planner_bugs'
191
192
193 def __unicode__(self):
194 return u'Bug external ID %s' % self.external_uid
195
196
jamesren3e9f6092010-03-11 21:32:10 +0000197class TestRun(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000198 """An individual test run from an Autotest job for the test plan.
199
200 Each Job object may have multiple TestRun objects associated with it.
201
202 Required:
203 test_job: The Job object associated with this TestRun
204 tko_test: The TKO Test associated with this TestRun
205 status: One of 'Active', 'Passed', 'Failed'
206 finalized: True if and only if the TestRun is ready to be shown in
207 triage
208 invalidated: True if and only if a user has decided to invalidate this
209 TestRun's results
210 seen: True if and only if a user has marked this TestRun as "seen"
211 triaged: True if and only if the TestRun no longer requires any user
212 intervention
213
214 Optional:
215 bugs: Bugs filed that a relevant to this run
216 """
showard26b7ec72009-12-21 22:43:57 +0000217 test_job = dbmodels.ForeignKey(Job)
218 tko_test = dbmodels.ForeignKey(tko_models.Test)
jamesrenc3940222010-02-19 21:57:37 +0000219 host = dbmodels.ForeignKey(Host)
jamesren3e9f6092010-03-11 21:32:10 +0000220 status = dbmodels.CharField(
221 max_length=16,
222 choices=model_attributes.TestRunStatus.choices(),
223 default=model_attributes.TestRunStatus.ACTIVE)
showard26b7ec72009-12-21 22:43:57 +0000224 finalized = dbmodels.BooleanField(default=False)
225 seen = dbmodels.BooleanField(default=False)
226 triaged = dbmodels.BooleanField(default=False)
jamesren4be631f2010-04-08 23:01:22 +0000227 invalidated = dbmodels.BooleanField(default=False)
showard26b7ec72009-12-21 22:43:57 +0000228
229 bugs = dbmodels.ManyToManyField(Bug, null=True,
230 db_table='planner_test_run_bugs')
231
232 class Meta:
233 db_table = 'planner_test_runs'
jamesren3e9f6092010-03-11 21:32:10 +0000234 unique_together = (('plan', 'test_job', 'tko_test', 'host'),)
showard26b7ec72009-12-21 22:43:57 +0000235
236
237 def _get_details_unicode(self):
238 return 'Test Run: %s' % self.id
239
240
241class DataType(dbmodels.Model):
242 """Encodes the data model types
243
244 For use in the history table, to identify the type of object that was
245 changed.
246
247 Required:
248 name: The name of the data type
249 db_table: The name of the database table that stores this type
250 """
251 name = dbmodels.CharField(max_length=255)
252 db_table = dbmodels.CharField(max_length=255)
253
254 class Meta:
255 db_table = 'planner_data_types'
256
257
258 def __unicode__(self):
259 return u'Data type %s (stored in table %s)' % (self.name, self.db_table)
260
261
262class History(ModelWithPlan):
263 """Represents a history action
264
265 Required:
266 action_id: An arbitrary ID that uniquely identifies the user action
267 related to the history entry. One user action may result in
268 multiple history entries
269 user: The user who initiated the change
270 data_type: The type of object that was changed
271 object_id: Value of the primary key field for the changed object
272 old_object_repr: A string representation of the object before the change
273 new_object_repr: A string representation of the object after the change
274
275 Others:
276 time: A timestamp. Automatically generated.
277 """
278 action_id = dbmodels.IntegerField()
279 user = dbmodels.ForeignKey(afe_models.User)
280 data_type = dbmodels.ForeignKey(DataType)
281 object_id = dbmodels.IntegerField()
282 old_object_repr = dbmodels.TextField(blank=True)
283 new_object_repr = dbmodels.TextField(blank=True)
284
285 time = dbmodels.DateTimeField(auto_now_add=True)
286
287 class Meta:
288 db_table = 'planner_history'
289
290
291 def _get_details_unicode(self):
292 return 'History entry: %s => %s' % (self.old_object_repr,
293 self.new_object_repr)
294
295
296class SavedObject(dbmodels.Model):
297 """A saved object that can be recalled at certain points in the UI
298
299 Required:
300 user: The creator of the object
301 object_type: One of 'support', 'triage', 'autoprocess', 'custom_query'
302 name: The name given to the object
303 encoded_object: The actual object
304 """
showard26b7ec72009-12-21 22:43:57 +0000305 user = dbmodels.ForeignKey(afe_models.User)
jamesren3e9f6092010-03-11 21:32:10 +0000306 object_type = dbmodels.CharField(
307 max_length=16,
308 choices=model_attributes.SavedObjectType.choices(),
309 db_column='type')
showard26b7ec72009-12-21 22:43:57 +0000310 name = dbmodels.CharField(max_length=255)
311 encoded_object = dbmodels.TextField()
312
313 class Meta:
314 db_table = 'planner_saved_objects'
315 unique_together = ('user', 'object_type', 'name')
316
317
318 def __unicode__(self):
319 return u'Saved %s object: %s, by %s' % (self.object_type, self.name,
320 self.user.login)
321
322
323class CustomQuery(ModelWithPlan):
324 """A custom SQL query for the triage page
325
326 Required:
327 query: the SQL WHERE clause to attach to the main query
328 """
329 query = dbmodels.TextField()
330
331 class Meta:
332 db_table = 'planner_custom_queries'
333
334
335 def _get_details_unicode(self):
336 return 'Custom Query: %s' % self.query
337
338
339class KeyVal(model_logic.ModelWithHash):
340 """Represents a keyval. Immutable once added to the table.
341
342 Required:
343 key: The key
344 value: The value
345
346 Others:
jamesren3e9f6092010-03-11 21:32:10 +0000347 the_hash: The result of SHA1(SHA1(key) ++ value), for duplicate
348 detection and fast search.
showard26b7ec72009-12-21 22:43:57 +0000349 """
350 key = dbmodels.CharField(max_length=1024)
351 value = dbmodels.CharField(max_length=1024)
352
353 class Meta:
354 db_table = 'planner_keyvals'
355
356
357 @classmethod
358 def _compute_hash(cls, **kwargs):
lmr6f80e7a2010-02-04 03:18:28 +0000359 round1 = utils.hash('sha1', kwargs['key']).hexdigest()
360 return utils.hash('sha1', round1 + kwargs['value']).hexdigest()
showard26b7ec72009-12-21 22:43:57 +0000361
362
363 def __unicode__(self):
364 return u'Keyval: %s = %s' % (self.key, self.value)
365
366
367class AutoProcess(ModelWithPlan):
368 """An autoprocessing directive to perform on test runs that enter triage
369
370 Required:
371 condition: A SQL WHERE clause. The autoprocessing will be applied if the
372 test run matches this condition
373 enabled: If this is False, this autoprocessing entry will not be applied
374
375 Optional:
376 labels: Labels to apply to the TKO test
377 keyvals: Keyval overrides to apply to the TKO test
378 bugs: Bugs filed that a relevant to this run
379 reason_override: Override for the AFE reason
380 """
381 condition = dbmodels.TextField()
382 enabled = dbmodels.BooleanField(default=False)
383
384 labels = dbmodels.ManyToManyField(tko_models.TestLabel, null=True,
385 db_table='planner_autoprocess_labels')
386 keyvals = dbmodels.ManyToManyField(KeyVal, null=True,
387 db_table='planner_autoprocess_keyvals')
388 bugs = dbmodels.ManyToManyField(Bug, null=True,
389 db_table='planner_autoprocess_bugs')
390 reason_override = dbmodels.CharField(max_length=255, null=True, blank=True)
391
392 class Meta:
393 db_table = 'planner_autoprocess'
394
395
396 def _get_details_unicode(self):
397 return 'Autoprocessing condition: %s' % self.condition
jamesren9a6f5f62010-05-05 22:55:54 +0000398
399
400class AdditionalParameter(ModelWithPlan):
401 """
402 Allows parameters to be passed to the execution engine for test configs
403
404 If this object matches a hostname by regex, it will apply the associated
405 parameters at their applicable locations.
406
407 Required:
408 hostname_regex: A regular expression, for matching on the hostname
409 param_type: Currently only 'Verify' (and site-specific values) allowed
410 application_order: The order in which to apply this parameter.
411 Parameters are attempted in the order specified here,
412 and stop when the first match is found
413 """
414 hostname_regex = dbmodels.CharField(max_length=255)
415 param_type = dbmodels.CharField(
416 max_length=32,
417 choices=model_attributes.AdditionalParameterType.choices())
418 application_order = dbmodels.IntegerField(blank=True)
419
420 class Meta:
421 db_table = 'planner_additional_parameters'
422 unique_together = ('plan', 'hostname_regex', 'param_type')
423
424
425 @classmethod
426 def find_applicable_additional_parameter(cls, plan, hostname, param_type):
427 """
428 Finds the first AdditionalParameter that matches the given arguments
429 """
430 params = cls.objects.filter(
431 plan=plan, param_type=param_type).order_by('application_order')
432 for param in params:
433 if re.match(param.hostname_regex, hostname):
434 return param
435 return None
436
437
438 def _get_details_unicode(self):
439 return 'Additional %s parameters, regex: %s' % (self.param_type,
440 self.hostname_regex)
441
442
443class AdditionalParameterValue(dbmodels.Model):
444 """
445 The actual values for the additional parameters
446
447 Required:
448 additional_parameter: The associated AdditionalParameter
449 key: The name of the parameter
450 value: The value of the parameter
451 """
452 additional_parameter = dbmodels.ForeignKey(AdditionalParameter)
453 key = dbmodels.CharField(max_length=255)
454 value = dbmodels.CharField(max_length=255)
455
456 class Meta:
457 db_table = 'planner_additional_parameter_values'
458 unique_together = ('additional_parameter', 'key')
459
460
461 def __unicode__(self):
462 return u'Value for parameter %d: %s=%s' % (self.additional_parameter.id,
463 self.key, self.value)