blob: f69aa2808795c721828651396d5fdabeca37730a [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.
22 support: The global support object to apply to this plan
23 """
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):
showard26b7ec72009-12-21 22:43:57 +0000130 """A planned test
131
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.
showard26b7ec72009-12-21 22:43:57 +0000141 """
jamesrenc3940222010-02-19 21:57:37 +0000142 alias = dbmodels.CharField(max_length=255)
showard26b7ec72009-12-21 22:43:57 +0000143 control_file = dbmodels.ForeignKey(ControlFile)
jamesren3e9f6092010-03-11 21:32:10 +0000144 is_server = dbmodels.BooleanField(default=True)
showard26b7ec72009-12-21 22:43:57 +0000145 execution_order = dbmodels.IntegerField(blank=True)
jamesrenc3940222010-02-19 21:57:37 +0000146 estimated_runtime = dbmodels.IntegerField()
showard26b7ec72009-12-21 22:43:57 +0000147
148 class Meta:
jamesren3e9f6092010-03-11 21:32:10 +0000149 db_table = 'planner_test_configs'
showard26b7ec72009-12-21 22:43:57 +0000150 ordering = ('execution_order',)
jamesrenc3940222010-02-19 21:57:37 +0000151 unique_together = (('plan', 'alias'),)
showard26b7ec72009-12-21 22:43:57 +0000152
153
154 def _get_details_unicode(self):
jamesren3e9f6092010-03-11 21:32:10 +0000155 return 'Planned test config - Control file id %s' % self.control_file.id
showard26b7ec72009-12-21 22:43:57 +0000156
157
jamesren3e9f6092010-03-11 21:32:10 +0000158class Job(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000159 """Represents an Autotest job initiated for a test plan
160
161 Required:
jamesren3e9f6092010-03-11 21:32:10 +0000162 test: The TestConfig associated with this Job
showard26b7ec72009-12-21 22:43:57 +0000163 afe_job: The Autotest job
164 """
jamesren3e9f6092010-03-11 21:32:10 +0000165 test_config = dbmodels.ForeignKey(TestConfig)
showard26b7ec72009-12-21 22:43:57 +0000166 afe_job = dbmodels.ForeignKey(afe_models.Job)
167
168 class Meta:
169 db_table = 'planner_test_jobs'
170
171
172 def _get_details_unicode(self):
jamesren128d4182010-02-19 21:57:58 +0000173 return 'AFE job %s' % self.afe_job.id
showard26b7ec72009-12-21 22:43:57 +0000174
175
176class Bug(dbmodels.Model):
177 """Represents a bug ID
178
179 Required:
180 external_uid: External unique ID for the bug
181 """
182 external_uid = dbmodels.CharField(max_length=255, unique=True)
183
184 class Meta:
185 db_table = 'planner_bugs'
186
187
188 def __unicode__(self):
189 return u'Bug external ID %s' % self.external_uid
190
191
jamesren3e9f6092010-03-11 21:32:10 +0000192class TestRun(ModelWithPlan, model_logic.ModelExtensions):
showard26b7ec72009-12-21 22:43:57 +0000193 """An individual test run from an Autotest job for the test plan.
194
195 Each Job object may have multiple TestRun objects associated with it.
196
197 Required:
198 test_job: The Job object associated with this TestRun
199 tko_test: The TKO Test associated with this TestRun
200 status: One of 'Active', 'Passed', 'Failed'
201 finalized: True if and only if the TestRun is ready to be shown in
202 triage
203 invalidated: True if and only if a user has decided to invalidate this
204 TestRun's results
205 seen: True if and only if a user has marked this TestRun as "seen"
206 triaged: True if and only if the TestRun no longer requires any user
207 intervention
208
209 Optional:
210 bugs: Bugs filed that a relevant to this run
211 """
showard26b7ec72009-12-21 22:43:57 +0000212 test_job = dbmodels.ForeignKey(Job)
213 tko_test = dbmodels.ForeignKey(tko_models.Test)
jamesrenc3940222010-02-19 21:57:37 +0000214 host = dbmodels.ForeignKey(Host)
jamesren3e9f6092010-03-11 21:32:10 +0000215 status = dbmodels.CharField(
216 max_length=16,
217 choices=model_attributes.TestRunStatus.choices(),
218 default=model_attributes.TestRunStatus.ACTIVE)
showard26b7ec72009-12-21 22:43:57 +0000219 finalized = dbmodels.BooleanField(default=False)
220 seen = dbmodels.BooleanField(default=False)
221 triaged = dbmodels.BooleanField(default=False)
222
223 bugs = dbmodels.ManyToManyField(Bug, null=True,
224 db_table='planner_test_run_bugs')
225
226 class Meta:
227 db_table = 'planner_test_runs'
jamesren3e9f6092010-03-11 21:32:10 +0000228 unique_together = (('plan', 'test_job', 'tko_test', 'host'),)
showard26b7ec72009-12-21 22:43:57 +0000229
230
231 def _get_details_unicode(self):
232 return 'Test Run: %s' % self.id
233
234
235class DataType(dbmodels.Model):
236 """Encodes the data model types
237
238 For use in the history table, to identify the type of object that was
239 changed.
240
241 Required:
242 name: The name of the data type
243 db_table: The name of the database table that stores this type
244 """
245 name = dbmodels.CharField(max_length=255)
246 db_table = dbmodels.CharField(max_length=255)
247
248 class Meta:
249 db_table = 'planner_data_types'
250
251
252 def __unicode__(self):
253 return u'Data type %s (stored in table %s)' % (self.name, self.db_table)
254
255
256class History(ModelWithPlan):
257 """Represents a history action
258
259 Required:
260 action_id: An arbitrary ID that uniquely identifies the user action
261 related to the history entry. One user action may result in
262 multiple history entries
263 user: The user who initiated the change
264 data_type: The type of object that was changed
265 object_id: Value of the primary key field for the changed object
266 old_object_repr: A string representation of the object before the change
267 new_object_repr: A string representation of the object after the change
268
269 Others:
270 time: A timestamp. Automatically generated.
271 """
272 action_id = dbmodels.IntegerField()
273 user = dbmodels.ForeignKey(afe_models.User)
274 data_type = dbmodels.ForeignKey(DataType)
275 object_id = dbmodels.IntegerField()
276 old_object_repr = dbmodels.TextField(blank=True)
277 new_object_repr = dbmodels.TextField(blank=True)
278
279 time = dbmodels.DateTimeField(auto_now_add=True)
280
281 class Meta:
282 db_table = 'planner_history'
283
284
285 def _get_details_unicode(self):
286 return 'History entry: %s => %s' % (self.old_object_repr,
287 self.new_object_repr)
288
289
290class SavedObject(dbmodels.Model):
291 """A saved object that can be recalled at certain points in the UI
292
293 Required:
294 user: The creator of the object
295 object_type: One of 'support', 'triage', 'autoprocess', 'custom_query'
296 name: The name given to the object
297 encoded_object: The actual object
298 """
showard26b7ec72009-12-21 22:43:57 +0000299 user = dbmodels.ForeignKey(afe_models.User)
jamesren3e9f6092010-03-11 21:32:10 +0000300 object_type = dbmodels.CharField(
301 max_length=16,
302 choices=model_attributes.SavedObjectType.choices(),
303 db_column='type')
showard26b7ec72009-12-21 22:43:57 +0000304 name = dbmodels.CharField(max_length=255)
305 encoded_object = dbmodels.TextField()
306
307 class Meta:
308 db_table = 'planner_saved_objects'
309 unique_together = ('user', 'object_type', 'name')
310
311
312 def __unicode__(self):
313 return u'Saved %s object: %s, by %s' % (self.object_type, self.name,
314 self.user.login)
315
316
317class CustomQuery(ModelWithPlan):
318 """A custom SQL query for the triage page
319
320 Required:
321 query: the SQL WHERE clause to attach to the main query
322 """
323 query = dbmodels.TextField()
324
325 class Meta:
326 db_table = 'planner_custom_queries'
327
328
329 def _get_details_unicode(self):
330 return 'Custom Query: %s' % self.query
331
332
333class KeyVal(model_logic.ModelWithHash):
334 """Represents a keyval. Immutable once added to the table.
335
336 Required:
337 key: The key
338 value: The value
339
340 Others:
jamesren3e9f6092010-03-11 21:32:10 +0000341 the_hash: The result of SHA1(SHA1(key) ++ value), for duplicate
342 detection and fast search.
showard26b7ec72009-12-21 22:43:57 +0000343 """
344 key = dbmodels.CharField(max_length=1024)
345 value = dbmodels.CharField(max_length=1024)
346
347 class Meta:
348 db_table = 'planner_keyvals'
349
350
351 @classmethod
352 def _compute_hash(cls, **kwargs):
lmr6f80e7a2010-02-04 03:18:28 +0000353 round1 = utils.hash('sha1', kwargs['key']).hexdigest()
354 return utils.hash('sha1', round1 + kwargs['value']).hexdigest()
showard26b7ec72009-12-21 22:43:57 +0000355
356
357 def __unicode__(self):
358 return u'Keyval: %s = %s' % (self.key, self.value)
359
360
361class AutoProcess(ModelWithPlan):
362 """An autoprocessing directive to perform on test runs that enter triage
363
364 Required:
365 condition: A SQL WHERE clause. The autoprocessing will be applied if the
366 test run matches this condition
367 enabled: If this is False, this autoprocessing entry will not be applied
368
369 Optional:
370 labels: Labels to apply to the TKO test
371 keyvals: Keyval overrides to apply to the TKO test
372 bugs: Bugs filed that a relevant to this run
373 reason_override: Override for the AFE reason
374 """
375 condition = dbmodels.TextField()
376 enabled = dbmodels.BooleanField(default=False)
377
378 labels = dbmodels.ManyToManyField(tko_models.TestLabel, null=True,
379 db_table='planner_autoprocess_labels')
380 keyvals = dbmodels.ManyToManyField(KeyVal, null=True,
381 db_table='planner_autoprocess_keyvals')
382 bugs = dbmodels.ManyToManyField(Bug, null=True,
383 db_table='planner_autoprocess_bugs')
384 reason_override = dbmodels.CharField(max_length=255, null=True, blank=True)
385
386 class Meta:
387 db_table = 'planner_autoprocess'
388
389
390 def _get_details_unicode(self):
391 return 'Autoprocessing condition: %s' % self.condition