blob: 9e07b4281e4f594abd2f244d2972f9846a9dadb1 [file] [log] [blame]
showard26b7ec72009-12-21 22:43:57 +00001from django.db import models as dbmodels
2import common
3from autotest_lib.frontend.afe import models as afe_models
4from autotest_lib.frontend.afe import model_logic, rpc_utils
showard250d84d2010-01-12 21:59:48 +00005from autotest_lib.frontend.tko import models as tko_models
jamesren3e9f6092010-03-11 21:32:10 +00006from autotest_lib.frontend.planner import model_attributes
7from autotest_lib.client.common_lib import utils
showard26b7ec72009-12-21 22:43:57 +00008
9
jamesrenc3940222010-02-19 21:57:37 +000010class Plan(dbmodels.Model, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +000011 """A test plan
12
13 Required:
14 name: Plan name, unique
15 complete: True if the plan is completed
16 dirty: True if the plan has been changed since the execution engine has
17 last seen it
18 initialized: True if the plan has started
19
20 Optional:
21 label_override: A label to apply to each Autotest job.
jamesrendbeebf82010-04-08 22:58:26 +000022 support: The global support script to apply to this plan
showard26b7ec72009-12-21 22:43:57 +000023 """
24 name = dbmodels.CharField(max_length=255, unique=True)
25 label_override = dbmodels.CharField(max_length=255, null=True, blank=True)
26 support = dbmodels.TextField(blank=True)
27 complete = dbmodels.BooleanField(default=False)
28 dirty = dbmodels.BooleanField(default=False)
29 initialized = dbmodels.BooleanField(default=False)
30
31 owners = dbmodels.ManyToManyField(afe_models.User,
32 db_table='planner_plan_owners')
33 hosts = dbmodels.ManyToManyField(afe_models.Host, through='Host')
jamesrenc3940222010-02-19 21:57:37 +000034 host_labels = dbmodels.ManyToManyField(afe_models.Label,
35 db_table='planner_plan_host_labels')
36
37 name_field = 'name'
showard26b7ec72009-12-21 22:43:57 +000038
39 class Meta:
40 db_table = 'planner_plans'
41
42
43 def __unicode__(self):
44 return unicode(self.name)
45
46
47class ModelWithPlan(dbmodels.Model):
48 """Superclass for models that have a plan_id
49
50 Required:
51 plan: The associated test plan
52 """
53 plan = dbmodels.ForeignKey(Plan)
54
55 class Meta:
56 abstract = True
57
58
59 def __unicode__(self):
60 return u'%s (%s)' % (self._get_details_unicode(), self.plan.name)
61
62
63 def _get_details_unicode(self):
64 """Gets the first part of the unicode string
65
66 subclasses must override this method
67 """
68 raise NotImplementedError(
69 'Subclasses must override _get_details_unicode()')
70
71
jamesrenc3940222010-02-19 21:57:37 +000072class Host(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +000073 """A plan host
74
75 Required:
76 host: The AFE host
77 complete: True if and only if this host is finished in the test plan
78 blocked: True if and only if the host is blocked (not executing tests)
jamesren3e9f6092010-03-11 21:32:10 +000079 added_by_label: True if and only if the host was added because of a host
80 label (as opposed to being explicitly added)
showard26b7ec72009-12-21 22:43:57 +000081 """
82 host = dbmodels.ForeignKey(afe_models.Host)
83 complete = dbmodels.BooleanField(default=False)
84 blocked = dbmodels.BooleanField(default=False)
jamesren3e9f6092010-03-11 21:32:10 +000085 added_by_label = dbmodels.BooleanField(default=False)
jamesrenc3940222010-02-19 21:57:37 +000086
showard26b7ec72009-12-21 22:43:57 +000087 class Meta:
88 db_table = 'planner_hosts'
89
90
jamesrenc3940222010-02-19 21:57:37 +000091 def status(self):
92 if self.complete:
jamesren3e9f6092010-03-11 21:32:10 +000093 return model_attributes.HostStatus.FINISHED
jamesrenc3940222010-02-19 21:57:37 +000094 if self.blocked:
jamesren3e9f6092010-03-11 21:32:10 +000095 return model_attributes.HostStatus.BLOCKED
96 return model_attributes.HostStatus.RUNNING
jamesrenc3940222010-02-19 21:57:37 +000097
98
showard26b7ec72009-12-21 22:43:57 +000099 def _get_details_unicode(self):
jamesren128d4182010-02-19 21:57:58 +0000100 return 'Host: %s' % self.host.hostname
showard26b7ec72009-12-21 22:43:57 +0000101
102
jamesren3e9f6092010-03-11 21:32:10 +0000103class ControlFile(model_logic.ModelWithHash,
104 model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000105 """A control file. Immutable once added to the table
106
107 Required:
108 contents: The text of the control file
109
110 Others:
jamesren3e9f6092010-03-11 21:32:10 +0000111 the_hash: The SHA1 hash of the control file, for duplicate detection
112 and fast search
showard26b7ec72009-12-21 22:43:57 +0000113 """
114 contents = dbmodels.TextField()
115
116 class Meta:
117 db_table = 'planner_test_control_files'
118
119
120 @classmethod
121 def _compute_hash(cls, **kwargs):
lmr6f80e7a2010-02-04 03:18:28 +0000122 return utils.hash('sha1', kwargs['contents']).hexdigest()
showard26b7ec72009-12-21 22:43:57 +0000123
124
125 def __unicode__(self):
126 return u'Control file id %s (SHA1: %s)' % (self.id, self.control_hash)
127
128
jamesren3e9f6092010-03-11 21:32:10 +0000129class TestConfig(ModelWithPlan, model_logic.ModelExtensions):
jamesrenb852bce2010-04-07 20:36:13 +0000130 """A configuration for a planned test
showard26b7ec72009-12-21 22:43:57 +0000131
132 Required:
jamesrenc3940222010-02-19 21:57:37 +0000133 alias: The name to give this test within the plan. Unique with plan id
showard26b7ec72009-12-21 22:43:57 +0000134 test_control_file: The control file to run
jamesren3e9f6092010-03-11 21:32:10 +0000135 is_server: True if this control file is a server-side test
showard26b7ec72009-12-21 22:43:57 +0000136 execution_order: An integer describing when this test should be run in
137 the test plan
jamesrenc3940222010-02-19 21:57:37 +0000138 estimated_runtime: Time in hours that the test is expected to run. Will
139 be automatically generated (on the frontend) for
140 tests in Autotest.
jamesrendbeebf82010-04-08 22:58:26 +0000141 skipped_hosts: Hosts that are going to skip this test.
showard26b7ec72009-12-21 22:43:57 +0000142 """
jamesrenc3940222010-02-19 21:57:37 +0000143 alias = dbmodels.CharField(max_length=255)
showard26b7ec72009-12-21 22:43:57 +0000144 control_file = dbmodels.ForeignKey(ControlFile)
jamesren3e9f6092010-03-11 21:32:10 +0000145 is_server = dbmodels.BooleanField(default=True)
showard26b7ec72009-12-21 22:43:57 +0000146 execution_order = dbmodels.IntegerField(blank=True)
jamesrenc3940222010-02-19 21:57:37 +0000147 estimated_runtime = dbmodels.IntegerField()
jamesrendbeebf82010-04-08 22:58:26 +0000148 skipped_hosts = dbmodels.ManyToManyField(
149 afe_models.Host, db_table='planner_test_configs_skipped_hosts')
showard26b7ec72009-12-21 22:43:57 +0000150
151 class Meta:
jamesren3e9f6092010-03-11 21:32:10 +0000152 db_table = 'planner_test_configs'
showard26b7ec72009-12-21 22:43:57 +0000153 ordering = ('execution_order',)
jamesrenc3940222010-02-19 21:57:37 +0000154 unique_together = (('plan', 'alias'),)
showard26b7ec72009-12-21 22:43:57 +0000155
156
157 def _get_details_unicode(self):
jamesren3e9f6092010-03-11 21:32:10 +0000158 return 'Planned test config - Control file id %s' % self.control_file.id
showard26b7ec72009-12-21 22:43:57 +0000159
160
jamesren3e9f6092010-03-11 21:32:10 +0000161class Job(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000162 """Represents an Autotest job initiated for a test plan
163
164 Required:
jamesren3e9f6092010-03-11 21:32:10 +0000165 test: The TestConfig associated with this Job
showard26b7ec72009-12-21 22:43:57 +0000166 afe_job: The Autotest job
167 """
jamesren3e9f6092010-03-11 21:32:10 +0000168 test_config = dbmodels.ForeignKey(TestConfig)
showard26b7ec72009-12-21 22:43:57 +0000169 afe_job = dbmodels.ForeignKey(afe_models.Job)
170
171 class Meta:
172 db_table = 'planner_test_jobs'
173
174
175 def _get_details_unicode(self):
jamesren128d4182010-02-19 21:57:58 +0000176 return 'AFE job %s' % self.afe_job.id
showard26b7ec72009-12-21 22:43:57 +0000177
178
179class Bug(dbmodels.Model):
180 """Represents a bug ID
181
182 Required:
183 external_uid: External unique ID for the bug
184 """
185 external_uid = dbmodels.CharField(max_length=255, unique=True)
186
187 class Meta:
188 db_table = 'planner_bugs'
189
190
191 def __unicode__(self):
192 return u'Bug external ID %s' % self.external_uid
193
194
jamesren3e9f6092010-03-11 21:32:10 +0000195class TestRun(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000196 """An individual test run from an Autotest job for the test plan.
197
198 Each Job object may have multiple TestRun objects associated with it.
199
200 Required:
201 test_job: The Job object associated with this TestRun
202 tko_test: The TKO Test associated with this TestRun
203 status: One of 'Active', 'Passed', 'Failed'
204 finalized: True if and only if the TestRun is ready to be shown in
205 triage
206 invalidated: True if and only if a user has decided to invalidate this
207 TestRun's results
208 seen: True if and only if a user has marked this TestRun as "seen"
209 triaged: True if and only if the TestRun no longer requires any user
210 intervention
211
212 Optional:
213 bugs: Bugs filed that a relevant to this run
214 """
showard26b7ec72009-12-21 22:43:57 +0000215 test_job = dbmodels.ForeignKey(Job)
216 tko_test = dbmodels.ForeignKey(tko_models.Test)
jamesrenc3940222010-02-19 21:57:37 +0000217 host = dbmodels.ForeignKey(Host)
jamesren3e9f6092010-03-11 21:32:10 +0000218 status = dbmodels.CharField(
219 max_length=16,
220 choices=model_attributes.TestRunStatus.choices(),
221 default=model_attributes.TestRunStatus.ACTIVE)
showard26b7ec72009-12-21 22:43:57 +0000222 finalized = dbmodels.BooleanField(default=False)
223 seen = dbmodels.BooleanField(default=False)
224 triaged = dbmodels.BooleanField(default=False)
225
226 bugs = dbmodels.ManyToManyField(Bug, null=True,
227 db_table='planner_test_run_bugs')
228
229 class Meta:
230 db_table = 'planner_test_runs'
jamesren3e9f6092010-03-11 21:32:10 +0000231 unique_together = (('plan', 'test_job', 'tko_test', 'host'),)
showard26b7ec72009-12-21 22:43:57 +0000232
233
234 def _get_details_unicode(self):
235 return 'Test Run: %s' % self.id
236
237
238class DataType(dbmodels.Model):
239 """Encodes the data model types
240
241 For use in the history table, to identify the type of object that was
242 changed.
243
244 Required:
245 name: The name of the data type
246 db_table: The name of the database table that stores this type
247 """
248 name = dbmodels.CharField(max_length=255)
249 db_table = dbmodels.CharField(max_length=255)
250
251 class Meta:
252 db_table = 'planner_data_types'
253
254
255 def __unicode__(self):
256 return u'Data type %s (stored in table %s)' % (self.name, self.db_table)
257
258
259class History(ModelWithPlan):
260 """Represents a history action
261
262 Required:
263 action_id: An arbitrary ID that uniquely identifies the user action
264 related to the history entry. One user action may result in
265 multiple history entries
266 user: The user who initiated the change
267 data_type: The type of object that was changed
268 object_id: Value of the primary key field for the changed object
269 old_object_repr: A string representation of the object before the change
270 new_object_repr: A string representation of the object after the change
271
272 Others:
273 time: A timestamp. Automatically generated.
274 """
275 action_id = dbmodels.IntegerField()
276 user = dbmodels.ForeignKey(afe_models.User)
277 data_type = dbmodels.ForeignKey(DataType)
278 object_id = dbmodels.IntegerField()
279 old_object_repr = dbmodels.TextField(blank=True)
280 new_object_repr = dbmodels.TextField(blank=True)
281
282 time = dbmodels.DateTimeField(auto_now_add=True)
283
284 class Meta:
285 db_table = 'planner_history'
286
287
288 def _get_details_unicode(self):
289 return 'History entry: %s => %s' % (self.old_object_repr,
290 self.new_object_repr)
291
292
293class SavedObject(dbmodels.Model):
294 """A saved object that can be recalled at certain points in the UI
295
296 Required:
297 user: The creator of the object
298 object_type: One of 'support', 'triage', 'autoprocess', 'custom_query'
299 name: The name given to the object
300 encoded_object: The actual object
301 """
showard26b7ec72009-12-21 22:43:57 +0000302 user = dbmodels.ForeignKey(afe_models.User)
jamesren3e9f6092010-03-11 21:32:10 +0000303 object_type = dbmodels.CharField(
304 max_length=16,
305 choices=model_attributes.SavedObjectType.choices(),
306 db_column='type')
showard26b7ec72009-12-21 22:43:57 +0000307 name = dbmodels.CharField(max_length=255)
308 encoded_object = dbmodels.TextField()
309
310 class Meta:
311 db_table = 'planner_saved_objects'
312 unique_together = ('user', 'object_type', 'name')
313
314
315 def __unicode__(self):
316 return u'Saved %s object: %s, by %s' % (self.object_type, self.name,
317 self.user.login)
318
319
320class CustomQuery(ModelWithPlan):
321 """A custom SQL query for the triage page
322
323 Required:
324 query: the SQL WHERE clause to attach to the main query
325 """
326 query = dbmodels.TextField()
327
328 class Meta:
329 db_table = 'planner_custom_queries'
330
331
332 def _get_details_unicode(self):
333 return 'Custom Query: %s' % self.query
334
335
336class KeyVal(model_logic.ModelWithHash):
337 """Represents a keyval. Immutable once added to the table.
338
339 Required:
340 key: The key
341 value: The value
342
343 Others:
jamesren3e9f6092010-03-11 21:32:10 +0000344 the_hash: The result of SHA1(SHA1(key) ++ value), for duplicate
345 detection and fast search.
showard26b7ec72009-12-21 22:43:57 +0000346 """
347 key = dbmodels.CharField(max_length=1024)
348 value = dbmodels.CharField(max_length=1024)
349
350 class Meta:
351 db_table = 'planner_keyvals'
352
353
354 @classmethod
355 def _compute_hash(cls, **kwargs):
lmr6f80e7a2010-02-04 03:18:28 +0000356 round1 = utils.hash('sha1', kwargs['key']).hexdigest()
357 return utils.hash('sha1', round1 + kwargs['value']).hexdigest()
showard26b7ec72009-12-21 22:43:57 +0000358
359
360 def __unicode__(self):
361 return u'Keyval: %s = %s' % (self.key, self.value)
362
363
364class AutoProcess(ModelWithPlan):
365 """An autoprocessing directive to perform on test runs that enter triage
366
367 Required:
368 condition: A SQL WHERE clause. The autoprocessing will be applied if the
369 test run matches this condition
370 enabled: If this is False, this autoprocessing entry will not be applied
371
372 Optional:
373 labels: Labels to apply to the TKO test
374 keyvals: Keyval overrides to apply to the TKO test
375 bugs: Bugs filed that a relevant to this run
376 reason_override: Override for the AFE reason
377 """
378 condition = dbmodels.TextField()
379 enabled = dbmodels.BooleanField(default=False)
380
381 labels = dbmodels.ManyToManyField(tko_models.TestLabel, null=True,
382 db_table='planner_autoprocess_labels')
383 keyvals = dbmodels.ManyToManyField(KeyVal, null=True,
384 db_table='planner_autoprocess_keyvals')
385 bugs = dbmodels.ManyToManyField(Bug, null=True,
386 db_table='planner_autoprocess_bugs')
387 reason_override = dbmodels.CharField(max_length=255, null=True, blank=True)
388
389 class Meta:
390 db_table = 'planner_autoprocess'
391
392
393 def _get_details_unicode(self):
394 return 'Autoprocessing condition: %s' % self.condition