blob: 8cf42fa933a89f4dc924d44121556c9f8858182f [file] [log] [blame]
showardd1195652009-12-08 22:21:02 +00001import logging, os
showardfb2a7fa2008-07-17 17:04:12 +00002from datetime import datetime
showard7c785282008-05-29 19:45:12 +00003from django.db import models as dbmodels, connection
jamesren35a70222010-02-16 19:30:46 +00004from xml.sax import saxutils
showardcafd16e2009-05-29 18:37:49 +00005import common
jamesrendd855242010-03-02 22:23:44 +00006from autotest_lib.frontend.afe import model_logic, model_attributes
showardcafd16e2009-05-29 18:37:49 +00007from autotest_lib.frontend import settings, thread_local
showardb1e51872008-10-07 11:08:18 +00008from autotest_lib.client.common_lib import enum, host_protections, global_config
showardeaa408e2009-09-11 18:45:31 +00009from autotest_lib.client.common_lib import host_queue_entry_states
mblighe8819cd2008-02-15 16:48:40 +000010
showard0fc38302008-10-23 00:44:07 +000011# job options and user preferences
jamesrendd855242010-03-02 22:23:44 +000012DEFAULT_REBOOT_BEFORE = model_attributes.RebootBefore.IF_DIRTY
13DEFAULT_REBOOT_AFTER = model_attributes.RebootBefore.ALWAYS
mblighe8819cd2008-02-15 16:48:40 +000014
showard89f84db2009-03-12 20:39:13 +000015
mblighe8819cd2008-02-15 16:48:40 +000016class AclAccessViolation(Exception):
jadmanski0afbb632008-06-06 21:10:57 +000017 """\
18 Raised when an operation is attempted with proper permissions as
19 dictated by ACLs.
20 """
mblighe8819cd2008-02-15 16:48:40 +000021
22
showard205fd602009-03-21 00:17:35 +000023class AtomicGroup(model_logic.ModelWithInvalid, dbmodels.Model):
showard89f84db2009-03-12 20:39:13 +000024 """\
25 An atomic group defines a collection of hosts which must only be scheduled
26 all at once. Any host with a label having an atomic group will only be
27 scheduled for a job at the same time as other hosts sharing that label.
28
29 Required:
30 name: A name for this atomic group. ex: 'rack23' or 'funky_net'
31 max_number_of_machines: The maximum number of machines that will be
32 scheduled at once when scheduling jobs to this atomic group.
33 The job.synch_count is considered the minimum.
34
35 Optional:
36 description: Arbitrary text description of this group's purpose.
37 """
showarda5288b42009-07-28 20:06:08 +000038 name = dbmodels.CharField(max_length=255, unique=True)
showard89f84db2009-03-12 20:39:13 +000039 description = dbmodels.TextField(blank=True)
showarde9450c92009-06-30 01:58:52 +000040 # This magic value is the default to simplify the scheduler logic.
41 # It must be "large". The common use of atomic groups is to want all
42 # machines in the group to be used, limits on which subset used are
43 # often chosen via dependency labels.
44 INFINITE_MACHINES = 333333333
45 max_number_of_machines = dbmodels.IntegerField(default=INFINITE_MACHINES)
showard205fd602009-03-21 00:17:35 +000046 invalid = dbmodels.BooleanField(default=False,
showarda5288b42009-07-28 20:06:08 +000047 editable=settings.FULL_ADMIN)
showard89f84db2009-03-12 20:39:13 +000048
showard89f84db2009-03-12 20:39:13 +000049 name_field = 'name'
jamesrene3656232010-03-02 00:00:30 +000050 objects = model_logic.ModelWithInvalidManager()
showard205fd602009-03-21 00:17:35 +000051 valid_objects = model_logic.ValidObjectsManager()
52
53
showard29f7cd22009-04-29 21:16:24 +000054 def enqueue_job(self, job, is_template=False):
showardc92da832009-04-07 18:14:34 +000055 """Enqueue a job on an associated atomic group of hosts."""
showard29f7cd22009-04-29 21:16:24 +000056 queue_entry = HostQueueEntry.create(atomic_group=self, job=job,
57 is_template=is_template)
showardc92da832009-04-07 18:14:34 +000058 queue_entry.save()
59
60
showard205fd602009-03-21 00:17:35 +000061 def clean_object(self):
62 self.label_set.clear()
showard89f84db2009-03-12 20:39:13 +000063
64
65 class Meta:
showardeab66ce2009-12-23 00:03:56 +000066 db_table = 'afe_atomic_groups'
showard89f84db2009-03-12 20:39:13 +000067
showard205fd602009-03-21 00:17:35 +000068
showarda5288b42009-07-28 20:06:08 +000069 def __unicode__(self):
70 return unicode(self.name)
showard89f84db2009-03-12 20:39:13 +000071
72
showard7c785282008-05-29 19:45:12 +000073class Label(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +000074 """\
75 Required:
showard89f84db2009-03-12 20:39:13 +000076 name: label name
mblighe8819cd2008-02-15 16:48:40 +000077
jadmanski0afbb632008-06-06 21:10:57 +000078 Optional:
showard89f84db2009-03-12 20:39:13 +000079 kernel_config: URL/path to kernel config for jobs run on this label.
80 platform: If True, this is a platform label (defaults to False).
81 only_if_needed: If True, a Host with this label can only be used if that
82 label is requested by the job/test (either as the meta_host or
83 in the job_dependencies).
84 atomic_group: The atomic group associated with this label.
jadmanski0afbb632008-06-06 21:10:57 +000085 """
showarda5288b42009-07-28 20:06:08 +000086 name = dbmodels.CharField(max_length=255, unique=True)
87 kernel_config = dbmodels.CharField(max_length=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +000088 platform = dbmodels.BooleanField(default=False)
89 invalid = dbmodels.BooleanField(default=False,
90 editable=settings.FULL_ADMIN)
showardb1e51872008-10-07 11:08:18 +000091 only_if_needed = dbmodels.BooleanField(default=False)
mblighe8819cd2008-02-15 16:48:40 +000092
jadmanski0afbb632008-06-06 21:10:57 +000093 name_field = 'name'
jamesrene3656232010-03-02 00:00:30 +000094 objects = model_logic.ModelWithInvalidManager()
jadmanski0afbb632008-06-06 21:10:57 +000095 valid_objects = model_logic.ValidObjectsManager()
showard89f84db2009-03-12 20:39:13 +000096 atomic_group = dbmodels.ForeignKey(AtomicGroup, null=True, blank=True)
97
mbligh5244cbb2008-04-24 20:39:52 +000098
jadmanski0afbb632008-06-06 21:10:57 +000099 def clean_object(self):
100 self.host_set.clear()
showard01a51672009-05-29 18:42:37 +0000101 self.test_set.clear()
mblighe8819cd2008-02-15 16:48:40 +0000102
103
showard29f7cd22009-04-29 21:16:24 +0000104 def enqueue_job(self, job, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +0000105 """Enqueue a job on any host of this label."""
showard29f7cd22009-04-29 21:16:24 +0000106 queue_entry = HostQueueEntry.create(meta_host=self, job=job,
107 is_template=is_template,
108 atomic_group=atomic_group)
jadmanski0afbb632008-06-06 21:10:57 +0000109 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000110
111
jadmanski0afbb632008-06-06 21:10:57 +0000112 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000113 db_table = 'afe_labels'
mblighe8819cd2008-02-15 16:48:40 +0000114
showarda5288b42009-07-28 20:06:08 +0000115 def __unicode__(self):
116 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000117
118
jamesren76fcf192010-04-21 20:39:50 +0000119class Drone(dbmodels.Model, model_logic.ModelExtensions):
120 """
121 A scheduler drone
122
123 hostname: the drone's hostname
124 """
125 hostname = dbmodels.CharField(max_length=255, unique=True)
126
127 name_field = 'hostname'
128 objects = model_logic.ExtendedManager()
129
130
131 def save(self, *args, **kwargs):
132 if not User.current_user().is_superuser():
133 raise Exception('Only superusers may edit drones')
134 super(Drone, self).save(*args, **kwargs)
135
136
137 def delete(self):
138 if not User.current_user().is_superuser():
139 raise Exception('Only superusers may delete drones')
140 super(Drone, self).delete()
141
142
143 class Meta:
144 db_table = 'afe_drones'
145
146 def __unicode__(self):
147 return unicode(self.hostname)
148
149
150class DroneSet(dbmodels.Model, model_logic.ModelExtensions):
151 """
152 A set of scheduler drones
153
154 These will be used by the scheduler to decide what drones a job is allowed
155 to run on.
156
157 name: the drone set's name
158 drones: the drones that are part of the set
159 """
160 DRONE_SETS_ENABLED = global_config.global_config.get_config_value(
161 'SCHEDULER', 'drone_sets_enabled', type=bool, default=False)
162 DEFAULT_DRONE_SET_NAME = global_config.global_config.get_config_value(
163 'SCHEDULER', 'default_drone_set_name', default=None)
164
165 name = dbmodels.CharField(max_length=255, unique=True)
166 drones = dbmodels.ManyToManyField(Drone, db_table='afe_drone_sets_drones')
167
168 name_field = 'name'
169 objects = model_logic.ExtendedManager()
170
171
172 def save(self, *args, **kwargs):
173 if not User.current_user().is_superuser():
174 raise Exception('Only superusers may edit drone sets')
175 super(DroneSet, self).save(*args, **kwargs)
176
177
178 def delete(self):
179 if not User.current_user().is_superuser():
180 raise Exception('Only superusers may delete drone sets')
181 super(DroneSet, self).delete()
182
183
184 @classmethod
185 def drone_sets_enabled(cls):
186 return cls.DRONE_SETS_ENABLED
187
188
189 @classmethod
190 def default_drone_set_name(cls):
191 return cls.DEFAULT_DRONE_SET_NAME
192
193
194 @classmethod
195 def get_default(cls):
196 return cls.smart_get(cls.DEFAULT_DRONE_SET_NAME)
197
198
199 @classmethod
200 def resolve_name(cls, drone_set_name):
201 """
202 Returns the name of one of these, if not None, in order of preference:
203 1) the drone set given,
204 2) the current user's default drone set, or
205 3) the global default drone set
206
207 or returns None if drone sets are disabled
208 """
209 if not cls.drone_sets_enabled():
210 return None
211
212 user = User.current_user()
213 user_drone_set_name = user.drone_set and user.drone_set.name
214
215 return drone_set_name or user_drone_set_name or cls.get_default().name
216
217
218 def get_drone_hostnames(self):
219 """
220 Gets the hostnames of all drones in this drone set
221 """
222 return set(self.drones.all().values_list('hostname', flat=True))
223
224
225 class Meta:
226 db_table = 'afe_drone_sets'
227
228 def __unicode__(self):
229 return unicode(self.name)
230
231
showardfb2a7fa2008-07-17 17:04:12 +0000232class User(dbmodels.Model, model_logic.ModelExtensions):
233 """\
234 Required:
235 login :user login name
236
237 Optional:
238 access_level: 0=User (default), 1=Admin, 100=Root
239 """
240 ACCESS_ROOT = 100
241 ACCESS_ADMIN = 1
242 ACCESS_USER = 0
243
showard64a95952010-01-13 21:27:16 +0000244 AUTOTEST_SYSTEM = 'autotest_system'
245
showarda5288b42009-07-28 20:06:08 +0000246 login = dbmodels.CharField(max_length=255, unique=True)
showardfb2a7fa2008-07-17 17:04:12 +0000247 access_level = dbmodels.IntegerField(default=ACCESS_USER, blank=True)
248
showard0fc38302008-10-23 00:44:07 +0000249 # user preferences
jamesrendd855242010-03-02 22:23:44 +0000250 reboot_before = dbmodels.SmallIntegerField(
251 choices=model_attributes.RebootBefore.choices(), blank=True,
252 default=DEFAULT_REBOOT_BEFORE)
253 reboot_after = dbmodels.SmallIntegerField(
254 choices=model_attributes.RebootAfter.choices(), blank=True,
255 default=DEFAULT_REBOOT_AFTER)
jamesren76fcf192010-04-21 20:39:50 +0000256 drone_set = dbmodels.ForeignKey(DroneSet, null=True, blank=True)
showard97db5ba2008-11-12 18:18:02 +0000257 show_experimental = dbmodels.BooleanField(default=False)
showard0fc38302008-10-23 00:44:07 +0000258
showardfb2a7fa2008-07-17 17:04:12 +0000259 name_field = 'login'
260 objects = model_logic.ExtendedManager()
261
262
showarda5288b42009-07-28 20:06:08 +0000263 def save(self, *args, **kwargs):
showardfb2a7fa2008-07-17 17:04:12 +0000264 # is this a new object being saved for the first time?
265 first_time = (self.id is None)
266 user = thread_local.get_user()
showard0fc38302008-10-23 00:44:07 +0000267 if user and not user.is_superuser() and user.login != self.login:
268 raise AclAccessViolation("You cannot modify user " + self.login)
showarda5288b42009-07-28 20:06:08 +0000269 super(User, self).save(*args, **kwargs)
showardfb2a7fa2008-07-17 17:04:12 +0000270 if first_time:
271 everyone = AclGroup.objects.get(name='Everyone')
272 everyone.users.add(self)
273
274
275 def is_superuser(self):
276 return self.access_level >= self.ACCESS_ROOT
277
278
showard64a95952010-01-13 21:27:16 +0000279 @classmethod
280 def current_user(cls):
281 user = thread_local.get_user()
282 if user is None:
showardcfcdd802010-01-15 00:16:33 +0000283 user, _ = cls.objects.get_or_create(login=cls.AUTOTEST_SYSTEM)
showard64a95952010-01-13 21:27:16 +0000284 user.access_level = cls.ACCESS_ROOT
285 user.save()
286 return user
287
288
showardfb2a7fa2008-07-17 17:04:12 +0000289 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000290 db_table = 'afe_users'
showardfb2a7fa2008-07-17 17:04:12 +0000291
showarda5288b42009-07-28 20:06:08 +0000292 def __unicode__(self):
293 return unicode(self.login)
showardfb2a7fa2008-07-17 17:04:12 +0000294
295
showardf8b19042009-05-12 17:22:49 +0000296class Host(model_logic.ModelWithInvalid, dbmodels.Model,
297 model_logic.ModelWithAttributes):
jadmanski0afbb632008-06-06 21:10:57 +0000298 """\
299 Required:
300 hostname
mblighe8819cd2008-02-15 16:48:40 +0000301
jadmanski0afbb632008-06-06 21:10:57 +0000302 optional:
showard21baa452008-10-21 00:08:39 +0000303 locked: if true, host is locked and will not be queued
mblighe8819cd2008-02-15 16:48:40 +0000304
jadmanski0afbb632008-06-06 21:10:57 +0000305 Internal:
306 synch_id: currently unused
307 status: string describing status of host
showard21baa452008-10-21 00:08:39 +0000308 invalid: true if the host has been deleted
309 protection: indicates what can be done to this host during repair
310 locked_by: user that locked the host, or null if the host is unlocked
311 lock_time: DateTime at which the host was locked
312 dirty: true if the host has been used without being rebooted
jadmanski0afbb632008-06-06 21:10:57 +0000313 """
314 Status = enum.Enum('Verifying', 'Running', 'Ready', 'Repairing',
jamesren121eee62010-04-13 19:10:12 +0000315 'Repair Failed', 'Cleaning', 'Pending',
showard6d7b2ff2009-06-10 00:16:47 +0000316 string_values=True)
showard4d233752010-01-20 19:06:40 +0000317 Protection = host_protections.Protection
mblighe8819cd2008-02-15 16:48:40 +0000318
showarda5288b42009-07-28 20:06:08 +0000319 hostname = dbmodels.CharField(max_length=255, unique=True)
showardeab66ce2009-12-23 00:03:56 +0000320 labels = dbmodels.ManyToManyField(Label, blank=True,
321 db_table='afe_hosts_labels')
jadmanski0afbb632008-06-06 21:10:57 +0000322 locked = dbmodels.BooleanField(default=False)
323 synch_id = dbmodels.IntegerField(blank=True, null=True,
324 editable=settings.FULL_ADMIN)
showarda5288b42009-07-28 20:06:08 +0000325 status = dbmodels.CharField(max_length=255, default=Status.READY,
jadmanski0afbb632008-06-06 21:10:57 +0000326 choices=Status.choices(),
327 editable=settings.FULL_ADMIN)
328 invalid = dbmodels.BooleanField(default=False,
329 editable=settings.FULL_ADMIN)
showardd5afc2f2008-08-12 17:19:44 +0000330 protection = dbmodels.SmallIntegerField(null=False, blank=True,
showarddf062562008-07-03 19:56:37 +0000331 choices=host_protections.choices,
332 default=host_protections.default)
showardfb2a7fa2008-07-17 17:04:12 +0000333 locked_by = dbmodels.ForeignKey(User, null=True, blank=True, editable=False)
334 lock_time = dbmodels.DateTimeField(null=True, blank=True, editable=False)
showard21baa452008-10-21 00:08:39 +0000335 dirty = dbmodels.BooleanField(default=True, editable=settings.FULL_ADMIN)
mblighe8819cd2008-02-15 16:48:40 +0000336
jadmanski0afbb632008-06-06 21:10:57 +0000337 name_field = 'hostname'
jamesrene3656232010-03-02 00:00:30 +0000338 objects = model_logic.ModelWithInvalidManager()
jadmanski0afbb632008-06-06 21:10:57 +0000339 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +0000340
showard2bab8f42008-11-12 18:15:22 +0000341
342 def __init__(self, *args, **kwargs):
343 super(Host, self).__init__(*args, **kwargs)
344 self._record_attributes(['status'])
345
346
showardb8471e32008-07-03 19:51:08 +0000347 @staticmethod
348 def create_one_time_host(hostname):
349 query = Host.objects.filter(hostname=hostname)
350 if query.count() == 0:
351 host = Host(hostname=hostname, invalid=True)
showarda8411af2008-08-07 22:35:58 +0000352 host.do_validate()
showardb8471e32008-07-03 19:51:08 +0000353 else:
354 host = query[0]
355 if not host.invalid:
356 raise model_logic.ValidationError({
mblighb5b7b5d2009-02-03 17:47:15 +0000357 'hostname' : '%s already exists in the autotest DB. '
358 'Select it rather than entering it as a one time '
359 'host.' % hostname
showardb8471e32008-07-03 19:51:08 +0000360 })
showard1ab512b2008-07-30 23:39:04 +0000361 host.protection = host_protections.Protection.DO_NOT_REPAIR
showard946a7af2009-04-15 21:53:23 +0000362 host.locked = False
showardb8471e32008-07-03 19:51:08 +0000363 host.save()
showard2924b0a2009-06-18 23:16:15 +0000364 host.clean_object()
showardb8471e32008-07-03 19:51:08 +0000365 return host
mbligh5244cbb2008-04-24 20:39:52 +0000366
showard1ff7b2e2009-05-15 23:17:18 +0000367
showardafd97de2009-10-01 18:45:09 +0000368 def resurrect_object(self, old_object):
369 super(Host, self).resurrect_object(old_object)
370 # invalid hosts can be in use by the scheduler (as one-time hosts), so
371 # don't change the status
372 self.status = old_object.status
373
374
jadmanski0afbb632008-06-06 21:10:57 +0000375 def clean_object(self):
376 self.aclgroup_set.clear()
377 self.labels.clear()
mblighe8819cd2008-02-15 16:48:40 +0000378
379
showarda5288b42009-07-28 20:06:08 +0000380 def save(self, *args, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000381 # extra spaces in the hostname can be a sneaky source of errors
382 self.hostname = self.hostname.strip()
383 # is this a new object being saved for the first time?
384 first_time = (self.id is None)
showard3dd47c22008-07-10 00:41:36 +0000385 if not first_time:
386 AclGroup.check_for_acl_violation_hosts([self])
showardfb2a7fa2008-07-17 17:04:12 +0000387 if self.locked and not self.locked_by:
showard64a95952010-01-13 21:27:16 +0000388 self.locked_by = User.current_user()
showardfb2a7fa2008-07-17 17:04:12 +0000389 self.lock_time = datetime.now()
showard21baa452008-10-21 00:08:39 +0000390 self.dirty = True
showardfb2a7fa2008-07-17 17:04:12 +0000391 elif not self.locked and self.locked_by:
392 self.locked_by = None
393 self.lock_time = None
showarda5288b42009-07-28 20:06:08 +0000394 super(Host, self).save(*args, **kwargs)
jadmanski0afbb632008-06-06 21:10:57 +0000395 if first_time:
396 everyone = AclGroup.objects.get(name='Everyone')
397 everyone.hosts.add(self)
showard2bab8f42008-11-12 18:15:22 +0000398 self._check_for_updated_attributes()
399
mblighe8819cd2008-02-15 16:48:40 +0000400
showardb8471e32008-07-03 19:51:08 +0000401 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000402 AclGroup.check_for_acl_violation_hosts([self])
showardb8471e32008-07-03 19:51:08 +0000403 for queue_entry in self.hostqueueentry_set.all():
404 queue_entry.deleted = True
showard64a95952010-01-13 21:27:16 +0000405 queue_entry.abort()
showardb8471e32008-07-03 19:51:08 +0000406 super(Host, self).delete()
407
mblighe8819cd2008-02-15 16:48:40 +0000408
showard2bab8f42008-11-12 18:15:22 +0000409 def on_attribute_changed(self, attribute, old_value):
410 assert attribute == 'status'
showardf1175bb2009-06-17 19:34:36 +0000411 logging.info(self.hostname + ' -> ' + self.status)
showard2bab8f42008-11-12 18:15:22 +0000412
413
showard29f7cd22009-04-29 21:16:24 +0000414 def enqueue_job(self, job, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +0000415 """Enqueue a job on this host."""
showard29f7cd22009-04-29 21:16:24 +0000416 queue_entry = HostQueueEntry.create(host=self, job=job,
417 is_template=is_template,
418 atomic_group=atomic_group)
jadmanski0afbb632008-06-06 21:10:57 +0000419 # allow recovery of dead hosts from the frontend
420 if not self.active_queue_entry() and self.is_dead():
421 self.status = Host.Status.READY
422 self.save()
423 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000424
showard08f981b2008-06-24 21:59:03 +0000425 block = IneligibleHostQueue(job=job, host=self)
426 block.save()
427
mblighe8819cd2008-02-15 16:48:40 +0000428
jadmanski0afbb632008-06-06 21:10:57 +0000429 def platform(self):
430 # TODO(showard): slighly hacky?
431 platforms = self.labels.filter(platform=True)
432 if len(platforms) == 0:
433 return None
434 return platforms[0]
435 platform.short_description = 'Platform'
mblighe8819cd2008-02-15 16:48:40 +0000436
437
showardcafd16e2009-05-29 18:37:49 +0000438 @classmethod
439 def check_no_platform(cls, hosts):
440 Host.objects.populate_relationships(hosts, Label, 'label_list')
441 errors = []
442 for host in hosts:
443 platforms = [label.name for label in host.label_list
444 if label.platform]
445 if platforms:
446 # do a join, just in case this host has multiple platforms,
447 # we'll be able to see it
448 errors.append('Host %s already has a platform: %s' % (
449 host.hostname, ', '.join(platforms)))
450 if errors:
451 raise model_logic.ValidationError({'labels': '; '.join(errors)})
452
453
jadmanski0afbb632008-06-06 21:10:57 +0000454 def is_dead(self):
455 return self.status == Host.Status.REPAIR_FAILED
mbligh3cab4a72008-03-05 23:19:09 +0000456
457
jadmanski0afbb632008-06-06 21:10:57 +0000458 def active_queue_entry(self):
459 active = list(self.hostqueueentry_set.filter(active=True))
460 if not active:
461 return None
462 assert len(active) == 1, ('More than one active entry for '
463 'host ' + self.hostname)
464 return active[0]
mblighe8819cd2008-02-15 16:48:40 +0000465
466
showardf8b19042009-05-12 17:22:49 +0000467 def _get_attribute_model_and_args(self, attribute):
468 return HostAttribute, dict(host=self, attribute=attribute)
showard0957a842009-05-11 19:25:08 +0000469
470
jadmanski0afbb632008-06-06 21:10:57 +0000471 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000472 db_table = 'afe_hosts'
mblighe8819cd2008-02-15 16:48:40 +0000473
showarda5288b42009-07-28 20:06:08 +0000474 def __unicode__(self):
475 return unicode(self.hostname)
mblighe8819cd2008-02-15 16:48:40 +0000476
477
showard0957a842009-05-11 19:25:08 +0000478class HostAttribute(dbmodels.Model):
479 """Arbitrary keyvals associated with hosts."""
480 host = dbmodels.ForeignKey(Host)
showarda5288b42009-07-28 20:06:08 +0000481 attribute = dbmodels.CharField(max_length=90)
482 value = dbmodels.CharField(max_length=300)
showard0957a842009-05-11 19:25:08 +0000483
484 objects = model_logic.ExtendedManager()
485
486 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000487 db_table = 'afe_host_attributes'
showard0957a842009-05-11 19:25:08 +0000488
489
showard7c785282008-05-29 19:45:12 +0000490class Test(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000491 """\
492 Required:
showard909c7a62008-07-15 21:52:38 +0000493 author: author name
494 description: description of the test
jadmanski0afbb632008-06-06 21:10:57 +0000495 name: test name
showard909c7a62008-07-15 21:52:38 +0000496 time: short, medium, long
497 test_class: This describes the class for your the test belongs in.
498 test_category: This describes the category for your tests
jadmanski0afbb632008-06-06 21:10:57 +0000499 test_type: Client or Server
500 path: path to pass to run_test()
showard909c7a62008-07-15 21:52:38 +0000501 sync_count: is a number >=1 (1 being the default). If it's 1, then it's an
502 async job. If it's >1 it's sync job for that number of machines
showard2bab8f42008-11-12 18:15:22 +0000503 i.e. if sync_count = 2 it is a sync job that requires two
504 machines.
jadmanski0afbb632008-06-06 21:10:57 +0000505 Optional:
showard909c7a62008-07-15 21:52:38 +0000506 dependencies: What the test requires to run. Comma deliminated list
showard989f25d2008-10-01 11:38:11 +0000507 dependency_labels: many-to-many relationship with labels corresponding to
508 test dependencies.
showard909c7a62008-07-15 21:52:38 +0000509 experimental: If this is set to True production servers will ignore the test
510 run_verify: Whether or not the scheduler should run the verify stage
jadmanski0afbb632008-06-06 21:10:57 +0000511 """
showard909c7a62008-07-15 21:52:38 +0000512 TestTime = enum.Enum('SHORT', 'MEDIUM', 'LONG', start_value=1)
jamesrencd7a81a2010-04-21 20:39:08 +0000513 TestTypes = model_attributes.TestTypes
jadmanski0afbb632008-06-06 21:10:57 +0000514 # TODO(showard) - this should be merged with Job.ControlType (but right
515 # now they use opposite values)
mblighe8819cd2008-02-15 16:48:40 +0000516
showarda5288b42009-07-28 20:06:08 +0000517 name = dbmodels.CharField(max_length=255, unique=True)
518 author = dbmodels.CharField(max_length=255)
519 test_class = dbmodels.CharField(max_length=255)
520 test_category = dbmodels.CharField(max_length=255)
521 dependencies = dbmodels.CharField(max_length=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000522 description = dbmodels.TextField(blank=True)
showard909c7a62008-07-15 21:52:38 +0000523 experimental = dbmodels.BooleanField(default=True)
524 run_verify = dbmodels.BooleanField(default=True)
525 test_time = dbmodels.SmallIntegerField(choices=TestTime.choices(),
526 default=TestTime.MEDIUM)
jamesrencd7a81a2010-04-21 20:39:08 +0000527 test_type = dbmodels.SmallIntegerField(choices=TestTypes.choices())
showard909c7a62008-07-15 21:52:38 +0000528 sync_count = dbmodels.IntegerField(default=1)
showarda5288b42009-07-28 20:06:08 +0000529 path = dbmodels.CharField(max_length=255, unique=True)
mblighe8819cd2008-02-15 16:48:40 +0000530
showardeab66ce2009-12-23 00:03:56 +0000531 dependency_labels = (
532 dbmodels.ManyToManyField(Label, blank=True,
533 db_table='afe_autotests_dependency_labels'))
jadmanski0afbb632008-06-06 21:10:57 +0000534 name_field = 'name'
535 objects = model_logic.ExtendedManager()
mblighe8819cd2008-02-15 16:48:40 +0000536
537
jamesren35a70222010-02-16 19:30:46 +0000538 def admin_description(self):
539 escaped_description = saxutils.escape(self.description)
540 return '<span style="white-space:pre">%s</span>' % escaped_description
541 admin_description.allow_tags = True
jamesrencae88c62010-02-19 00:12:28 +0000542 admin_description.short_description = 'Description'
jamesren35a70222010-02-16 19:30:46 +0000543
544
jadmanski0afbb632008-06-06 21:10:57 +0000545 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000546 db_table = 'afe_autotests'
mblighe8819cd2008-02-15 16:48:40 +0000547
showarda5288b42009-07-28 20:06:08 +0000548 def __unicode__(self):
549 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000550
551
jamesren4a41e012010-07-16 22:33:48 +0000552class TestParameter(dbmodels.Model):
553 """
554 A declared parameter of a test
555 """
556 test = dbmodels.ForeignKey(Test)
557 name = dbmodels.CharField(max_length=255)
558
559 class Meta:
560 db_table = 'afe_test_parameters'
561 unique_together = ('test', 'name')
562
563 def __unicode__(self):
564 return u'%s (%s)' % (self.name, test.name)
565
566
showard2b9a88b2008-06-13 20:55:03 +0000567class Profiler(dbmodels.Model, model_logic.ModelExtensions):
568 """\
569 Required:
570 name: profiler name
571 test_type: Client or Server
572
573 Optional:
574 description: arbirary text description
575 """
showarda5288b42009-07-28 20:06:08 +0000576 name = dbmodels.CharField(max_length=255, unique=True)
showard2b9a88b2008-06-13 20:55:03 +0000577 description = dbmodels.TextField(blank=True)
578
579 name_field = 'name'
580 objects = model_logic.ExtendedManager()
581
582
583 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000584 db_table = 'afe_profilers'
showard2b9a88b2008-06-13 20:55:03 +0000585
showarda5288b42009-07-28 20:06:08 +0000586 def __unicode__(self):
587 return unicode(self.name)
showard2b9a88b2008-06-13 20:55:03 +0000588
589
showard7c785282008-05-29 19:45:12 +0000590class AclGroup(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000591 """\
592 Required:
593 name: name of ACL group
mblighe8819cd2008-02-15 16:48:40 +0000594
jadmanski0afbb632008-06-06 21:10:57 +0000595 Optional:
596 description: arbitrary description of group
597 """
showarda5288b42009-07-28 20:06:08 +0000598 name = dbmodels.CharField(max_length=255, unique=True)
599 description = dbmodels.CharField(max_length=255, blank=True)
showardeab66ce2009-12-23 00:03:56 +0000600 users = dbmodels.ManyToManyField(User, blank=False,
601 db_table='afe_acl_groups_users')
602 hosts = dbmodels.ManyToManyField(Host, blank=True,
603 db_table='afe_acl_groups_hosts')
mblighe8819cd2008-02-15 16:48:40 +0000604
jadmanski0afbb632008-06-06 21:10:57 +0000605 name_field = 'name'
606 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000607
showard08f981b2008-06-24 21:59:03 +0000608 @staticmethod
showard3dd47c22008-07-10 00:41:36 +0000609 def check_for_acl_violation_hosts(hosts):
showard64a95952010-01-13 21:27:16 +0000610 user = User.current_user()
showard3dd47c22008-07-10 00:41:36 +0000611 if user.is_superuser():
showard9dbdcda2008-10-14 17:34:36 +0000612 return
showard3dd47c22008-07-10 00:41:36 +0000613 accessible_host_ids = set(
showardd9ac4452009-02-07 02:04:37 +0000614 host.id for host in Host.objects.filter(aclgroup__users=user))
showard3dd47c22008-07-10 00:41:36 +0000615 for host in hosts:
616 # Check if the user has access to this host,
showard98ead172009-06-22 18:13:24 +0000617 # but only if it is not a metahost or a one-time-host
618 no_access = (isinstance(host, Host)
619 and not host.invalid
620 and int(host.id) not in accessible_host_ids)
621 if no_access:
showardeaa408e2009-09-11 18:45:31 +0000622 raise AclAccessViolation("%s does not have access to %s" %
623 (str(user), str(host)))
showard3dd47c22008-07-10 00:41:36 +0000624
showard9dbdcda2008-10-14 17:34:36 +0000625
626 @staticmethod
showarddc817512008-11-12 18:16:41 +0000627 def check_abort_permissions(queue_entries):
628 """
629 look for queue entries that aren't abortable, meaning
630 * the job isn't owned by this user, and
631 * the machine isn't ACL-accessible, or
632 * the machine is in the "Everyone" ACL
633 """
showard64a95952010-01-13 21:27:16 +0000634 user = User.current_user()
showard9dbdcda2008-10-14 17:34:36 +0000635 if user.is_superuser():
636 return
showarddc817512008-11-12 18:16:41 +0000637 not_owned = queue_entries.exclude(job__owner=user.login)
638 # I do this using ID sets instead of just Django filters because
showarda5288b42009-07-28 20:06:08 +0000639 # filtering on M2M dbmodels is broken in Django 0.96. It's better in
640 # 1.0.
641 # TODO: Use Django filters, now that we're using 1.0.
showarddc817512008-11-12 18:16:41 +0000642 accessible_ids = set(
643 entry.id for entry
showardd9ac4452009-02-07 02:04:37 +0000644 in not_owned.filter(host__aclgroup__users__login=user.login))
showarddc817512008-11-12 18:16:41 +0000645 public_ids = set(entry.id for entry
showardd9ac4452009-02-07 02:04:37 +0000646 in not_owned.filter(host__aclgroup__name='Everyone'))
showarddc817512008-11-12 18:16:41 +0000647 cannot_abort = [entry for entry in not_owned.select_related()
648 if entry.id not in accessible_ids
649 or entry.id in public_ids]
650 if len(cannot_abort) == 0:
651 return
652 entry_names = ', '.join('%s-%s/%s' % (entry.job.id, entry.job.owner,
showard3f15eed2008-11-14 22:40:48 +0000653 entry.host_or_metahost_name())
showarddc817512008-11-12 18:16:41 +0000654 for entry in cannot_abort)
655 raise AclAccessViolation('You cannot abort the following job entries: '
656 + entry_names)
showard9dbdcda2008-10-14 17:34:36 +0000657
658
showard3dd47c22008-07-10 00:41:36 +0000659 def check_for_acl_violation_acl_group(self):
showard64a95952010-01-13 21:27:16 +0000660 user = User.current_user()
showard3dd47c22008-07-10 00:41:36 +0000661 if user.is_superuser():
showard8cbaf1e2009-09-08 16:27:04 +0000662 return
663 if self.name == 'Everyone':
664 raise AclAccessViolation("You cannot modify 'Everyone'!")
showard3dd47c22008-07-10 00:41:36 +0000665 if not user in self.users.all():
666 raise AclAccessViolation("You do not have access to %s"
667 % self.name)
668
669 @staticmethod
showard08f981b2008-06-24 21:59:03 +0000670 def on_host_membership_change():
671 everyone = AclGroup.objects.get(name='Everyone')
672
showard3dd47c22008-07-10 00:41:36 +0000673 # find hosts that aren't in any ACL group and add them to Everyone
showard08f981b2008-06-24 21:59:03 +0000674 # TODO(showard): this is a bit of a hack, since the fact that this query
675 # works is kind of a coincidence of Django internals. This trick
676 # doesn't work in general (on all foreign key relationships). I'll
677 # replace it with a better technique when the need arises.
showardd9ac4452009-02-07 02:04:37 +0000678 orphaned_hosts = Host.valid_objects.filter(aclgroup__id__isnull=True)
showard08f981b2008-06-24 21:59:03 +0000679 everyone.hosts.add(*orphaned_hosts.distinct())
680
681 # find hosts in both Everyone and another ACL group, and remove them
682 # from Everyone
showarda5288b42009-07-28 20:06:08 +0000683 hosts_in_everyone = Host.valid_objects.filter(aclgroup__name='Everyone')
684 acled_hosts = set()
685 for host in hosts_in_everyone:
686 # Has an ACL group other than Everyone
687 if host.aclgroup_set.count() > 1:
688 acled_hosts.add(host)
689 everyone.hosts.remove(*acled_hosts)
showard08f981b2008-06-24 21:59:03 +0000690
691
692 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000693 if (self.name == 'Everyone'):
694 raise AclAccessViolation("You cannot delete 'Everyone'!")
695 self.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000696 super(AclGroup, self).delete()
697 self.on_host_membership_change()
698
699
showard04f2cd82008-07-25 20:53:31 +0000700 def add_current_user_if_empty(self):
701 if not self.users.count():
showard64a95952010-01-13 21:27:16 +0000702 self.users.add(User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000703
704
showard8cbaf1e2009-09-08 16:27:04 +0000705 def perform_after_save(self, change):
706 if not change:
showard64a95952010-01-13 21:27:16 +0000707 self.users.add(User.current_user())
showard8cbaf1e2009-09-08 16:27:04 +0000708 self.add_current_user_if_empty()
709 self.on_host_membership_change()
710
711
712 def save(self, *args, **kwargs):
713 change = bool(self.id)
714 if change:
715 # Check the original object for an ACL violation
716 AclGroup.objects.get(id=self.id).check_for_acl_violation_acl_group()
717 super(AclGroup, self).save(*args, **kwargs)
718 self.perform_after_save(change)
719
showardeb3be4d2008-04-21 20:59:26 +0000720
jadmanski0afbb632008-06-06 21:10:57 +0000721 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000722 db_table = 'afe_acl_groups'
mblighe8819cd2008-02-15 16:48:40 +0000723
showarda5288b42009-07-28 20:06:08 +0000724 def __unicode__(self):
725 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000726
mblighe8819cd2008-02-15 16:48:40 +0000727
jamesren4a41e012010-07-16 22:33:48 +0000728class Kernel(dbmodels.Model):
729 """
730 A kernel configuration for a parameterized job
731 """
732 version = dbmodels.CharField(max_length=255)
733 cmdline = dbmodels.CharField(max_length=255, blank=True)
734
735 @classmethod
736 def create_kernels(cls, kernel_list):
737 """
738 Creates all kernels in the kernel list
739
740 @param kernel_list A list of dictionaries that describe the kernels, in
741 the same format as the 'kernel' argument to
742 rpc_interface.generate_control_file
743 @returns a list of the created kernels
744 """
745 if not kernel_list:
746 return None
747 return [cls._create(kernel) for kernel in kernel_list]
748
749
750 @classmethod
751 def _create(cls, kernel_dict):
752 version = kernel_dict.pop('version')
753 cmdline = kernel_dict.pop('cmdline', '')
754
755 if kernel_dict:
756 raise Exception('Extraneous kernel arguments remain: %r'
757 % kernel_dict)
758
759 kernel, _ = cls.objects.get_or_create(version=version,
760 cmdline=cmdline)
761 return kernel
762
763
764 class Meta:
765 db_table = 'afe_kernels'
766 unique_together = ('version', 'cmdline')
767
768 def __unicode__(self):
769 return u'%s %s' % (self.version, self.cmdline)
770
771
772class ParameterizedJob(dbmodels.Model):
773 """
774 Auxiliary configuration for a parameterized job
775 """
776 test = dbmodels.ForeignKey(Test)
777 label = dbmodels.ForeignKey(Label, null=True)
778 use_container = dbmodels.BooleanField(default=False)
779 profile_only = dbmodels.BooleanField(default=False)
780 upload_kernel_config = dbmodels.BooleanField(default=False)
781
782 kernels = dbmodels.ManyToManyField(
783 Kernel, db_table='afe_parameterized_job_kernels')
784 profilers = dbmodels.ManyToManyField(
785 Profiler, through='ParameterizedJobProfiler')
786
787
788 @classmethod
789 def smart_get(cls, id_or_name, *args, **kwargs):
790 """For compatibility with Job.add_object"""
791 return cls.objects.get(pk=id_or_name)
792
793
794 def job(self):
795 jobs = self.job_set.all()
796 assert jobs.count() <= 1
797 return jobs and jobs[0] or None
798
799
800 class Meta:
801 db_table = 'afe_parameterized_jobs'
802
803 def __unicode__(self):
804 return u'%s (parameterized) - %s' % (self.test.name, self.job())
805
806
807class ParameterizedJobProfiler(dbmodels.Model):
808 """
809 A profiler to run on a parameterized job
810 """
811 parameterized_job = dbmodels.ForeignKey(ParameterizedJob)
812 profiler = dbmodels.ForeignKey(Profiler)
813
814 class Meta:
815 db_table = 'afe_parameterized_jobs_profilers'
816 unique_together = ('parameterized_job', 'profiler')
817
818
819class ParameterizedJobProfilerParameter(dbmodels.Model):
820 """
821 A parameter for a profiler in a parameterized job
822 """
823 parameterized_job_profiler = dbmodels.ForeignKey(ParameterizedJobProfiler)
824 parameter_name = dbmodels.CharField(max_length=255)
825 parameter_value = dbmodels.TextField()
826 parameter_type = dbmodels.CharField(
827 max_length=8, choices=model_attributes.ParameterTypes.choices())
828
829 class Meta:
830 db_table = 'afe_parameterized_job_profiler_parameters'
831 unique_together = ('parameterized_job_profiler', 'parameter_name')
832
833 def __unicode__(self):
834 return u'%s - %s' % (self.parameterized_job_profiler.profiler.name,
835 self.parameter_name)
836
837
838class ParameterizedJobParameter(dbmodels.Model):
839 """
840 Parameters for a parameterized job
841 """
842 parameterized_job = dbmodels.ForeignKey(ParameterizedJob)
843 test_parameter = dbmodels.ForeignKey(TestParameter)
844 parameter_value = dbmodels.TextField()
845 parameter_type = dbmodels.CharField(
846 max_length=8, choices=model_attributes.ParameterTypes.choices())
847
848 class Meta:
849 db_table = 'afe_parameterized_job_parameters'
850 unique_together = ('parameterized_job', 'test_parameter')
851
852 def __unicode__(self):
853 return u'%s - %s' % (self.parameterized_job.job().name,
854 self.test_parameter.name)
855
856
showard7c785282008-05-29 19:45:12 +0000857class JobManager(model_logic.ExtendedManager):
jadmanski0afbb632008-06-06 21:10:57 +0000858 'Custom manager to provide efficient status counts querying.'
859 def get_status_counts(self, job_ids):
860 """\
861 Returns a dictionary mapping the given job IDs to their status
862 count dictionaries.
863 """
864 if not job_ids:
865 return {}
866 id_list = '(%s)' % ','.join(str(job_id) for job_id in job_ids)
867 cursor = connection.cursor()
868 cursor.execute("""
showardd3dc1992009-04-22 21:01:40 +0000869 SELECT job_id, status, aborted, complete, COUNT(*)
showardeab66ce2009-12-23 00:03:56 +0000870 FROM afe_host_queue_entries
jadmanski0afbb632008-06-06 21:10:57 +0000871 WHERE job_id IN %s
showardd3dc1992009-04-22 21:01:40 +0000872 GROUP BY job_id, status, aborted, complete
jadmanski0afbb632008-06-06 21:10:57 +0000873 """ % id_list)
showard25aaf3f2009-06-08 23:23:40 +0000874 all_job_counts = dict((job_id, {}) for job_id in job_ids)
showardd3dc1992009-04-22 21:01:40 +0000875 for job_id, status, aborted, complete, count in cursor.fetchall():
showard25aaf3f2009-06-08 23:23:40 +0000876 job_dict = all_job_counts[job_id]
showardd3dc1992009-04-22 21:01:40 +0000877 full_status = HostQueueEntry.compute_full_status(status, aborted,
878 complete)
showardb6d16622009-05-26 19:35:29 +0000879 job_dict.setdefault(full_status, 0)
showard25aaf3f2009-06-08 23:23:40 +0000880 job_dict[full_status] += count
jadmanski0afbb632008-06-06 21:10:57 +0000881 return all_job_counts
mblighe8819cd2008-02-15 16:48:40 +0000882
883
showard7c785282008-05-29 19:45:12 +0000884class Job(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000885 """\
886 owner: username of job owner
887 name: job name (does not have to be unique)
888 priority: Low, Medium, High, Urgent (or 0-3)
889 control_file: contents of control file
890 control_type: Client or Server
891 created_on: date of job creation
892 submitted_on: date of job submission
showard2bab8f42008-11-12 18:15:22 +0000893 synch_count: how many hosts should be used per autoserv execution
showard909c7a62008-07-15 21:52:38 +0000894 run_verify: Whether or not to run the verify phase
showard12f3e322009-05-13 21:27:42 +0000895 timeout: hours from queuing time until job times out
896 max_runtime_hrs: hours from job starting time until job times out
showard542e8402008-09-19 20:16:18 +0000897 email_list: list of people to email on completion delimited by any of:
898 white space, ',', ':', ';'
showard989f25d2008-10-01 11:38:11 +0000899 dependency_labels: many-to-many relationship with labels corresponding to
900 job dependencies
showard21baa452008-10-21 00:08:39 +0000901 reboot_before: Never, If dirty, or Always
902 reboot_after: Never, If all tests passed, or Always
showarda1e74b32009-05-12 17:32:04 +0000903 parse_failed_repair: if True, a failed repair launched by this job will have
904 its results parsed as part of the job.
jamesren76fcf192010-04-21 20:39:50 +0000905 drone_set: The set of drones to run this job on
jadmanski0afbb632008-06-06 21:10:57 +0000906 """
showardb1e51872008-10-07 11:08:18 +0000907 DEFAULT_TIMEOUT = global_config.global_config.get_config_value(
908 'AUTOTEST_WEB', 'job_timeout_default', default=240)
showard12f3e322009-05-13 21:27:42 +0000909 DEFAULT_MAX_RUNTIME_HRS = global_config.global_config.get_config_value(
910 'AUTOTEST_WEB', 'job_max_runtime_hrs_default', default=72)
showarda1e74b32009-05-12 17:32:04 +0000911 DEFAULT_PARSE_FAILED_REPAIR = global_config.global_config.get_config_value(
912 'AUTOTEST_WEB', 'parse_failed_repair_default', type=bool,
913 default=False)
showardb1e51872008-10-07 11:08:18 +0000914
jadmanski0afbb632008-06-06 21:10:57 +0000915 Priority = enum.Enum('Low', 'Medium', 'High', 'Urgent')
916 ControlType = enum.Enum('Server', 'Client', start_value=1)
mblighe8819cd2008-02-15 16:48:40 +0000917
showarda5288b42009-07-28 20:06:08 +0000918 owner = dbmodels.CharField(max_length=255)
919 name = dbmodels.CharField(max_length=255)
jadmanski0afbb632008-06-06 21:10:57 +0000920 priority = dbmodels.SmallIntegerField(choices=Priority.choices(),
921 blank=True, # to allow 0
922 default=Priority.MEDIUM)
jamesren4a41e012010-07-16 22:33:48 +0000923 control_file = dbmodels.TextField(null=True, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000924 control_type = dbmodels.SmallIntegerField(choices=ControlType.choices(),
showardb1e51872008-10-07 11:08:18 +0000925 blank=True, # to allow 0
926 default=ControlType.CLIENT)
showard68c7aa02008-10-09 16:49:11 +0000927 created_on = dbmodels.DateTimeField()
showard2bab8f42008-11-12 18:15:22 +0000928 synch_count = dbmodels.IntegerField(null=True, default=1)
showardb1e51872008-10-07 11:08:18 +0000929 timeout = dbmodels.IntegerField(default=DEFAULT_TIMEOUT)
showard9976ce92008-10-15 20:28:13 +0000930 run_verify = dbmodels.BooleanField(default=True)
showarda5288b42009-07-28 20:06:08 +0000931 email_list = dbmodels.CharField(max_length=250, blank=True)
showardeab66ce2009-12-23 00:03:56 +0000932 dependency_labels = (
933 dbmodels.ManyToManyField(Label, blank=True,
934 db_table='afe_jobs_dependency_labels'))
jamesrendd855242010-03-02 22:23:44 +0000935 reboot_before = dbmodels.SmallIntegerField(
936 choices=model_attributes.RebootBefore.choices(), blank=True,
937 default=DEFAULT_REBOOT_BEFORE)
938 reboot_after = dbmodels.SmallIntegerField(
939 choices=model_attributes.RebootAfter.choices(), blank=True,
940 default=DEFAULT_REBOOT_AFTER)
showarda1e74b32009-05-12 17:32:04 +0000941 parse_failed_repair = dbmodels.BooleanField(
942 default=DEFAULT_PARSE_FAILED_REPAIR)
showard12f3e322009-05-13 21:27:42 +0000943 max_runtime_hrs = dbmodels.IntegerField(default=DEFAULT_MAX_RUNTIME_HRS)
jamesren76fcf192010-04-21 20:39:50 +0000944 drone_set = dbmodels.ForeignKey(DroneSet, null=True, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000945
jamesren4a41e012010-07-16 22:33:48 +0000946 parameterized_job = dbmodels.ForeignKey(ParameterizedJob, null=True,
947 blank=True)
948
mblighe8819cd2008-02-15 16:48:40 +0000949
jadmanski0afbb632008-06-06 21:10:57 +0000950 # custom manager
951 objects = JobManager()
mblighe8819cd2008-02-15 16:48:40 +0000952
953
jadmanski0afbb632008-06-06 21:10:57 +0000954 def is_server_job(self):
955 return self.control_type == self.ControlType.SERVER
mblighe8819cd2008-02-15 16:48:40 +0000956
957
jadmanski0afbb632008-06-06 21:10:57 +0000958 @classmethod
jamesren4a41e012010-07-16 22:33:48 +0000959 def parameterized_jobs_enabled(cls):
960 return global_config.global_config.get_config_value(
961 'AUTOTEST_WEB', 'parameterized_jobs', type=bool)
962
963
964 @classmethod
965 def check_parameterized_job(cls, control_file, parameterized_job):
966 """
967 Checks that the job is valid given the global config settings
968
969 First, either control_file must be set, or parameterized_job must be
970 set, but not both. Second, parameterized_job must be set if and only if
971 the parameterized_jobs option in the global config is set to True.
972 """
973 if not (bool(control_file) ^ bool(parameterized_job)):
974 raise Exception('Job must have either control file or '
975 'parameterization, but not both')
976
977 parameterized_jobs_enabled = cls.parameterized_jobs_enabled()
978 if control_file and parameterized_jobs_enabled:
979 raise Exception('Control file specified, but parameterized jobs '
980 'are enabled')
981 if parameterized_job and not parameterized_jobs_enabled:
982 raise Exception('Parameterized job specified, but parameterized '
983 'jobs are not enabled')
984
985
986 @classmethod
showarda1e74b32009-05-12 17:32:04 +0000987 def create(cls, owner, options, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000988 """\
989 Creates a job by taking some information (the listed args)
990 and filling in the rest of the necessary information.
991 """
showard3dd47c22008-07-10 00:41:36 +0000992 AclGroup.check_for_acl_violation_hosts(hosts)
showard4d233752010-01-20 19:06:40 +0000993
jamesren4a41e012010-07-16 22:33:48 +0000994 control_file = options.get('control_file')
995 parameterized_job = options.get('parameterized_job')
jamesren4a41e012010-07-16 22:33:48 +0000996
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800997 # The current implementation of parameterized jobs requires that only
998 # control files or parameterized jobs are used. Using the image
999 # parameter on autoupdate_ParameterizedJob doesn't mix pure
1000 # parameterized jobs and control files jobs, it does muck enough with
1001 # normal jobs by adding a parameterized id to them that this check will
1002 # fail. So for now we just skip this check.
1003 # cls.check_parameterized_job(control_file=control_file,
1004 # parameterized_job=parameterized_job)
showard4d233752010-01-20 19:06:40 +00001005 user = User.current_user()
1006 if options.get('reboot_before') is None:
1007 options['reboot_before'] = user.get_reboot_before_display()
1008 if options.get('reboot_after') is None:
1009 options['reboot_after'] = user.get_reboot_after_display()
1010
jamesren76fcf192010-04-21 20:39:50 +00001011 drone_set = DroneSet.resolve_name(options.get('drone_set'))
1012
jadmanski0afbb632008-06-06 21:10:57 +00001013 job = cls.add_object(
showarda1e74b32009-05-12 17:32:04 +00001014 owner=owner,
1015 name=options['name'],
1016 priority=options['priority'],
jamesren4a41e012010-07-16 22:33:48 +00001017 control_file=control_file,
showarda1e74b32009-05-12 17:32:04 +00001018 control_type=options['control_type'],
1019 synch_count=options.get('synch_count'),
1020 timeout=options.get('timeout'),
showard12f3e322009-05-13 21:27:42 +00001021 max_runtime_hrs=options.get('max_runtime_hrs'),
showarda1e74b32009-05-12 17:32:04 +00001022 run_verify=options.get('run_verify'),
1023 email_list=options.get('email_list'),
1024 reboot_before=options.get('reboot_before'),
1025 reboot_after=options.get('reboot_after'),
1026 parse_failed_repair=options.get('parse_failed_repair'),
jamesren76fcf192010-04-21 20:39:50 +00001027 created_on=datetime.now(),
jamesren4a41e012010-07-16 22:33:48 +00001028 drone_set=drone_set,
1029 parameterized_job=parameterized_job)
mblighe8819cd2008-02-15 16:48:40 +00001030
showarda1e74b32009-05-12 17:32:04 +00001031 job.dependency_labels = options['dependencies']
showardc1a98d12010-01-15 00:22:22 +00001032
jamesrend8b6e172010-04-16 23:45:00 +00001033 if options.get('keyvals'):
showardc1a98d12010-01-15 00:22:22 +00001034 for key, value in options['keyvals'].iteritems():
1035 JobKeyval.objects.create(job=job, key=key, value=value)
1036
jadmanski0afbb632008-06-06 21:10:57 +00001037 return job
mblighe8819cd2008-02-15 16:48:40 +00001038
1039
jamesren4a41e012010-07-16 22:33:48 +00001040 def save(self, *args, **kwargs):
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -08001041 # The current implementation of parameterized jobs requires that only
1042 # control files or parameterized jobs are used. Using the image
1043 # parameter on autoupdate_ParameterizedJob doesn't mix pure
1044 # parameterized jobs and control files jobs, it does muck enough with
1045 # normal jobs by adding a parameterized id to them that this check will
1046 # fail. So for now we just skip this check.
1047 # cls.check_parameterized_job(control_file=self.control_file,
1048 # parameterized_job=self.parameterized_job)
jamesren4a41e012010-07-16 22:33:48 +00001049 super(Job, self).save(*args, **kwargs)
1050
1051
showard29f7cd22009-04-29 21:16:24 +00001052 def queue(self, hosts, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +00001053 """Enqueue a job on the given hosts."""
showarda9545c02009-12-18 22:44:26 +00001054 if not hosts:
1055 if atomic_group:
1056 # No hosts or labels are required to queue an atomic group
1057 # Job. However, if they are given, we respect them below.
1058 atomic_group.enqueue_job(self, is_template=is_template)
1059 else:
1060 # hostless job
1061 entry = HostQueueEntry.create(job=self, is_template=is_template)
1062 entry.save()
1063 return
1064
jadmanski0afbb632008-06-06 21:10:57 +00001065 for host in hosts:
showard29f7cd22009-04-29 21:16:24 +00001066 host.enqueue_job(self, atomic_group=atomic_group,
1067 is_template=is_template)
1068
1069
1070 def create_recurring_job(self, start_date, loop_period, loop_count, owner):
1071 rec = RecurringRun(job=self, start_date=start_date,
1072 loop_period=loop_period,
1073 loop_count=loop_count,
1074 owner=User.objects.get(login=owner))
1075 rec.save()
1076 return rec.id
mblighe8819cd2008-02-15 16:48:40 +00001077
1078
jadmanski0afbb632008-06-06 21:10:57 +00001079 def user(self):
1080 try:
1081 return User.objects.get(login=self.owner)
1082 except self.DoesNotExist:
1083 return None
mblighe8819cd2008-02-15 16:48:40 +00001084
1085
showard64a95952010-01-13 21:27:16 +00001086 def abort(self):
showard98863972008-10-29 21:14:56 +00001087 for queue_entry in self.hostqueueentry_set.all():
showard64a95952010-01-13 21:27:16 +00001088 queue_entry.abort()
showard98863972008-10-29 21:14:56 +00001089
1090
showardd1195652009-12-08 22:21:02 +00001091 def tag(self):
1092 return '%s-%s' % (self.id, self.owner)
1093
1094
showardc1a98d12010-01-15 00:22:22 +00001095 def keyval_dict(self):
1096 return dict((keyval.key, keyval.value)
1097 for keyval in self.jobkeyval_set.all())
1098
1099
jadmanski0afbb632008-06-06 21:10:57 +00001100 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001101 db_table = 'afe_jobs'
mblighe8819cd2008-02-15 16:48:40 +00001102
showarda5288b42009-07-28 20:06:08 +00001103 def __unicode__(self):
1104 return u'%s (%s-%s)' % (self.name, self.id, self.owner)
mblighe8819cd2008-02-15 16:48:40 +00001105
1106
showardc1a98d12010-01-15 00:22:22 +00001107class JobKeyval(dbmodels.Model, model_logic.ModelExtensions):
1108 """Keyvals associated with jobs"""
1109 job = dbmodels.ForeignKey(Job)
1110 key = dbmodels.CharField(max_length=90)
1111 value = dbmodels.CharField(max_length=300)
1112
1113 objects = model_logic.ExtendedManager()
1114
1115 class Meta:
1116 db_table = 'afe_job_keyvals'
1117
1118
showard7c785282008-05-29 19:45:12 +00001119class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +00001120 job = dbmodels.ForeignKey(Job)
1121 host = dbmodels.ForeignKey(Host)
mblighe8819cd2008-02-15 16:48:40 +00001122
jadmanski0afbb632008-06-06 21:10:57 +00001123 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +00001124
jadmanski0afbb632008-06-06 21:10:57 +00001125 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001126 db_table = 'afe_ineligible_host_queues'
mblighe8819cd2008-02-15 16:48:40 +00001127
mblighe8819cd2008-02-15 16:48:40 +00001128
showard7c785282008-05-29 19:45:12 +00001129class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
showardeaa408e2009-09-11 18:45:31 +00001130 Status = host_queue_entry_states.Status
1131 ACTIVE_STATUSES = host_queue_entry_states.ACTIVE_STATUSES
showardeab66ce2009-12-23 00:03:56 +00001132 COMPLETE_STATUSES = host_queue_entry_states.COMPLETE_STATUSES
showarda3ab0d52008-11-03 19:03:47 +00001133
jadmanski0afbb632008-06-06 21:10:57 +00001134 job = dbmodels.ForeignKey(Job)
1135 host = dbmodels.ForeignKey(Host, blank=True, null=True)
showarda5288b42009-07-28 20:06:08 +00001136 status = dbmodels.CharField(max_length=255)
jadmanski0afbb632008-06-06 21:10:57 +00001137 meta_host = dbmodels.ForeignKey(Label, blank=True, null=True,
1138 db_column='meta_host')
1139 active = dbmodels.BooleanField(default=False)
1140 complete = dbmodels.BooleanField(default=False)
showardb8471e32008-07-03 19:51:08 +00001141 deleted = dbmodels.BooleanField(default=False)
showarda5288b42009-07-28 20:06:08 +00001142 execution_subdir = dbmodels.CharField(max_length=255, blank=True,
1143 default='')
showard89f84db2009-03-12 20:39:13 +00001144 # If atomic_group is set, this is a virtual HostQueueEntry that will
1145 # be expanded into many actual hosts within the group at schedule time.
1146 atomic_group = dbmodels.ForeignKey(AtomicGroup, blank=True, null=True)
showardd3dc1992009-04-22 21:01:40 +00001147 aborted = dbmodels.BooleanField(default=False)
showardd3771cc2009-10-07 20:48:22 +00001148 started_on = dbmodels.DateTimeField(null=True, blank=True)
mblighe8819cd2008-02-15 16:48:40 +00001149
jadmanski0afbb632008-06-06 21:10:57 +00001150 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +00001151
mblighe8819cd2008-02-15 16:48:40 +00001152
showard2bab8f42008-11-12 18:15:22 +00001153 def __init__(self, *args, **kwargs):
1154 super(HostQueueEntry, self).__init__(*args, **kwargs)
1155 self._record_attributes(['status'])
1156
1157
showard29f7cd22009-04-29 21:16:24 +00001158 @classmethod
1159 def create(cls, job, host=None, meta_host=None, atomic_group=None,
1160 is_template=False):
1161 if is_template:
1162 status = cls.Status.TEMPLATE
1163 else:
1164 status = cls.Status.QUEUED
1165
1166 return cls(job=job, host=host, meta_host=meta_host,
1167 atomic_group=atomic_group, status=status)
1168
1169
showarda5288b42009-07-28 20:06:08 +00001170 def save(self, *args, **kwargs):
showard2bab8f42008-11-12 18:15:22 +00001171 self._set_active_and_complete()
showarda5288b42009-07-28 20:06:08 +00001172 super(HostQueueEntry, self).save(*args, **kwargs)
showard2bab8f42008-11-12 18:15:22 +00001173 self._check_for_updated_attributes()
1174
1175
showardc0ac3a72009-07-08 21:14:45 +00001176 def execution_path(self):
1177 """
1178 Path to this entry's results (relative to the base results directory).
1179 """
showardd1195652009-12-08 22:21:02 +00001180 return os.path.join(self.job.tag(), self.execution_subdir)
showardc0ac3a72009-07-08 21:14:45 +00001181
1182
showard3f15eed2008-11-14 22:40:48 +00001183 def host_or_metahost_name(self):
1184 if self.host:
1185 return self.host.hostname
showard7890e792009-07-28 20:10:20 +00001186 elif self.meta_host:
showard3f15eed2008-11-14 22:40:48 +00001187 return self.meta_host.name
showard7890e792009-07-28 20:10:20 +00001188 else:
1189 assert self.atomic_group, "no host, meta_host or atomic group!"
1190 return self.atomic_group.name
showard3f15eed2008-11-14 22:40:48 +00001191
1192
showard2bab8f42008-11-12 18:15:22 +00001193 def _set_active_and_complete(self):
showardd3dc1992009-04-22 21:01:40 +00001194 if self.status in self.ACTIVE_STATUSES:
showard2bab8f42008-11-12 18:15:22 +00001195 self.active, self.complete = True, False
1196 elif self.status in self.COMPLETE_STATUSES:
1197 self.active, self.complete = False, True
1198 else:
1199 self.active, self.complete = False, False
1200
1201
1202 def on_attribute_changed(self, attribute, old_value):
1203 assert attribute == 'status'
showardf1175bb2009-06-17 19:34:36 +00001204 logging.info('%s/%d (%d) -> %s' % (self.host, self.job.id, self.id,
1205 self.status))
showard2bab8f42008-11-12 18:15:22 +00001206
1207
jadmanski0afbb632008-06-06 21:10:57 +00001208 def is_meta_host_entry(self):
1209 'True if this is a entry has a meta_host instead of a host.'
1210 return self.host is None and self.meta_host is not None
mblighe8819cd2008-02-15 16:48:40 +00001211
showarda3ab0d52008-11-03 19:03:47 +00001212
showard4c119042008-09-29 19:16:18 +00001213 def log_abort(self, user):
1214 abort_log = AbortedHostQueueEntry(queue_entry=self, aborted_by=user)
1215 abort_log.save()
1216
showarda3ab0d52008-11-03 19:03:47 +00001217
showard64a95952010-01-13 21:27:16 +00001218 def abort(self):
showarda3ab0d52008-11-03 19:03:47 +00001219 # this isn't completely immune to race conditions since it's not atomic,
1220 # but it should be safe given the scheduler's behavior.
showardd3dc1992009-04-22 21:01:40 +00001221 if not self.complete and not self.aborted:
showard64a95952010-01-13 21:27:16 +00001222 self.log_abort(User.current_user())
showardd3dc1992009-04-22 21:01:40 +00001223 self.aborted = True
showarda3ab0d52008-11-03 19:03:47 +00001224 self.save()
mblighe8819cd2008-02-15 16:48:40 +00001225
showardd3dc1992009-04-22 21:01:40 +00001226
1227 @classmethod
1228 def compute_full_status(cls, status, aborted, complete):
1229 if aborted and not complete:
1230 return 'Aborted (%s)' % status
1231 return status
1232
1233
1234 def full_status(self):
1235 return self.compute_full_status(self.status, self.aborted,
1236 self.complete)
1237
1238
1239 def _postprocess_object_dict(self, object_dict):
1240 object_dict['full_status'] = self.full_status()
1241
1242
jadmanski0afbb632008-06-06 21:10:57 +00001243 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001244 db_table = 'afe_host_queue_entries'
mblighe8819cd2008-02-15 16:48:40 +00001245
showard12f3e322009-05-13 21:27:42 +00001246
showard4c119042008-09-29 19:16:18 +00001247
showarda5288b42009-07-28 20:06:08 +00001248 def __unicode__(self):
showard12f3e322009-05-13 21:27:42 +00001249 hostname = None
1250 if self.host:
1251 hostname = self.host.hostname
showarda5288b42009-07-28 20:06:08 +00001252 return u"%s/%d (%d)" % (hostname, self.job.id, self.id)
showard12f3e322009-05-13 21:27:42 +00001253
1254
showard4c119042008-09-29 19:16:18 +00001255class AbortedHostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
1256 queue_entry = dbmodels.OneToOneField(HostQueueEntry, primary_key=True)
1257 aborted_by = dbmodels.ForeignKey(User)
showard68c7aa02008-10-09 16:49:11 +00001258 aborted_on = dbmodels.DateTimeField()
showard4c119042008-09-29 19:16:18 +00001259
1260 objects = model_logic.ExtendedManager()
1261
showard68c7aa02008-10-09 16:49:11 +00001262
showarda5288b42009-07-28 20:06:08 +00001263 def save(self, *args, **kwargs):
showard68c7aa02008-10-09 16:49:11 +00001264 self.aborted_on = datetime.now()
showarda5288b42009-07-28 20:06:08 +00001265 super(AbortedHostQueueEntry, self).save(*args, **kwargs)
showard68c7aa02008-10-09 16:49:11 +00001266
showard4c119042008-09-29 19:16:18 +00001267 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001268 db_table = 'afe_aborted_host_queue_entries'
showard29f7cd22009-04-29 21:16:24 +00001269
1270
1271class RecurringRun(dbmodels.Model, model_logic.ModelExtensions):
1272 """\
1273 job: job to use as a template
1274 owner: owner of the instantiated template
1275 start_date: Run the job at scheduled date
1276 loop_period: Re-run (loop) the job periodically
1277 (in every loop_period seconds)
1278 loop_count: Re-run (loop) count
1279 """
1280
1281 job = dbmodels.ForeignKey(Job)
1282 owner = dbmodels.ForeignKey(User)
1283 start_date = dbmodels.DateTimeField()
1284 loop_period = dbmodels.IntegerField(blank=True)
1285 loop_count = dbmodels.IntegerField(blank=True)
1286
1287 objects = model_logic.ExtendedManager()
1288
1289 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001290 db_table = 'afe_recurring_run'
showard29f7cd22009-04-29 21:16:24 +00001291
showarda5288b42009-07-28 20:06:08 +00001292 def __unicode__(self):
1293 return u'RecurringRun(job %s, start %s, period %s, count %s)' % (
showard29f7cd22009-04-29 21:16:24 +00001294 self.job.id, self.start_date, self.loop_period, self.loop_count)
showard6d7b2ff2009-06-10 00:16:47 +00001295
1296
1297class SpecialTask(dbmodels.Model, model_logic.ModelExtensions):
1298 """\
1299 Tasks to run on hosts at the next time they are in the Ready state. Use this
1300 for high-priority tasks, such as forced repair or forced reinstall.
1301
1302 host: host to run this task on
showard2fe3f1d2009-07-06 20:19:11 +00001303 task: special task to run
showard6d7b2ff2009-06-10 00:16:47 +00001304 time_requested: date and time the request for this task was made
1305 is_active: task is currently running
1306 is_complete: task has finished running
showard2fe3f1d2009-07-06 20:19:11 +00001307 time_started: date and time the task started
showard2fe3f1d2009-07-06 20:19:11 +00001308 queue_entry: Host queue entry waiting on this task (or None, if task was not
1309 started in preparation of a job)
showard6d7b2ff2009-06-10 00:16:47 +00001310 """
showard2fe3f1d2009-07-06 20:19:11 +00001311 Task = enum.Enum('Verify', 'Cleanup', 'Repair', string_values=True)
showard6d7b2ff2009-06-10 00:16:47 +00001312
1313 host = dbmodels.ForeignKey(Host, blank=False, null=False)
showarda5288b42009-07-28 20:06:08 +00001314 task = dbmodels.CharField(max_length=64, choices=Task.choices(),
showard6d7b2ff2009-06-10 00:16:47 +00001315 blank=False, null=False)
jamesren76fcf192010-04-21 20:39:50 +00001316 requested_by = dbmodels.ForeignKey(User)
showard6d7b2ff2009-06-10 00:16:47 +00001317 time_requested = dbmodels.DateTimeField(auto_now_add=True, blank=False,
1318 null=False)
1319 is_active = dbmodels.BooleanField(default=False, blank=False, null=False)
1320 is_complete = dbmodels.BooleanField(default=False, blank=False, null=False)
showardc0ac3a72009-07-08 21:14:45 +00001321 time_started = dbmodels.DateTimeField(null=True, blank=True)
showard2fe3f1d2009-07-06 20:19:11 +00001322 queue_entry = dbmodels.ForeignKey(HostQueueEntry, blank=True, null=True)
showarde60e44e2009-11-13 20:45:38 +00001323 success = dbmodels.BooleanField(default=False, blank=False, null=False)
showard6d7b2ff2009-06-10 00:16:47 +00001324
1325 objects = model_logic.ExtendedManager()
1326
1327
showard9bb960b2009-11-19 01:02:11 +00001328 def save(self, **kwargs):
1329 if self.queue_entry:
1330 self.requested_by = User.objects.get(
1331 login=self.queue_entry.job.owner)
1332 super(SpecialTask, self).save(**kwargs)
1333
1334
showarded2afea2009-07-07 20:54:07 +00001335 def execution_path(self):
showardc0ac3a72009-07-08 21:14:45 +00001336 """@see HostQueueEntry.execution_path()"""
showarded2afea2009-07-07 20:54:07 +00001337 return 'hosts/%s/%s-%s' % (self.host.hostname, self.id,
1338 self.task.lower())
1339
1340
showardc0ac3a72009-07-08 21:14:45 +00001341 # property to emulate HostQueueEntry.status
1342 @property
1343 def status(self):
1344 """
1345 Return a host queue entry status appropriate for this task. Although
1346 SpecialTasks are not HostQueueEntries, it is helpful to the user to
1347 present similar statuses.
1348 """
1349 if self.is_complete:
showarde60e44e2009-11-13 20:45:38 +00001350 if self.success:
1351 return HostQueueEntry.Status.COMPLETED
1352 return HostQueueEntry.Status.FAILED
showardc0ac3a72009-07-08 21:14:45 +00001353 if self.is_active:
1354 return HostQueueEntry.Status.RUNNING
1355 return HostQueueEntry.Status.QUEUED
1356
1357
1358 # property to emulate HostQueueEntry.started_on
1359 @property
1360 def started_on(self):
1361 return self.time_started
1362
1363
showard6d7b2ff2009-06-10 00:16:47 +00001364 @classmethod
showardc5103442010-01-15 00:20:26 +00001365 def schedule_special_task(cls, host, task):
showard474d1362009-08-20 23:32:01 +00001366 """
showardc5103442010-01-15 00:20:26 +00001367 Schedules a special task on a host if the task is not already scheduled.
showard6d7b2ff2009-06-10 00:16:47 +00001368 """
showardc5103442010-01-15 00:20:26 +00001369 existing_tasks = SpecialTask.objects.filter(host__id=host.id, task=task,
1370 is_active=False,
1371 is_complete=False)
1372 if existing_tasks:
1373 return existing_tasks[0]
1374
1375 special_task = SpecialTask(host=host, task=task,
1376 requested_by=User.current_user())
1377 special_task.save()
1378 return special_task
showard2fe3f1d2009-07-06 20:19:11 +00001379
1380
showarded2afea2009-07-07 20:54:07 +00001381 def activate(self):
showard474d1362009-08-20 23:32:01 +00001382 """
1383 Sets a task as active and sets the time started to the current time.
showard2fe3f1d2009-07-06 20:19:11 +00001384 """
showard97446882009-07-20 22:37:28 +00001385 logging.info('Starting: %s', self)
showard2fe3f1d2009-07-06 20:19:11 +00001386 self.is_active = True
1387 self.time_started = datetime.now()
1388 self.save()
1389
1390
showarde60e44e2009-11-13 20:45:38 +00001391 def finish(self, success):
showard474d1362009-08-20 23:32:01 +00001392 """
showard2fe3f1d2009-07-06 20:19:11 +00001393 Sets a task as completed
1394 """
showard97446882009-07-20 22:37:28 +00001395 logging.info('Finished: %s', self)
showarded2afea2009-07-07 20:54:07 +00001396 self.is_active = False
showard2fe3f1d2009-07-06 20:19:11 +00001397 self.is_complete = True
showarde60e44e2009-11-13 20:45:38 +00001398 self.success = success
showard2fe3f1d2009-07-06 20:19:11 +00001399 self.save()
showard6d7b2ff2009-06-10 00:16:47 +00001400
1401
1402 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001403 db_table = 'afe_special_tasks'
showard6d7b2ff2009-06-10 00:16:47 +00001404
showard474d1362009-08-20 23:32:01 +00001405
showarda5288b42009-07-28 20:06:08 +00001406 def __unicode__(self):
1407 result = u'Special Task %s (host %s, task %s, time %s)' % (
showarded2afea2009-07-07 20:54:07 +00001408 self.id, self.host, self.task, self.time_requested)
showard6d7b2ff2009-06-10 00:16:47 +00001409 if self.is_complete:
showarda5288b42009-07-28 20:06:08 +00001410 result += u' (completed)'
showard6d7b2ff2009-06-10 00:16:47 +00001411 elif self.is_active:
showarda5288b42009-07-28 20:06:08 +00001412 result += u' (active)'
showard6d7b2ff2009-06-10 00:16:47 +00001413
1414 return result