blob: 33ffb8ca785e979c3e170b157ded4e8be33a1610 [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')
996 cls.check_parameterized_job(control_file=control_file,
997 parameterized_job=parameterized_job)
998
showard4d233752010-01-20 19:06:40 +0000999 user = User.current_user()
1000 if options.get('reboot_before') is None:
1001 options['reboot_before'] = user.get_reboot_before_display()
1002 if options.get('reboot_after') is None:
1003 options['reboot_after'] = user.get_reboot_after_display()
1004
jamesren76fcf192010-04-21 20:39:50 +00001005 drone_set = DroneSet.resolve_name(options.get('drone_set'))
1006
jadmanski0afbb632008-06-06 21:10:57 +00001007 job = cls.add_object(
showarda1e74b32009-05-12 17:32:04 +00001008 owner=owner,
1009 name=options['name'],
1010 priority=options['priority'],
jamesren4a41e012010-07-16 22:33:48 +00001011 control_file=control_file,
showarda1e74b32009-05-12 17:32:04 +00001012 control_type=options['control_type'],
1013 synch_count=options.get('synch_count'),
1014 timeout=options.get('timeout'),
showard12f3e322009-05-13 21:27:42 +00001015 max_runtime_hrs=options.get('max_runtime_hrs'),
showarda1e74b32009-05-12 17:32:04 +00001016 run_verify=options.get('run_verify'),
1017 email_list=options.get('email_list'),
1018 reboot_before=options.get('reboot_before'),
1019 reboot_after=options.get('reboot_after'),
1020 parse_failed_repair=options.get('parse_failed_repair'),
jamesren76fcf192010-04-21 20:39:50 +00001021 created_on=datetime.now(),
jamesren4a41e012010-07-16 22:33:48 +00001022 drone_set=drone_set,
1023 parameterized_job=parameterized_job)
mblighe8819cd2008-02-15 16:48:40 +00001024
showarda1e74b32009-05-12 17:32:04 +00001025 job.dependency_labels = options['dependencies']
showardc1a98d12010-01-15 00:22:22 +00001026
jamesrend8b6e172010-04-16 23:45:00 +00001027 if options.get('keyvals'):
showardc1a98d12010-01-15 00:22:22 +00001028 for key, value in options['keyvals'].iteritems():
1029 JobKeyval.objects.create(job=job, key=key, value=value)
1030
jadmanski0afbb632008-06-06 21:10:57 +00001031 return job
mblighe8819cd2008-02-15 16:48:40 +00001032
1033
jamesren4a41e012010-07-16 22:33:48 +00001034 def save(self, *args, **kwargs):
1035 self.check_parameterized_job(control_file=self.control_file,
1036 parameterized_job=self.parameterized_job)
1037 super(Job, self).save(*args, **kwargs)
1038
1039
showard29f7cd22009-04-29 21:16:24 +00001040 def queue(self, hosts, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +00001041 """Enqueue a job on the given hosts."""
showarda9545c02009-12-18 22:44:26 +00001042 if not hosts:
1043 if atomic_group:
1044 # No hosts or labels are required to queue an atomic group
1045 # Job. However, if they are given, we respect them below.
1046 atomic_group.enqueue_job(self, is_template=is_template)
1047 else:
1048 # hostless job
1049 entry = HostQueueEntry.create(job=self, is_template=is_template)
1050 entry.save()
1051 return
1052
jadmanski0afbb632008-06-06 21:10:57 +00001053 for host in hosts:
showard29f7cd22009-04-29 21:16:24 +00001054 host.enqueue_job(self, atomic_group=atomic_group,
1055 is_template=is_template)
1056
1057
1058 def create_recurring_job(self, start_date, loop_period, loop_count, owner):
1059 rec = RecurringRun(job=self, start_date=start_date,
1060 loop_period=loop_period,
1061 loop_count=loop_count,
1062 owner=User.objects.get(login=owner))
1063 rec.save()
1064 return rec.id
mblighe8819cd2008-02-15 16:48:40 +00001065
1066
jadmanski0afbb632008-06-06 21:10:57 +00001067 def user(self):
1068 try:
1069 return User.objects.get(login=self.owner)
1070 except self.DoesNotExist:
1071 return None
mblighe8819cd2008-02-15 16:48:40 +00001072
1073
showard64a95952010-01-13 21:27:16 +00001074 def abort(self):
showard98863972008-10-29 21:14:56 +00001075 for queue_entry in self.hostqueueentry_set.all():
showard64a95952010-01-13 21:27:16 +00001076 queue_entry.abort()
showard98863972008-10-29 21:14:56 +00001077
1078
showardd1195652009-12-08 22:21:02 +00001079 def tag(self):
1080 return '%s-%s' % (self.id, self.owner)
1081
1082
showardc1a98d12010-01-15 00:22:22 +00001083 def keyval_dict(self):
1084 return dict((keyval.key, keyval.value)
1085 for keyval in self.jobkeyval_set.all())
1086
1087
jadmanski0afbb632008-06-06 21:10:57 +00001088 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001089 db_table = 'afe_jobs'
mblighe8819cd2008-02-15 16:48:40 +00001090
showarda5288b42009-07-28 20:06:08 +00001091 def __unicode__(self):
1092 return u'%s (%s-%s)' % (self.name, self.id, self.owner)
mblighe8819cd2008-02-15 16:48:40 +00001093
1094
showardc1a98d12010-01-15 00:22:22 +00001095class JobKeyval(dbmodels.Model, model_logic.ModelExtensions):
1096 """Keyvals associated with jobs"""
1097 job = dbmodels.ForeignKey(Job)
1098 key = dbmodels.CharField(max_length=90)
1099 value = dbmodels.CharField(max_length=300)
1100
1101 objects = model_logic.ExtendedManager()
1102
1103 class Meta:
1104 db_table = 'afe_job_keyvals'
1105
1106
showard7c785282008-05-29 19:45:12 +00001107class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +00001108 job = dbmodels.ForeignKey(Job)
1109 host = dbmodels.ForeignKey(Host)
mblighe8819cd2008-02-15 16:48:40 +00001110
jadmanski0afbb632008-06-06 21:10:57 +00001111 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +00001112
jadmanski0afbb632008-06-06 21:10:57 +00001113 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001114 db_table = 'afe_ineligible_host_queues'
mblighe8819cd2008-02-15 16:48:40 +00001115
mblighe8819cd2008-02-15 16:48:40 +00001116
showard7c785282008-05-29 19:45:12 +00001117class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
showardeaa408e2009-09-11 18:45:31 +00001118 Status = host_queue_entry_states.Status
1119 ACTIVE_STATUSES = host_queue_entry_states.ACTIVE_STATUSES
showardeab66ce2009-12-23 00:03:56 +00001120 COMPLETE_STATUSES = host_queue_entry_states.COMPLETE_STATUSES
showarda3ab0d52008-11-03 19:03:47 +00001121
jadmanski0afbb632008-06-06 21:10:57 +00001122 job = dbmodels.ForeignKey(Job)
1123 host = dbmodels.ForeignKey(Host, blank=True, null=True)
showarda5288b42009-07-28 20:06:08 +00001124 status = dbmodels.CharField(max_length=255)
jadmanski0afbb632008-06-06 21:10:57 +00001125 meta_host = dbmodels.ForeignKey(Label, blank=True, null=True,
1126 db_column='meta_host')
1127 active = dbmodels.BooleanField(default=False)
1128 complete = dbmodels.BooleanField(default=False)
showardb8471e32008-07-03 19:51:08 +00001129 deleted = dbmodels.BooleanField(default=False)
showarda5288b42009-07-28 20:06:08 +00001130 execution_subdir = dbmodels.CharField(max_length=255, blank=True,
1131 default='')
showard89f84db2009-03-12 20:39:13 +00001132 # If atomic_group is set, this is a virtual HostQueueEntry that will
1133 # be expanded into many actual hosts within the group at schedule time.
1134 atomic_group = dbmodels.ForeignKey(AtomicGroup, blank=True, null=True)
showardd3dc1992009-04-22 21:01:40 +00001135 aborted = dbmodels.BooleanField(default=False)
showardd3771cc2009-10-07 20:48:22 +00001136 started_on = dbmodels.DateTimeField(null=True, blank=True)
mblighe8819cd2008-02-15 16:48:40 +00001137
jadmanski0afbb632008-06-06 21:10:57 +00001138 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +00001139
mblighe8819cd2008-02-15 16:48:40 +00001140
showard2bab8f42008-11-12 18:15:22 +00001141 def __init__(self, *args, **kwargs):
1142 super(HostQueueEntry, self).__init__(*args, **kwargs)
1143 self._record_attributes(['status'])
1144
1145
showard29f7cd22009-04-29 21:16:24 +00001146 @classmethod
1147 def create(cls, job, host=None, meta_host=None, atomic_group=None,
1148 is_template=False):
1149 if is_template:
1150 status = cls.Status.TEMPLATE
1151 else:
1152 status = cls.Status.QUEUED
1153
1154 return cls(job=job, host=host, meta_host=meta_host,
1155 atomic_group=atomic_group, status=status)
1156
1157
showarda5288b42009-07-28 20:06:08 +00001158 def save(self, *args, **kwargs):
showard2bab8f42008-11-12 18:15:22 +00001159 self._set_active_and_complete()
showarda5288b42009-07-28 20:06:08 +00001160 super(HostQueueEntry, self).save(*args, **kwargs)
showard2bab8f42008-11-12 18:15:22 +00001161 self._check_for_updated_attributes()
1162
1163
showardc0ac3a72009-07-08 21:14:45 +00001164 def execution_path(self):
1165 """
1166 Path to this entry's results (relative to the base results directory).
1167 """
showardd1195652009-12-08 22:21:02 +00001168 return os.path.join(self.job.tag(), self.execution_subdir)
showardc0ac3a72009-07-08 21:14:45 +00001169
1170
showard3f15eed2008-11-14 22:40:48 +00001171 def host_or_metahost_name(self):
1172 if self.host:
1173 return self.host.hostname
showard7890e792009-07-28 20:10:20 +00001174 elif self.meta_host:
showard3f15eed2008-11-14 22:40:48 +00001175 return self.meta_host.name
showard7890e792009-07-28 20:10:20 +00001176 else:
1177 assert self.atomic_group, "no host, meta_host or atomic group!"
1178 return self.atomic_group.name
showard3f15eed2008-11-14 22:40:48 +00001179
1180
showard2bab8f42008-11-12 18:15:22 +00001181 def _set_active_and_complete(self):
showardd3dc1992009-04-22 21:01:40 +00001182 if self.status in self.ACTIVE_STATUSES:
showard2bab8f42008-11-12 18:15:22 +00001183 self.active, self.complete = True, False
1184 elif self.status in self.COMPLETE_STATUSES:
1185 self.active, self.complete = False, True
1186 else:
1187 self.active, self.complete = False, False
1188
1189
1190 def on_attribute_changed(self, attribute, old_value):
1191 assert attribute == 'status'
showardf1175bb2009-06-17 19:34:36 +00001192 logging.info('%s/%d (%d) -> %s' % (self.host, self.job.id, self.id,
1193 self.status))
showard2bab8f42008-11-12 18:15:22 +00001194
1195
jadmanski0afbb632008-06-06 21:10:57 +00001196 def is_meta_host_entry(self):
1197 'True if this is a entry has a meta_host instead of a host.'
1198 return self.host is None and self.meta_host is not None
mblighe8819cd2008-02-15 16:48:40 +00001199
showarda3ab0d52008-11-03 19:03:47 +00001200
showard4c119042008-09-29 19:16:18 +00001201 def log_abort(self, user):
1202 abort_log = AbortedHostQueueEntry(queue_entry=self, aborted_by=user)
1203 abort_log.save()
1204
showarda3ab0d52008-11-03 19:03:47 +00001205
showard64a95952010-01-13 21:27:16 +00001206 def abort(self):
showarda3ab0d52008-11-03 19:03:47 +00001207 # this isn't completely immune to race conditions since it's not atomic,
1208 # but it should be safe given the scheduler's behavior.
showardd3dc1992009-04-22 21:01:40 +00001209 if not self.complete and not self.aborted:
showard64a95952010-01-13 21:27:16 +00001210 self.log_abort(User.current_user())
showardd3dc1992009-04-22 21:01:40 +00001211 self.aborted = True
showarda3ab0d52008-11-03 19:03:47 +00001212 self.save()
mblighe8819cd2008-02-15 16:48:40 +00001213
showardd3dc1992009-04-22 21:01:40 +00001214
1215 @classmethod
1216 def compute_full_status(cls, status, aborted, complete):
1217 if aborted and not complete:
1218 return 'Aborted (%s)' % status
1219 return status
1220
1221
1222 def full_status(self):
1223 return self.compute_full_status(self.status, self.aborted,
1224 self.complete)
1225
1226
1227 def _postprocess_object_dict(self, object_dict):
1228 object_dict['full_status'] = self.full_status()
1229
1230
jadmanski0afbb632008-06-06 21:10:57 +00001231 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001232 db_table = 'afe_host_queue_entries'
mblighe8819cd2008-02-15 16:48:40 +00001233
showard12f3e322009-05-13 21:27:42 +00001234
showard4c119042008-09-29 19:16:18 +00001235
showarda5288b42009-07-28 20:06:08 +00001236 def __unicode__(self):
showard12f3e322009-05-13 21:27:42 +00001237 hostname = None
1238 if self.host:
1239 hostname = self.host.hostname
showarda5288b42009-07-28 20:06:08 +00001240 return u"%s/%d (%d)" % (hostname, self.job.id, self.id)
showard12f3e322009-05-13 21:27:42 +00001241
1242
showard4c119042008-09-29 19:16:18 +00001243class AbortedHostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
1244 queue_entry = dbmodels.OneToOneField(HostQueueEntry, primary_key=True)
1245 aborted_by = dbmodels.ForeignKey(User)
showard68c7aa02008-10-09 16:49:11 +00001246 aborted_on = dbmodels.DateTimeField()
showard4c119042008-09-29 19:16:18 +00001247
1248 objects = model_logic.ExtendedManager()
1249
showard68c7aa02008-10-09 16:49:11 +00001250
showarda5288b42009-07-28 20:06:08 +00001251 def save(self, *args, **kwargs):
showard68c7aa02008-10-09 16:49:11 +00001252 self.aborted_on = datetime.now()
showarda5288b42009-07-28 20:06:08 +00001253 super(AbortedHostQueueEntry, self).save(*args, **kwargs)
showard68c7aa02008-10-09 16:49:11 +00001254
showard4c119042008-09-29 19:16:18 +00001255 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001256 db_table = 'afe_aborted_host_queue_entries'
showard29f7cd22009-04-29 21:16:24 +00001257
1258
1259class RecurringRun(dbmodels.Model, model_logic.ModelExtensions):
1260 """\
1261 job: job to use as a template
1262 owner: owner of the instantiated template
1263 start_date: Run the job at scheduled date
1264 loop_period: Re-run (loop) the job periodically
1265 (in every loop_period seconds)
1266 loop_count: Re-run (loop) count
1267 """
1268
1269 job = dbmodels.ForeignKey(Job)
1270 owner = dbmodels.ForeignKey(User)
1271 start_date = dbmodels.DateTimeField()
1272 loop_period = dbmodels.IntegerField(blank=True)
1273 loop_count = dbmodels.IntegerField(blank=True)
1274
1275 objects = model_logic.ExtendedManager()
1276
1277 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001278 db_table = 'afe_recurring_run'
showard29f7cd22009-04-29 21:16:24 +00001279
showarda5288b42009-07-28 20:06:08 +00001280 def __unicode__(self):
1281 return u'RecurringRun(job %s, start %s, period %s, count %s)' % (
showard29f7cd22009-04-29 21:16:24 +00001282 self.job.id, self.start_date, self.loop_period, self.loop_count)
showard6d7b2ff2009-06-10 00:16:47 +00001283
1284
1285class SpecialTask(dbmodels.Model, model_logic.ModelExtensions):
1286 """\
1287 Tasks to run on hosts at the next time they are in the Ready state. Use this
1288 for high-priority tasks, such as forced repair or forced reinstall.
1289
1290 host: host to run this task on
showard2fe3f1d2009-07-06 20:19:11 +00001291 task: special task to run
showard6d7b2ff2009-06-10 00:16:47 +00001292 time_requested: date and time the request for this task was made
1293 is_active: task is currently running
1294 is_complete: task has finished running
showard2fe3f1d2009-07-06 20:19:11 +00001295 time_started: date and time the task started
showard2fe3f1d2009-07-06 20:19:11 +00001296 queue_entry: Host queue entry waiting on this task (or None, if task was not
1297 started in preparation of a job)
showard6d7b2ff2009-06-10 00:16:47 +00001298 """
showard2fe3f1d2009-07-06 20:19:11 +00001299 Task = enum.Enum('Verify', 'Cleanup', 'Repair', string_values=True)
showard6d7b2ff2009-06-10 00:16:47 +00001300
1301 host = dbmodels.ForeignKey(Host, blank=False, null=False)
showarda5288b42009-07-28 20:06:08 +00001302 task = dbmodels.CharField(max_length=64, choices=Task.choices(),
showard6d7b2ff2009-06-10 00:16:47 +00001303 blank=False, null=False)
jamesren76fcf192010-04-21 20:39:50 +00001304 requested_by = dbmodels.ForeignKey(User)
showard6d7b2ff2009-06-10 00:16:47 +00001305 time_requested = dbmodels.DateTimeField(auto_now_add=True, blank=False,
1306 null=False)
1307 is_active = dbmodels.BooleanField(default=False, blank=False, null=False)
1308 is_complete = dbmodels.BooleanField(default=False, blank=False, null=False)
showardc0ac3a72009-07-08 21:14:45 +00001309 time_started = dbmodels.DateTimeField(null=True, blank=True)
showard2fe3f1d2009-07-06 20:19:11 +00001310 queue_entry = dbmodels.ForeignKey(HostQueueEntry, blank=True, null=True)
showarde60e44e2009-11-13 20:45:38 +00001311 success = dbmodels.BooleanField(default=False, blank=False, null=False)
showard6d7b2ff2009-06-10 00:16:47 +00001312
1313 objects = model_logic.ExtendedManager()
1314
1315
showard9bb960b2009-11-19 01:02:11 +00001316 def save(self, **kwargs):
1317 if self.queue_entry:
1318 self.requested_by = User.objects.get(
1319 login=self.queue_entry.job.owner)
1320 super(SpecialTask, self).save(**kwargs)
1321
1322
showarded2afea2009-07-07 20:54:07 +00001323 def execution_path(self):
showardc0ac3a72009-07-08 21:14:45 +00001324 """@see HostQueueEntry.execution_path()"""
showarded2afea2009-07-07 20:54:07 +00001325 return 'hosts/%s/%s-%s' % (self.host.hostname, self.id,
1326 self.task.lower())
1327
1328
showardc0ac3a72009-07-08 21:14:45 +00001329 # property to emulate HostQueueEntry.status
1330 @property
1331 def status(self):
1332 """
1333 Return a host queue entry status appropriate for this task. Although
1334 SpecialTasks are not HostQueueEntries, it is helpful to the user to
1335 present similar statuses.
1336 """
1337 if self.is_complete:
showarde60e44e2009-11-13 20:45:38 +00001338 if self.success:
1339 return HostQueueEntry.Status.COMPLETED
1340 return HostQueueEntry.Status.FAILED
showardc0ac3a72009-07-08 21:14:45 +00001341 if self.is_active:
1342 return HostQueueEntry.Status.RUNNING
1343 return HostQueueEntry.Status.QUEUED
1344
1345
1346 # property to emulate HostQueueEntry.started_on
1347 @property
1348 def started_on(self):
1349 return self.time_started
1350
1351
showard6d7b2ff2009-06-10 00:16:47 +00001352 @classmethod
showardc5103442010-01-15 00:20:26 +00001353 def schedule_special_task(cls, host, task):
showard474d1362009-08-20 23:32:01 +00001354 """
showardc5103442010-01-15 00:20:26 +00001355 Schedules a special task on a host if the task is not already scheduled.
showard6d7b2ff2009-06-10 00:16:47 +00001356 """
showardc5103442010-01-15 00:20:26 +00001357 existing_tasks = SpecialTask.objects.filter(host__id=host.id, task=task,
1358 is_active=False,
1359 is_complete=False)
1360 if existing_tasks:
1361 return existing_tasks[0]
1362
1363 special_task = SpecialTask(host=host, task=task,
1364 requested_by=User.current_user())
1365 special_task.save()
1366 return special_task
showard2fe3f1d2009-07-06 20:19:11 +00001367
1368
showarded2afea2009-07-07 20:54:07 +00001369 def activate(self):
showard474d1362009-08-20 23:32:01 +00001370 """
1371 Sets a task as active and sets the time started to the current time.
showard2fe3f1d2009-07-06 20:19:11 +00001372 """
showard97446882009-07-20 22:37:28 +00001373 logging.info('Starting: %s', self)
showard2fe3f1d2009-07-06 20:19:11 +00001374 self.is_active = True
1375 self.time_started = datetime.now()
1376 self.save()
1377
1378
showarde60e44e2009-11-13 20:45:38 +00001379 def finish(self, success):
showard474d1362009-08-20 23:32:01 +00001380 """
showard2fe3f1d2009-07-06 20:19:11 +00001381 Sets a task as completed
1382 """
showard97446882009-07-20 22:37:28 +00001383 logging.info('Finished: %s', self)
showarded2afea2009-07-07 20:54:07 +00001384 self.is_active = False
showard2fe3f1d2009-07-06 20:19:11 +00001385 self.is_complete = True
showarde60e44e2009-11-13 20:45:38 +00001386 self.success = success
showard2fe3f1d2009-07-06 20:19:11 +00001387 self.save()
showard6d7b2ff2009-06-10 00:16:47 +00001388
1389
1390 class Meta:
showardeab66ce2009-12-23 00:03:56 +00001391 db_table = 'afe_special_tasks'
showard6d7b2ff2009-06-10 00:16:47 +00001392
showard474d1362009-08-20 23:32:01 +00001393
showarda5288b42009-07-28 20:06:08 +00001394 def __unicode__(self):
1395 result = u'Special Task %s (host %s, task %s, time %s)' % (
showarded2afea2009-07-07 20:54:07 +00001396 self.id, self.host, self.task, self.time_requested)
showard6d7b2ff2009-06-10 00:16:47 +00001397 if self.is_complete:
showarda5288b42009-07-28 20:06:08 +00001398 result += u' (completed)'
showard6d7b2ff2009-06-10 00:16:47 +00001399 elif self.is_active:
showarda5288b42009-07-28 20:06:08 +00001400 result += u' (active)'
showard6d7b2ff2009-06-10 00:16:47 +00001401
1402 return result