blob: e2909f87ef35f8d3cded28d93e84040959337b79 [file] [log] [blame]
showardf1175bb2009-06-17 19:34:36 +00001import logging
showardfb2a7fa2008-07-17 17:04:12 +00002from datetime import datetime
showard7c785282008-05-29 19:45:12 +00003from django.db import models as dbmodels, connection
showardcafd16e2009-05-29 18:37:49 +00004import common
5from autotest_lib.frontend.afe import model_logic
6from autotest_lib.frontend import settings, thread_local
showardb1e51872008-10-07 11:08:18 +00007from autotest_lib.client.common_lib import enum, host_protections, global_config
showardeaa408e2009-09-11 18:45:31 +00008from autotest_lib.client.common_lib import host_queue_entry_states
mblighe8819cd2008-02-15 16:48:40 +00009
showard0fc38302008-10-23 00:44:07 +000010# job options and user preferences
11RebootBefore = enum.Enum('Never', 'If dirty', 'Always')
12DEFAULT_REBOOT_BEFORE = RebootBefore.IF_DIRTY
13RebootAfter = enum.Enum('Never', 'If all tests passed', 'Always')
14DEFAULT_REBOOT_AFTER = RebootBefore.ALWAYS
mblighe8819cd2008-02-15 16:48:40 +000015
showard89f84db2009-03-12 20:39:13 +000016
mblighe8819cd2008-02-15 16:48:40 +000017class AclAccessViolation(Exception):
jadmanski0afbb632008-06-06 21:10:57 +000018 """\
19 Raised when an operation is attempted with proper permissions as
20 dictated by ACLs.
21 """
mblighe8819cd2008-02-15 16:48:40 +000022
23
showard205fd602009-03-21 00:17:35 +000024class AtomicGroup(model_logic.ModelWithInvalid, dbmodels.Model):
showard89f84db2009-03-12 20:39:13 +000025 """\
26 An atomic group defines a collection of hosts which must only be scheduled
27 all at once. Any host with a label having an atomic group will only be
28 scheduled for a job at the same time as other hosts sharing that label.
29
30 Required:
31 name: A name for this atomic group. ex: 'rack23' or 'funky_net'
32 max_number_of_machines: The maximum number of machines that will be
33 scheduled at once when scheduling jobs to this atomic group.
34 The job.synch_count is considered the minimum.
35
36 Optional:
37 description: Arbitrary text description of this group's purpose.
38 """
showarda5288b42009-07-28 20:06:08 +000039 name = dbmodels.CharField(max_length=255, unique=True)
showard89f84db2009-03-12 20:39:13 +000040 description = dbmodels.TextField(blank=True)
showarde9450c92009-06-30 01:58:52 +000041 # This magic value is the default to simplify the scheduler logic.
42 # It must be "large". The common use of atomic groups is to want all
43 # machines in the group to be used, limits on which subset used are
44 # often chosen via dependency labels.
45 INFINITE_MACHINES = 333333333
46 max_number_of_machines = dbmodels.IntegerField(default=INFINITE_MACHINES)
showard205fd602009-03-21 00:17:35 +000047 invalid = dbmodels.BooleanField(default=False,
showarda5288b42009-07-28 20:06:08 +000048 editable=settings.FULL_ADMIN)
showard89f84db2009-03-12 20:39:13 +000049
showard89f84db2009-03-12 20:39:13 +000050 name_field = 'name'
showard205fd602009-03-21 00:17:35 +000051 objects = model_logic.ExtendedManager()
52 valid_objects = model_logic.ValidObjectsManager()
53
54
showard29f7cd22009-04-29 21:16:24 +000055 def enqueue_job(self, job, is_template=False):
showardc92da832009-04-07 18:14:34 +000056 """Enqueue a job on an associated atomic group of hosts."""
showard29f7cd22009-04-29 21:16:24 +000057 queue_entry = HostQueueEntry.create(atomic_group=self, job=job,
58 is_template=is_template)
showardc92da832009-04-07 18:14:34 +000059 queue_entry.save()
60
61
showard205fd602009-03-21 00:17:35 +000062 def clean_object(self):
63 self.label_set.clear()
showard89f84db2009-03-12 20:39:13 +000064
65
66 class Meta:
67 db_table = 'atomic_groups'
68
showard205fd602009-03-21 00:17:35 +000069
showarda5288b42009-07-28 20:06:08 +000070 def __unicode__(self):
71 return unicode(self.name)
showard89f84db2009-03-12 20:39:13 +000072
73
showard7c785282008-05-29 19:45:12 +000074class Label(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +000075 """\
76 Required:
showard89f84db2009-03-12 20:39:13 +000077 name: label name
mblighe8819cd2008-02-15 16:48:40 +000078
jadmanski0afbb632008-06-06 21:10:57 +000079 Optional:
showard89f84db2009-03-12 20:39:13 +000080 kernel_config: URL/path to kernel config for jobs run on this label.
81 platform: If True, this is a platform label (defaults to False).
82 only_if_needed: If True, a Host with this label can only be used if that
83 label is requested by the job/test (either as the meta_host or
84 in the job_dependencies).
85 atomic_group: The atomic group associated with this label.
jadmanski0afbb632008-06-06 21:10:57 +000086 """
showarda5288b42009-07-28 20:06:08 +000087 name = dbmodels.CharField(max_length=255, unique=True)
88 kernel_config = dbmodels.CharField(max_length=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +000089 platform = dbmodels.BooleanField(default=False)
90 invalid = dbmodels.BooleanField(default=False,
91 editable=settings.FULL_ADMIN)
showardb1e51872008-10-07 11:08:18 +000092 only_if_needed = dbmodels.BooleanField(default=False)
mblighe8819cd2008-02-15 16:48:40 +000093
jadmanski0afbb632008-06-06 21:10:57 +000094 name_field = 'name'
95 objects = model_logic.ExtendedManager()
96 valid_objects = model_logic.ValidObjectsManager()
showard89f84db2009-03-12 20:39:13 +000097 atomic_group = dbmodels.ForeignKey(AtomicGroup, null=True, blank=True)
98
mbligh5244cbb2008-04-24 20:39:52 +000099
jadmanski0afbb632008-06-06 21:10:57 +0000100 def clean_object(self):
101 self.host_set.clear()
showard01a51672009-05-29 18:42:37 +0000102 self.test_set.clear()
mblighe8819cd2008-02-15 16:48:40 +0000103
104
showard29f7cd22009-04-29 21:16:24 +0000105 def enqueue_job(self, job, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +0000106 """Enqueue a job on any host of this label."""
showard29f7cd22009-04-29 21:16:24 +0000107 queue_entry = HostQueueEntry.create(meta_host=self, job=job,
108 is_template=is_template,
109 atomic_group=atomic_group)
jadmanski0afbb632008-06-06 21:10:57 +0000110 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000111
112
jadmanski0afbb632008-06-06 21:10:57 +0000113 class Meta:
114 db_table = 'labels'
mblighe8819cd2008-02-15 16:48:40 +0000115
showarda5288b42009-07-28 20:06:08 +0000116 def __unicode__(self):
117 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000118
119
showardfb2a7fa2008-07-17 17:04:12 +0000120class User(dbmodels.Model, model_logic.ModelExtensions):
121 """\
122 Required:
123 login :user login name
124
125 Optional:
126 access_level: 0=User (default), 1=Admin, 100=Root
127 """
128 ACCESS_ROOT = 100
129 ACCESS_ADMIN = 1
130 ACCESS_USER = 0
131
showarda5288b42009-07-28 20:06:08 +0000132 login = dbmodels.CharField(max_length=255, unique=True)
showardfb2a7fa2008-07-17 17:04:12 +0000133 access_level = dbmodels.IntegerField(default=ACCESS_USER, blank=True)
134
showard0fc38302008-10-23 00:44:07 +0000135 # user preferences
136 reboot_before = dbmodels.SmallIntegerField(choices=RebootBefore.choices(),
137 blank=True,
138 default=DEFAULT_REBOOT_BEFORE)
139 reboot_after = dbmodels.SmallIntegerField(choices=RebootAfter.choices(),
140 blank=True,
141 default=DEFAULT_REBOOT_AFTER)
showard97db5ba2008-11-12 18:18:02 +0000142 show_experimental = dbmodels.BooleanField(default=False)
showard0fc38302008-10-23 00:44:07 +0000143
showardfb2a7fa2008-07-17 17:04:12 +0000144 name_field = 'login'
145 objects = model_logic.ExtendedManager()
146
147
showarda5288b42009-07-28 20:06:08 +0000148 def save(self, *args, **kwargs):
showardfb2a7fa2008-07-17 17:04:12 +0000149 # is this a new object being saved for the first time?
150 first_time = (self.id is None)
151 user = thread_local.get_user()
showard0fc38302008-10-23 00:44:07 +0000152 if user and not user.is_superuser() and user.login != self.login:
153 raise AclAccessViolation("You cannot modify user " + self.login)
showarda5288b42009-07-28 20:06:08 +0000154 super(User, self).save(*args, **kwargs)
showardfb2a7fa2008-07-17 17:04:12 +0000155 if first_time:
156 everyone = AclGroup.objects.get(name='Everyone')
157 everyone.users.add(self)
158
159
160 def is_superuser(self):
161 return self.access_level >= self.ACCESS_ROOT
162
163
164 class Meta:
165 db_table = 'users'
166
showarda5288b42009-07-28 20:06:08 +0000167 def __unicode__(self):
168 return unicode(self.login)
showardfb2a7fa2008-07-17 17:04:12 +0000169
170
showardf8b19042009-05-12 17:22:49 +0000171class Host(model_logic.ModelWithInvalid, dbmodels.Model,
172 model_logic.ModelWithAttributes):
jadmanski0afbb632008-06-06 21:10:57 +0000173 """\
174 Required:
175 hostname
mblighe8819cd2008-02-15 16:48:40 +0000176
jadmanski0afbb632008-06-06 21:10:57 +0000177 optional:
showard21baa452008-10-21 00:08:39 +0000178 locked: if true, host is locked and will not be queued
mblighe8819cd2008-02-15 16:48:40 +0000179
jadmanski0afbb632008-06-06 21:10:57 +0000180 Internal:
181 synch_id: currently unused
182 status: string describing status of host
showard21baa452008-10-21 00:08:39 +0000183 invalid: true if the host has been deleted
184 protection: indicates what can be done to this host during repair
185 locked_by: user that locked the host, or null if the host is unlocked
186 lock_time: DateTime at which the host was locked
187 dirty: true if the host has been used without being rebooted
jadmanski0afbb632008-06-06 21:10:57 +0000188 """
189 Status = enum.Enum('Verifying', 'Running', 'Ready', 'Repairing',
showard45ae8192008-11-05 19:32:53 +0000190 'Repair Failed', 'Dead', 'Cleaning', 'Pending',
showard6d7b2ff2009-06-10 00:16:47 +0000191 string_values=True)
mblighe8819cd2008-02-15 16:48:40 +0000192
showarda5288b42009-07-28 20:06:08 +0000193 hostname = dbmodels.CharField(max_length=255, unique=True)
194 labels = dbmodels.ManyToManyField(Label, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000195 locked = dbmodels.BooleanField(default=False)
196 synch_id = dbmodels.IntegerField(blank=True, null=True,
197 editable=settings.FULL_ADMIN)
showarda5288b42009-07-28 20:06:08 +0000198 status = dbmodels.CharField(max_length=255, default=Status.READY,
jadmanski0afbb632008-06-06 21:10:57 +0000199 choices=Status.choices(),
200 editable=settings.FULL_ADMIN)
201 invalid = dbmodels.BooleanField(default=False,
202 editable=settings.FULL_ADMIN)
showardd5afc2f2008-08-12 17:19:44 +0000203 protection = dbmodels.SmallIntegerField(null=False, blank=True,
showarddf062562008-07-03 19:56:37 +0000204 choices=host_protections.choices,
205 default=host_protections.default)
showardfb2a7fa2008-07-17 17:04:12 +0000206 locked_by = dbmodels.ForeignKey(User, null=True, blank=True, editable=False)
207 lock_time = dbmodels.DateTimeField(null=True, blank=True, editable=False)
showard21baa452008-10-21 00:08:39 +0000208 dirty = dbmodels.BooleanField(default=True, editable=settings.FULL_ADMIN)
mblighe8819cd2008-02-15 16:48:40 +0000209
jadmanski0afbb632008-06-06 21:10:57 +0000210 name_field = 'hostname'
211 objects = model_logic.ExtendedManager()
212 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +0000213
showard2bab8f42008-11-12 18:15:22 +0000214
215 def __init__(self, *args, **kwargs):
216 super(Host, self).__init__(*args, **kwargs)
217 self._record_attributes(['status'])
218
219
showardb8471e32008-07-03 19:51:08 +0000220 @staticmethod
221 def create_one_time_host(hostname):
222 query = Host.objects.filter(hostname=hostname)
223 if query.count() == 0:
224 host = Host(hostname=hostname, invalid=True)
showarda8411af2008-08-07 22:35:58 +0000225 host.do_validate()
showardb8471e32008-07-03 19:51:08 +0000226 else:
227 host = query[0]
228 if not host.invalid:
229 raise model_logic.ValidationError({
mblighb5b7b5d2009-02-03 17:47:15 +0000230 'hostname' : '%s already exists in the autotest DB. '
231 'Select it rather than entering it as a one time '
232 'host.' % hostname
showardb8471e32008-07-03 19:51:08 +0000233 })
showard1ab512b2008-07-30 23:39:04 +0000234 host.protection = host_protections.Protection.DO_NOT_REPAIR
showard946a7af2009-04-15 21:53:23 +0000235 host.locked = False
showardb8471e32008-07-03 19:51:08 +0000236 host.save()
showard2924b0a2009-06-18 23:16:15 +0000237 host.clean_object()
showardb8471e32008-07-03 19:51:08 +0000238 return host
mbligh5244cbb2008-04-24 20:39:52 +0000239
showard1ff7b2e2009-05-15 23:17:18 +0000240
showardafd97de2009-10-01 18:45:09 +0000241 def resurrect_object(self, old_object):
242 super(Host, self).resurrect_object(old_object)
243 # invalid hosts can be in use by the scheduler (as one-time hosts), so
244 # don't change the status
245 self.status = old_object.status
246
247
jadmanski0afbb632008-06-06 21:10:57 +0000248 def clean_object(self):
249 self.aclgroup_set.clear()
250 self.labels.clear()
mblighe8819cd2008-02-15 16:48:40 +0000251
252
showarda5288b42009-07-28 20:06:08 +0000253 def save(self, *args, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000254 # extra spaces in the hostname can be a sneaky source of errors
255 self.hostname = self.hostname.strip()
256 # is this a new object being saved for the first time?
257 first_time = (self.id is None)
showard3dd47c22008-07-10 00:41:36 +0000258 if not first_time:
259 AclGroup.check_for_acl_violation_hosts([self])
showardfb2a7fa2008-07-17 17:04:12 +0000260 if self.locked and not self.locked_by:
261 self.locked_by = thread_local.get_user()
262 self.lock_time = datetime.now()
showard21baa452008-10-21 00:08:39 +0000263 self.dirty = True
showardfb2a7fa2008-07-17 17:04:12 +0000264 elif not self.locked and self.locked_by:
265 self.locked_by = None
266 self.lock_time = None
showarda5288b42009-07-28 20:06:08 +0000267 super(Host, self).save(*args, **kwargs)
jadmanski0afbb632008-06-06 21:10:57 +0000268 if first_time:
269 everyone = AclGroup.objects.get(name='Everyone')
270 everyone.hosts.add(self)
showard2bab8f42008-11-12 18:15:22 +0000271 self._check_for_updated_attributes()
272
mblighe8819cd2008-02-15 16:48:40 +0000273
showardb8471e32008-07-03 19:51:08 +0000274 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000275 AclGroup.check_for_acl_violation_hosts([self])
showardb8471e32008-07-03 19:51:08 +0000276 for queue_entry in self.hostqueueentry_set.all():
277 queue_entry.deleted = True
showard9a1f2e12008-10-02 11:14:29 +0000278 queue_entry.abort(thread_local.get_user())
showardb8471e32008-07-03 19:51:08 +0000279 super(Host, self).delete()
280
mblighe8819cd2008-02-15 16:48:40 +0000281
showard2bab8f42008-11-12 18:15:22 +0000282 def on_attribute_changed(self, attribute, old_value):
283 assert attribute == 'status'
showardf1175bb2009-06-17 19:34:36 +0000284 logging.info(self.hostname + ' -> ' + self.status)
showard2bab8f42008-11-12 18:15:22 +0000285
286
showard29f7cd22009-04-29 21:16:24 +0000287 def enqueue_job(self, job, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +0000288 """Enqueue a job on this host."""
showard29f7cd22009-04-29 21:16:24 +0000289 queue_entry = HostQueueEntry.create(host=self, job=job,
290 is_template=is_template,
291 atomic_group=atomic_group)
jadmanski0afbb632008-06-06 21:10:57 +0000292 # allow recovery of dead hosts from the frontend
293 if not self.active_queue_entry() and self.is_dead():
294 self.status = Host.Status.READY
295 self.save()
296 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000297
showard08f981b2008-06-24 21:59:03 +0000298 block = IneligibleHostQueue(job=job, host=self)
299 block.save()
300
mblighe8819cd2008-02-15 16:48:40 +0000301
jadmanski0afbb632008-06-06 21:10:57 +0000302 def platform(self):
303 # TODO(showard): slighly hacky?
304 platforms = self.labels.filter(platform=True)
305 if len(platforms) == 0:
306 return None
307 return platforms[0]
308 platform.short_description = 'Platform'
mblighe8819cd2008-02-15 16:48:40 +0000309
310
showardcafd16e2009-05-29 18:37:49 +0000311 @classmethod
312 def check_no_platform(cls, hosts):
313 Host.objects.populate_relationships(hosts, Label, 'label_list')
314 errors = []
315 for host in hosts:
316 platforms = [label.name for label in host.label_list
317 if label.platform]
318 if platforms:
319 # do a join, just in case this host has multiple platforms,
320 # we'll be able to see it
321 errors.append('Host %s already has a platform: %s' % (
322 host.hostname, ', '.join(platforms)))
323 if errors:
324 raise model_logic.ValidationError({'labels': '; '.join(errors)})
325
326
jadmanski0afbb632008-06-06 21:10:57 +0000327 def is_dead(self):
328 return self.status == Host.Status.REPAIR_FAILED
mbligh3cab4a72008-03-05 23:19:09 +0000329
330
jadmanski0afbb632008-06-06 21:10:57 +0000331 def active_queue_entry(self):
332 active = list(self.hostqueueentry_set.filter(active=True))
333 if not active:
334 return None
335 assert len(active) == 1, ('More than one active entry for '
336 'host ' + self.hostname)
337 return active[0]
mblighe8819cd2008-02-15 16:48:40 +0000338
339
showardf8b19042009-05-12 17:22:49 +0000340 def _get_attribute_model_and_args(self, attribute):
341 return HostAttribute, dict(host=self, attribute=attribute)
showard0957a842009-05-11 19:25:08 +0000342
343
jadmanski0afbb632008-06-06 21:10:57 +0000344 class Meta:
345 db_table = 'hosts'
mblighe8819cd2008-02-15 16:48:40 +0000346
showarda5288b42009-07-28 20:06:08 +0000347 def __unicode__(self):
348 return unicode(self.hostname)
mblighe8819cd2008-02-15 16:48:40 +0000349
350
showard0957a842009-05-11 19:25:08 +0000351class HostAttribute(dbmodels.Model):
352 """Arbitrary keyvals associated with hosts."""
353 host = dbmodels.ForeignKey(Host)
showarda5288b42009-07-28 20:06:08 +0000354 attribute = dbmodels.CharField(max_length=90)
355 value = dbmodels.CharField(max_length=300)
showard0957a842009-05-11 19:25:08 +0000356
357 objects = model_logic.ExtendedManager()
358
359 class Meta:
360 db_table = 'host_attributes'
361
362
showard7c785282008-05-29 19:45:12 +0000363class Test(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000364 """\
365 Required:
showard909c7a62008-07-15 21:52:38 +0000366 author: author name
367 description: description of the test
jadmanski0afbb632008-06-06 21:10:57 +0000368 name: test name
showard909c7a62008-07-15 21:52:38 +0000369 time: short, medium, long
370 test_class: This describes the class for your the test belongs in.
371 test_category: This describes the category for your tests
jadmanski0afbb632008-06-06 21:10:57 +0000372 test_type: Client or Server
373 path: path to pass to run_test()
showard909c7a62008-07-15 21:52:38 +0000374 sync_count: is a number >=1 (1 being the default). If it's 1, then it's an
375 async job. If it's >1 it's sync job for that number of machines
showard2bab8f42008-11-12 18:15:22 +0000376 i.e. if sync_count = 2 it is a sync job that requires two
377 machines.
jadmanski0afbb632008-06-06 21:10:57 +0000378 Optional:
showard909c7a62008-07-15 21:52:38 +0000379 dependencies: What the test requires to run. Comma deliminated list
showard989f25d2008-10-01 11:38:11 +0000380 dependency_labels: many-to-many relationship with labels corresponding to
381 test dependencies.
showard909c7a62008-07-15 21:52:38 +0000382 experimental: If this is set to True production servers will ignore the test
383 run_verify: Whether or not the scheduler should run the verify stage
jadmanski0afbb632008-06-06 21:10:57 +0000384 """
showard909c7a62008-07-15 21:52:38 +0000385 TestTime = enum.Enum('SHORT', 'MEDIUM', 'LONG', start_value=1)
jadmanski0afbb632008-06-06 21:10:57 +0000386 # TODO(showard) - this should be merged with Job.ControlType (but right
387 # now they use opposite values)
388 Types = enum.Enum('Client', 'Server', start_value=1)
mblighe8819cd2008-02-15 16:48:40 +0000389
showarda5288b42009-07-28 20:06:08 +0000390 name = dbmodels.CharField(max_length=255, unique=True)
391 author = dbmodels.CharField(max_length=255)
392 test_class = dbmodels.CharField(max_length=255)
393 test_category = dbmodels.CharField(max_length=255)
394 dependencies = dbmodels.CharField(max_length=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000395 description = dbmodels.TextField(blank=True)
showard909c7a62008-07-15 21:52:38 +0000396 experimental = dbmodels.BooleanField(default=True)
397 run_verify = dbmodels.BooleanField(default=True)
398 test_time = dbmodels.SmallIntegerField(choices=TestTime.choices(),
399 default=TestTime.MEDIUM)
jadmanski0afbb632008-06-06 21:10:57 +0000400 test_type = dbmodels.SmallIntegerField(choices=Types.choices())
showard909c7a62008-07-15 21:52:38 +0000401 sync_count = dbmodels.IntegerField(default=1)
showarda5288b42009-07-28 20:06:08 +0000402 path = dbmodels.CharField(max_length=255, unique=True)
403 dependency_labels = dbmodels.ManyToManyField(Label, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000404
jadmanski0afbb632008-06-06 21:10:57 +0000405 name_field = 'name'
406 objects = model_logic.ExtendedManager()
mblighe8819cd2008-02-15 16:48:40 +0000407
408
jadmanski0afbb632008-06-06 21:10:57 +0000409 class Meta:
410 db_table = 'autotests'
mblighe8819cd2008-02-15 16:48:40 +0000411
showarda5288b42009-07-28 20:06:08 +0000412 def __unicode__(self):
413 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000414
415
showard2b9a88b2008-06-13 20:55:03 +0000416class Profiler(dbmodels.Model, model_logic.ModelExtensions):
417 """\
418 Required:
419 name: profiler name
420 test_type: Client or Server
421
422 Optional:
423 description: arbirary text description
424 """
showarda5288b42009-07-28 20:06:08 +0000425 name = dbmodels.CharField(max_length=255, unique=True)
showard2b9a88b2008-06-13 20:55:03 +0000426 description = dbmodels.TextField(blank=True)
427
428 name_field = 'name'
429 objects = model_logic.ExtendedManager()
430
431
432 class Meta:
433 db_table = 'profilers'
434
showarda5288b42009-07-28 20:06:08 +0000435 def __unicode__(self):
436 return unicode(self.name)
showard2b9a88b2008-06-13 20:55:03 +0000437
438
showard7c785282008-05-29 19:45:12 +0000439class AclGroup(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000440 """\
441 Required:
442 name: name of ACL group
mblighe8819cd2008-02-15 16:48:40 +0000443
jadmanski0afbb632008-06-06 21:10:57 +0000444 Optional:
445 description: arbitrary description of group
446 """
showarda5288b42009-07-28 20:06:08 +0000447 name = dbmodels.CharField(max_length=255, unique=True)
448 description = dbmodels.CharField(max_length=255, blank=True)
showard8cbaf1e2009-09-08 16:27:04 +0000449 users = dbmodels.ManyToManyField(User, blank=False)
450 hosts = dbmodels.ManyToManyField(Host, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000451
jadmanski0afbb632008-06-06 21:10:57 +0000452 name_field = 'name'
453 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000454
showard08f981b2008-06-24 21:59:03 +0000455 @staticmethod
showard3dd47c22008-07-10 00:41:36 +0000456 def check_for_acl_violation_hosts(hosts):
457 user = thread_local.get_user()
458 if user.is_superuser():
showard9dbdcda2008-10-14 17:34:36 +0000459 return
showard3dd47c22008-07-10 00:41:36 +0000460 accessible_host_ids = set(
showardd9ac4452009-02-07 02:04:37 +0000461 host.id for host in Host.objects.filter(aclgroup__users=user))
showard3dd47c22008-07-10 00:41:36 +0000462 for host in hosts:
463 # Check if the user has access to this host,
showard98ead172009-06-22 18:13:24 +0000464 # but only if it is not a metahost or a one-time-host
465 no_access = (isinstance(host, Host)
466 and not host.invalid
467 and int(host.id) not in accessible_host_ids)
468 if no_access:
showardeaa408e2009-09-11 18:45:31 +0000469 raise AclAccessViolation("%s does not have access to %s" %
470 (str(user), str(host)))
showard3dd47c22008-07-10 00:41:36 +0000471
showard9dbdcda2008-10-14 17:34:36 +0000472
473 @staticmethod
showarddc817512008-11-12 18:16:41 +0000474 def check_abort_permissions(queue_entries):
475 """
476 look for queue entries that aren't abortable, meaning
477 * the job isn't owned by this user, and
478 * the machine isn't ACL-accessible, or
479 * the machine is in the "Everyone" ACL
480 """
showard9dbdcda2008-10-14 17:34:36 +0000481 user = thread_local.get_user()
482 if user.is_superuser():
483 return
showarddc817512008-11-12 18:16:41 +0000484 not_owned = queue_entries.exclude(job__owner=user.login)
485 # I do this using ID sets instead of just Django filters because
showarda5288b42009-07-28 20:06:08 +0000486 # filtering on M2M dbmodels is broken in Django 0.96. It's better in
487 # 1.0.
488 # TODO: Use Django filters, now that we're using 1.0.
showarddc817512008-11-12 18:16:41 +0000489 accessible_ids = set(
490 entry.id for entry
showardd9ac4452009-02-07 02:04:37 +0000491 in not_owned.filter(host__aclgroup__users__login=user.login))
showarddc817512008-11-12 18:16:41 +0000492 public_ids = set(entry.id for entry
showardd9ac4452009-02-07 02:04:37 +0000493 in not_owned.filter(host__aclgroup__name='Everyone'))
showarddc817512008-11-12 18:16:41 +0000494 cannot_abort = [entry for entry in not_owned.select_related()
495 if entry.id not in accessible_ids
496 or entry.id in public_ids]
497 if len(cannot_abort) == 0:
498 return
499 entry_names = ', '.join('%s-%s/%s' % (entry.job.id, entry.job.owner,
showard3f15eed2008-11-14 22:40:48 +0000500 entry.host_or_metahost_name())
showarddc817512008-11-12 18:16:41 +0000501 for entry in cannot_abort)
502 raise AclAccessViolation('You cannot abort the following job entries: '
503 + entry_names)
showard9dbdcda2008-10-14 17:34:36 +0000504
505
showard3dd47c22008-07-10 00:41:36 +0000506 def check_for_acl_violation_acl_group(self):
507 user = thread_local.get_user()
508 if user.is_superuser():
showard8cbaf1e2009-09-08 16:27:04 +0000509 return
510 if self.name == 'Everyone':
511 raise AclAccessViolation("You cannot modify 'Everyone'!")
showard3dd47c22008-07-10 00:41:36 +0000512 if not user in self.users.all():
513 raise AclAccessViolation("You do not have access to %s"
514 % self.name)
515
516 @staticmethod
showard08f981b2008-06-24 21:59:03 +0000517 def on_host_membership_change():
518 everyone = AclGroup.objects.get(name='Everyone')
519
showard3dd47c22008-07-10 00:41:36 +0000520 # find hosts that aren't in any ACL group and add them to Everyone
showard08f981b2008-06-24 21:59:03 +0000521 # TODO(showard): this is a bit of a hack, since the fact that this query
522 # works is kind of a coincidence of Django internals. This trick
523 # doesn't work in general (on all foreign key relationships). I'll
524 # replace it with a better technique when the need arises.
showardd9ac4452009-02-07 02:04:37 +0000525 orphaned_hosts = Host.valid_objects.filter(aclgroup__id__isnull=True)
showard08f981b2008-06-24 21:59:03 +0000526 everyone.hosts.add(*orphaned_hosts.distinct())
527
528 # find hosts in both Everyone and another ACL group, and remove them
529 # from Everyone
showarda5288b42009-07-28 20:06:08 +0000530 hosts_in_everyone = Host.valid_objects.filter(aclgroup__name='Everyone')
531 acled_hosts = set()
532 for host in hosts_in_everyone:
533 # Has an ACL group other than Everyone
534 if host.aclgroup_set.count() > 1:
535 acled_hosts.add(host)
536 everyone.hosts.remove(*acled_hosts)
showard08f981b2008-06-24 21:59:03 +0000537
538
539 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000540 if (self.name == 'Everyone'):
541 raise AclAccessViolation("You cannot delete 'Everyone'!")
542 self.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000543 super(AclGroup, self).delete()
544 self.on_host_membership_change()
545
546
showard04f2cd82008-07-25 20:53:31 +0000547 def add_current_user_if_empty(self):
548 if not self.users.count():
549 self.users.add(thread_local.get_user())
550
551
showard8cbaf1e2009-09-08 16:27:04 +0000552 def perform_after_save(self, change):
553 if not change:
554 self.users.add(thread_local.get_user())
555 self.add_current_user_if_empty()
556 self.on_host_membership_change()
557
558
559 def save(self, *args, **kwargs):
560 change = bool(self.id)
561 if change:
562 # Check the original object for an ACL violation
563 AclGroup.objects.get(id=self.id).check_for_acl_violation_acl_group()
564 super(AclGroup, self).save(*args, **kwargs)
565 self.perform_after_save(change)
566
showardeb3be4d2008-04-21 20:59:26 +0000567
jadmanski0afbb632008-06-06 21:10:57 +0000568 class Meta:
569 db_table = 'acl_groups'
mblighe8819cd2008-02-15 16:48:40 +0000570
showarda5288b42009-07-28 20:06:08 +0000571 def __unicode__(self):
572 return unicode(self.name)
mblighe8819cd2008-02-15 16:48:40 +0000573
mblighe8819cd2008-02-15 16:48:40 +0000574
showard7c785282008-05-29 19:45:12 +0000575class JobManager(model_logic.ExtendedManager):
jadmanski0afbb632008-06-06 21:10:57 +0000576 'Custom manager to provide efficient status counts querying.'
577 def get_status_counts(self, job_ids):
578 """\
579 Returns a dictionary mapping the given job IDs to their status
580 count dictionaries.
581 """
582 if not job_ids:
583 return {}
584 id_list = '(%s)' % ','.join(str(job_id) for job_id in job_ids)
585 cursor = connection.cursor()
586 cursor.execute("""
showardd3dc1992009-04-22 21:01:40 +0000587 SELECT job_id, status, aborted, complete, COUNT(*)
jadmanski0afbb632008-06-06 21:10:57 +0000588 FROM host_queue_entries
589 WHERE job_id IN %s
showardd3dc1992009-04-22 21:01:40 +0000590 GROUP BY job_id, status, aborted, complete
jadmanski0afbb632008-06-06 21:10:57 +0000591 """ % id_list)
showard25aaf3f2009-06-08 23:23:40 +0000592 all_job_counts = dict((job_id, {}) for job_id in job_ids)
showardd3dc1992009-04-22 21:01:40 +0000593 for job_id, status, aborted, complete, count in cursor.fetchall():
showard25aaf3f2009-06-08 23:23:40 +0000594 job_dict = all_job_counts[job_id]
showardd3dc1992009-04-22 21:01:40 +0000595 full_status = HostQueueEntry.compute_full_status(status, aborted,
596 complete)
showardb6d16622009-05-26 19:35:29 +0000597 job_dict.setdefault(full_status, 0)
showard25aaf3f2009-06-08 23:23:40 +0000598 job_dict[full_status] += count
jadmanski0afbb632008-06-06 21:10:57 +0000599 return all_job_counts
mblighe8819cd2008-02-15 16:48:40 +0000600
601
showard7c785282008-05-29 19:45:12 +0000602class Job(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000603 """\
604 owner: username of job owner
605 name: job name (does not have to be unique)
606 priority: Low, Medium, High, Urgent (or 0-3)
607 control_file: contents of control file
608 control_type: Client or Server
609 created_on: date of job creation
610 submitted_on: date of job submission
showard2bab8f42008-11-12 18:15:22 +0000611 synch_count: how many hosts should be used per autoserv execution
showard909c7a62008-07-15 21:52:38 +0000612 run_verify: Whether or not to run the verify phase
showard12f3e322009-05-13 21:27:42 +0000613 timeout: hours from queuing time until job times out
614 max_runtime_hrs: hours from job starting time until job times out
showard542e8402008-09-19 20:16:18 +0000615 email_list: list of people to email on completion delimited by any of:
616 white space, ',', ':', ';'
showard989f25d2008-10-01 11:38:11 +0000617 dependency_labels: many-to-many relationship with labels corresponding to
618 job dependencies
showard21baa452008-10-21 00:08:39 +0000619 reboot_before: Never, If dirty, or Always
620 reboot_after: Never, If all tests passed, or Always
showarda1e74b32009-05-12 17:32:04 +0000621 parse_failed_repair: if True, a failed repair launched by this job will have
622 its results parsed as part of the job.
jadmanski0afbb632008-06-06 21:10:57 +0000623 """
showardb1e51872008-10-07 11:08:18 +0000624 DEFAULT_TIMEOUT = global_config.global_config.get_config_value(
625 'AUTOTEST_WEB', 'job_timeout_default', default=240)
showard12f3e322009-05-13 21:27:42 +0000626 DEFAULT_MAX_RUNTIME_HRS = global_config.global_config.get_config_value(
627 'AUTOTEST_WEB', 'job_max_runtime_hrs_default', default=72)
showarda1e74b32009-05-12 17:32:04 +0000628 DEFAULT_PARSE_FAILED_REPAIR = global_config.global_config.get_config_value(
629 'AUTOTEST_WEB', 'parse_failed_repair_default', type=bool,
630 default=False)
showardb1e51872008-10-07 11:08:18 +0000631
jadmanski0afbb632008-06-06 21:10:57 +0000632 Priority = enum.Enum('Low', 'Medium', 'High', 'Urgent')
633 ControlType = enum.Enum('Server', 'Client', start_value=1)
mblighe8819cd2008-02-15 16:48:40 +0000634
showarda5288b42009-07-28 20:06:08 +0000635 owner = dbmodels.CharField(max_length=255)
636 name = dbmodels.CharField(max_length=255)
jadmanski0afbb632008-06-06 21:10:57 +0000637 priority = dbmodels.SmallIntegerField(choices=Priority.choices(),
638 blank=True, # to allow 0
639 default=Priority.MEDIUM)
640 control_file = dbmodels.TextField()
641 control_type = dbmodels.SmallIntegerField(choices=ControlType.choices(),
showardb1e51872008-10-07 11:08:18 +0000642 blank=True, # to allow 0
643 default=ControlType.CLIENT)
showard68c7aa02008-10-09 16:49:11 +0000644 created_on = dbmodels.DateTimeField()
showard2bab8f42008-11-12 18:15:22 +0000645 synch_count = dbmodels.IntegerField(null=True, default=1)
showardb1e51872008-10-07 11:08:18 +0000646 timeout = dbmodels.IntegerField(default=DEFAULT_TIMEOUT)
showard9976ce92008-10-15 20:28:13 +0000647 run_verify = dbmodels.BooleanField(default=True)
showarda5288b42009-07-28 20:06:08 +0000648 email_list = dbmodels.CharField(max_length=250, blank=True)
649 dependency_labels = dbmodels.ManyToManyField(Label, blank=True)
showard21baa452008-10-21 00:08:39 +0000650 reboot_before = dbmodels.SmallIntegerField(choices=RebootBefore.choices(),
651 blank=True,
showard0fc38302008-10-23 00:44:07 +0000652 default=DEFAULT_REBOOT_BEFORE)
showard21baa452008-10-21 00:08:39 +0000653 reboot_after = dbmodels.SmallIntegerField(choices=RebootAfter.choices(),
654 blank=True,
showard0fc38302008-10-23 00:44:07 +0000655 default=DEFAULT_REBOOT_AFTER)
showarda1e74b32009-05-12 17:32:04 +0000656 parse_failed_repair = dbmodels.BooleanField(
657 default=DEFAULT_PARSE_FAILED_REPAIR)
showard12f3e322009-05-13 21:27:42 +0000658 max_runtime_hrs = dbmodels.IntegerField(default=DEFAULT_MAX_RUNTIME_HRS)
mblighe8819cd2008-02-15 16:48:40 +0000659
660
jadmanski0afbb632008-06-06 21:10:57 +0000661 # custom manager
662 objects = JobManager()
mblighe8819cd2008-02-15 16:48:40 +0000663
664
jadmanski0afbb632008-06-06 21:10:57 +0000665 def is_server_job(self):
666 return self.control_type == self.ControlType.SERVER
mblighe8819cd2008-02-15 16:48:40 +0000667
668
jadmanski0afbb632008-06-06 21:10:57 +0000669 @classmethod
showarda1e74b32009-05-12 17:32:04 +0000670 def create(cls, owner, options, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000671 """\
672 Creates a job by taking some information (the listed args)
673 and filling in the rest of the necessary information.
674 """
showard3dd47c22008-07-10 00:41:36 +0000675 AclGroup.check_for_acl_violation_hosts(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000676 job = cls.add_object(
showarda1e74b32009-05-12 17:32:04 +0000677 owner=owner,
678 name=options['name'],
679 priority=options['priority'],
680 control_file=options['control_file'],
681 control_type=options['control_type'],
682 synch_count=options.get('synch_count'),
683 timeout=options.get('timeout'),
showard12f3e322009-05-13 21:27:42 +0000684 max_runtime_hrs=options.get('max_runtime_hrs'),
showarda1e74b32009-05-12 17:32:04 +0000685 run_verify=options.get('run_verify'),
686 email_list=options.get('email_list'),
687 reboot_before=options.get('reboot_before'),
688 reboot_after=options.get('reboot_after'),
689 parse_failed_repair=options.get('parse_failed_repair'),
showard68c7aa02008-10-09 16:49:11 +0000690 created_on=datetime.now())
mblighe8819cd2008-02-15 16:48:40 +0000691
showarda1e74b32009-05-12 17:32:04 +0000692 job.dependency_labels = options['dependencies']
jadmanski0afbb632008-06-06 21:10:57 +0000693 return job
mblighe8819cd2008-02-15 16:48:40 +0000694
695
showard29f7cd22009-04-29 21:16:24 +0000696 def queue(self, hosts, atomic_group=None, is_template=False):
showardc92da832009-04-07 18:14:34 +0000697 """Enqueue a job on the given hosts."""
698 if atomic_group and not hosts:
699 # No hosts or labels are required to queue an atomic group
700 # Job. However, if they are given, we respect them below.
showard29f7cd22009-04-29 21:16:24 +0000701 atomic_group.enqueue_job(self, is_template=is_template)
jadmanski0afbb632008-06-06 21:10:57 +0000702 for host in hosts:
showard29f7cd22009-04-29 21:16:24 +0000703 host.enqueue_job(self, atomic_group=atomic_group,
704 is_template=is_template)
705
706
707 def create_recurring_job(self, start_date, loop_period, loop_count, owner):
708 rec = RecurringRun(job=self, start_date=start_date,
709 loop_period=loop_period,
710 loop_count=loop_count,
711 owner=User.objects.get(login=owner))
712 rec.save()
713 return rec.id
mblighe8819cd2008-02-15 16:48:40 +0000714
715
jadmanski0afbb632008-06-06 21:10:57 +0000716 def user(self):
717 try:
718 return User.objects.get(login=self.owner)
719 except self.DoesNotExist:
720 return None
mblighe8819cd2008-02-15 16:48:40 +0000721
722
showard98863972008-10-29 21:14:56 +0000723 def abort(self, aborted_by):
724 for queue_entry in self.hostqueueentry_set.all():
725 queue_entry.abort(aborted_by)
726
727
jadmanski0afbb632008-06-06 21:10:57 +0000728 class Meta:
729 db_table = 'jobs'
mblighe8819cd2008-02-15 16:48:40 +0000730
showarda5288b42009-07-28 20:06:08 +0000731 def __unicode__(self):
732 return u'%s (%s-%s)' % (self.name, self.id, self.owner)
mblighe8819cd2008-02-15 16:48:40 +0000733
734
showard7c785282008-05-29 19:45:12 +0000735class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000736 job = dbmodels.ForeignKey(Job)
737 host = dbmodels.ForeignKey(Host)
mblighe8819cd2008-02-15 16:48:40 +0000738
jadmanski0afbb632008-06-06 21:10:57 +0000739 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000740
jadmanski0afbb632008-06-06 21:10:57 +0000741 class Meta:
742 db_table = 'ineligible_host_queues'
mblighe8819cd2008-02-15 16:48:40 +0000743
mblighe8819cd2008-02-15 16:48:40 +0000744
showard7c785282008-05-29 19:45:12 +0000745class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
showardeaa408e2009-09-11 18:45:31 +0000746 Status = host_queue_entry_states.Status
747 ACTIVE_STATUSES = host_queue_entry_states.ACTIVE_STATUSES
748 COMPLETE_STATUSES = host_queue_entry_states.COMPLETE_STATUSES
showarda3ab0d52008-11-03 19:03:47 +0000749
jadmanski0afbb632008-06-06 21:10:57 +0000750 job = dbmodels.ForeignKey(Job)
751 host = dbmodels.ForeignKey(Host, blank=True, null=True)
showarda5288b42009-07-28 20:06:08 +0000752 status = dbmodels.CharField(max_length=255)
jadmanski0afbb632008-06-06 21:10:57 +0000753 meta_host = dbmodels.ForeignKey(Label, blank=True, null=True,
754 db_column='meta_host')
755 active = dbmodels.BooleanField(default=False)
756 complete = dbmodels.BooleanField(default=False)
showardb8471e32008-07-03 19:51:08 +0000757 deleted = dbmodels.BooleanField(default=False)
showarda5288b42009-07-28 20:06:08 +0000758 execution_subdir = dbmodels.CharField(max_length=255, blank=True,
759 default='')
showard89f84db2009-03-12 20:39:13 +0000760 # If atomic_group is set, this is a virtual HostQueueEntry that will
761 # be expanded into many actual hosts within the group at schedule time.
762 atomic_group = dbmodels.ForeignKey(AtomicGroup, blank=True, null=True)
showardd3dc1992009-04-22 21:01:40 +0000763 aborted = dbmodels.BooleanField(default=False)
showardd3771cc2009-10-07 20:48:22 +0000764 started_on = dbmodels.DateTimeField(null=True, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000765
jadmanski0afbb632008-06-06 21:10:57 +0000766 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000767
mblighe8819cd2008-02-15 16:48:40 +0000768
showard2bab8f42008-11-12 18:15:22 +0000769 def __init__(self, *args, **kwargs):
770 super(HostQueueEntry, self).__init__(*args, **kwargs)
771 self._record_attributes(['status'])
772
773
showard29f7cd22009-04-29 21:16:24 +0000774 @classmethod
775 def create(cls, job, host=None, meta_host=None, atomic_group=None,
776 is_template=False):
777 if is_template:
778 status = cls.Status.TEMPLATE
779 else:
780 status = cls.Status.QUEUED
781
782 return cls(job=job, host=host, meta_host=meta_host,
783 atomic_group=atomic_group, status=status)
784
785
showarda5288b42009-07-28 20:06:08 +0000786 def save(self, *args, **kwargs):
showard2bab8f42008-11-12 18:15:22 +0000787 self._set_active_and_complete()
showarda5288b42009-07-28 20:06:08 +0000788 super(HostQueueEntry, self).save(*args, **kwargs)
showard2bab8f42008-11-12 18:15:22 +0000789 self._check_for_updated_attributes()
790
791
showardc0ac3a72009-07-08 21:14:45 +0000792 def execution_path(self):
793 """
794 Path to this entry's results (relative to the base results directory).
795 """
796 return self.execution_subdir
797
798
showard3f15eed2008-11-14 22:40:48 +0000799 def host_or_metahost_name(self):
800 if self.host:
801 return self.host.hostname
showard7890e792009-07-28 20:10:20 +0000802 elif self.meta_host:
showard3f15eed2008-11-14 22:40:48 +0000803 return self.meta_host.name
showard7890e792009-07-28 20:10:20 +0000804 else:
805 assert self.atomic_group, "no host, meta_host or atomic group!"
806 return self.atomic_group.name
showard3f15eed2008-11-14 22:40:48 +0000807
808
showard2bab8f42008-11-12 18:15:22 +0000809 def _set_active_and_complete(self):
showardd3dc1992009-04-22 21:01:40 +0000810 if self.status in self.ACTIVE_STATUSES:
showard2bab8f42008-11-12 18:15:22 +0000811 self.active, self.complete = True, False
812 elif self.status in self.COMPLETE_STATUSES:
813 self.active, self.complete = False, True
814 else:
815 self.active, self.complete = False, False
816
817
818 def on_attribute_changed(self, attribute, old_value):
819 assert attribute == 'status'
showardf1175bb2009-06-17 19:34:36 +0000820 logging.info('%s/%d (%d) -> %s' % (self.host, self.job.id, self.id,
821 self.status))
showard2bab8f42008-11-12 18:15:22 +0000822
823
jadmanski0afbb632008-06-06 21:10:57 +0000824 def is_meta_host_entry(self):
825 'True if this is a entry has a meta_host instead of a host.'
826 return self.host is None and self.meta_host is not None
mblighe8819cd2008-02-15 16:48:40 +0000827
showarda3ab0d52008-11-03 19:03:47 +0000828
showard4c119042008-09-29 19:16:18 +0000829 def log_abort(self, user):
showard98863972008-10-29 21:14:56 +0000830 if user is None:
831 # automatic system abort (i.e. job timeout)
832 return
showard4c119042008-09-29 19:16:18 +0000833 abort_log = AbortedHostQueueEntry(queue_entry=self, aborted_by=user)
834 abort_log.save()
835
showarda3ab0d52008-11-03 19:03:47 +0000836
showard4c119042008-09-29 19:16:18 +0000837 def abort(self, user):
showarda3ab0d52008-11-03 19:03:47 +0000838 # this isn't completely immune to race conditions since it's not atomic,
839 # but it should be safe given the scheduler's behavior.
showardd3dc1992009-04-22 21:01:40 +0000840 if not self.complete and not self.aborted:
showard4c119042008-09-29 19:16:18 +0000841 self.log_abort(user)
showardd3dc1992009-04-22 21:01:40 +0000842 self.aborted = True
showarda3ab0d52008-11-03 19:03:47 +0000843 self.save()
mblighe8819cd2008-02-15 16:48:40 +0000844
showardd3dc1992009-04-22 21:01:40 +0000845
846 @classmethod
847 def compute_full_status(cls, status, aborted, complete):
848 if aborted and not complete:
849 return 'Aborted (%s)' % status
850 return status
851
852
853 def full_status(self):
854 return self.compute_full_status(self.status, self.aborted,
855 self.complete)
856
857
858 def _postprocess_object_dict(self, object_dict):
859 object_dict['full_status'] = self.full_status()
860
861
jadmanski0afbb632008-06-06 21:10:57 +0000862 class Meta:
863 db_table = 'host_queue_entries'
mblighe8819cd2008-02-15 16:48:40 +0000864
showard12f3e322009-05-13 21:27:42 +0000865
showard4c119042008-09-29 19:16:18 +0000866
showarda5288b42009-07-28 20:06:08 +0000867 def __unicode__(self):
showard12f3e322009-05-13 21:27:42 +0000868 hostname = None
869 if self.host:
870 hostname = self.host.hostname
showarda5288b42009-07-28 20:06:08 +0000871 return u"%s/%d (%d)" % (hostname, self.job.id, self.id)
showard12f3e322009-05-13 21:27:42 +0000872
873
showard4c119042008-09-29 19:16:18 +0000874class AbortedHostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
875 queue_entry = dbmodels.OneToOneField(HostQueueEntry, primary_key=True)
876 aborted_by = dbmodels.ForeignKey(User)
showard68c7aa02008-10-09 16:49:11 +0000877 aborted_on = dbmodels.DateTimeField()
showard4c119042008-09-29 19:16:18 +0000878
879 objects = model_logic.ExtendedManager()
880
showard68c7aa02008-10-09 16:49:11 +0000881
showarda5288b42009-07-28 20:06:08 +0000882 def save(self, *args, **kwargs):
showard68c7aa02008-10-09 16:49:11 +0000883 self.aborted_on = datetime.now()
showarda5288b42009-07-28 20:06:08 +0000884 super(AbortedHostQueueEntry, self).save(*args, **kwargs)
showard68c7aa02008-10-09 16:49:11 +0000885
showard4c119042008-09-29 19:16:18 +0000886 class Meta:
887 db_table = 'aborted_host_queue_entries'
showard29f7cd22009-04-29 21:16:24 +0000888
889
890class RecurringRun(dbmodels.Model, model_logic.ModelExtensions):
891 """\
892 job: job to use as a template
893 owner: owner of the instantiated template
894 start_date: Run the job at scheduled date
895 loop_period: Re-run (loop) the job periodically
896 (in every loop_period seconds)
897 loop_count: Re-run (loop) count
898 """
899
900 job = dbmodels.ForeignKey(Job)
901 owner = dbmodels.ForeignKey(User)
902 start_date = dbmodels.DateTimeField()
903 loop_period = dbmodels.IntegerField(blank=True)
904 loop_count = dbmodels.IntegerField(blank=True)
905
906 objects = model_logic.ExtendedManager()
907
908 class Meta:
909 db_table = 'recurring_run'
910
showarda5288b42009-07-28 20:06:08 +0000911 def __unicode__(self):
912 return u'RecurringRun(job %s, start %s, period %s, count %s)' % (
showard29f7cd22009-04-29 21:16:24 +0000913 self.job.id, self.start_date, self.loop_period, self.loop_count)
showard6d7b2ff2009-06-10 00:16:47 +0000914
915
916class SpecialTask(dbmodels.Model, model_logic.ModelExtensions):
917 """\
918 Tasks to run on hosts at the next time they are in the Ready state. Use this
919 for high-priority tasks, such as forced repair or forced reinstall.
920
921 host: host to run this task on
showard2fe3f1d2009-07-06 20:19:11 +0000922 task: special task to run
showard6d7b2ff2009-06-10 00:16:47 +0000923 time_requested: date and time the request for this task was made
924 is_active: task is currently running
925 is_complete: task has finished running
showard2fe3f1d2009-07-06 20:19:11 +0000926 time_started: date and time the task started
showard2fe3f1d2009-07-06 20:19:11 +0000927 queue_entry: Host queue entry waiting on this task (or None, if task was not
928 started in preparation of a job)
showard6d7b2ff2009-06-10 00:16:47 +0000929 """
showard2fe3f1d2009-07-06 20:19:11 +0000930 Task = enum.Enum('Verify', 'Cleanup', 'Repair', string_values=True)
showard6d7b2ff2009-06-10 00:16:47 +0000931
932 host = dbmodels.ForeignKey(Host, blank=False, null=False)
showarda5288b42009-07-28 20:06:08 +0000933 task = dbmodels.CharField(max_length=64, choices=Task.choices(),
showard6d7b2ff2009-06-10 00:16:47 +0000934 blank=False, null=False)
935 time_requested = dbmodels.DateTimeField(auto_now_add=True, blank=False,
936 null=False)
937 is_active = dbmodels.BooleanField(default=False, blank=False, null=False)
938 is_complete = dbmodels.BooleanField(default=False, blank=False, null=False)
showardc0ac3a72009-07-08 21:14:45 +0000939 time_started = dbmodels.DateTimeField(null=True, blank=True)
showard2fe3f1d2009-07-06 20:19:11 +0000940 queue_entry = dbmodels.ForeignKey(HostQueueEntry, blank=True, null=True)
showard6d7b2ff2009-06-10 00:16:47 +0000941
942 objects = model_logic.ExtendedManager()
943
944
showarded2afea2009-07-07 20:54:07 +0000945 def execution_path(self):
showardc0ac3a72009-07-08 21:14:45 +0000946 """@see HostQueueEntry.execution_path()"""
showarded2afea2009-07-07 20:54:07 +0000947 return 'hosts/%s/%s-%s' % (self.host.hostname, self.id,
948 self.task.lower())
949
950
showardc0ac3a72009-07-08 21:14:45 +0000951 # property to emulate HostQueueEntry.status
952 @property
953 def status(self):
954 """
955 Return a host queue entry status appropriate for this task. Although
956 SpecialTasks are not HostQueueEntries, it is helpful to the user to
957 present similar statuses.
958 """
959 if self.is_complete:
960 return HostQueueEntry.Status.COMPLETED
961 if self.is_active:
962 return HostQueueEntry.Status.RUNNING
963 return HostQueueEntry.Status.QUEUED
964
965
966 # property to emulate HostQueueEntry.started_on
967 @property
968 def started_on(self):
969 return self.time_started
970
971
showard6d7b2ff2009-06-10 00:16:47 +0000972 @classmethod
973 def schedule_special_task(cls, hosts, task):
showard474d1362009-08-20 23:32:01 +0000974 """
showard2fe3f1d2009-07-06 20:19:11 +0000975 Schedules hosts for a special task, if the task is not already scheduled
showard6d7b2ff2009-06-10 00:16:47 +0000976 """
977 for host in hosts:
showard2fe3f1d2009-07-06 20:19:11 +0000978 if not SpecialTask.objects.filter(host__id=host.id, task=task,
showarded2afea2009-07-07 20:54:07 +0000979 is_active=False,
980 is_complete=False):
showard2fe3f1d2009-07-06 20:19:11 +0000981 special_task = SpecialTask(host=host, task=task)
982 special_task.save()
983
984
showarded2afea2009-07-07 20:54:07 +0000985 def activate(self):
showard474d1362009-08-20 23:32:01 +0000986 """
987 Sets a task as active and sets the time started to the current time.
showard2fe3f1d2009-07-06 20:19:11 +0000988 """
showard97446882009-07-20 22:37:28 +0000989 logging.info('Starting: %s', self)
showard2fe3f1d2009-07-06 20:19:11 +0000990 self.is_active = True
991 self.time_started = datetime.now()
992 self.save()
993
994
995 def finish(self):
showard474d1362009-08-20 23:32:01 +0000996 """
showard2fe3f1d2009-07-06 20:19:11 +0000997 Sets a task as completed
998 """
showard97446882009-07-20 22:37:28 +0000999 logging.info('Finished: %s', self)
showarded2afea2009-07-07 20:54:07 +00001000 self.is_active = False
showard2fe3f1d2009-07-06 20:19:11 +00001001 self.is_complete = True
1002 self.save()
showard6d7b2ff2009-06-10 00:16:47 +00001003
1004
1005 class Meta:
1006 db_table = 'special_tasks'
1007
showard474d1362009-08-20 23:32:01 +00001008
showarda5288b42009-07-28 20:06:08 +00001009 def __unicode__(self):
1010 result = u'Special Task %s (host %s, task %s, time %s)' % (
showarded2afea2009-07-07 20:54:07 +00001011 self.id, self.host, self.task, self.time_requested)
showard6d7b2ff2009-06-10 00:16:47 +00001012 if self.is_complete:
showarda5288b42009-07-28 20:06:08 +00001013 result += u' (completed)'
showard6d7b2ff2009-06-10 00:16:47 +00001014 elif self.is_active:
showarda5288b42009-07-28 20:06:08 +00001015 result += u' (active)'
showard6d7b2ff2009-06-10 00:16:47 +00001016
1017 return result