blob: d511461ba1897fef074bd408995512292281b176 [file] [log] [blame]
showardfb2a7fa2008-07-17 17:04:12 +00001from datetime import datetime
showard7c785282008-05-29 19:45:12 +00002from django.db import models as dbmodels, connection
showarddf062562008-07-03 19:56:37 +00003from frontend.afe import model_logic
showard3dd47c22008-07-10 00:41:36 +00004from frontend import settings, thread_local
showarddf062562008-07-03 19:56:37 +00005from autotest_lib.client.common_lib import enum, host_protections
mblighe8819cd2008-02-15 16:48:40 +00006
7
mblighe8819cd2008-02-15 16:48:40 +00008class AclAccessViolation(Exception):
jadmanski0afbb632008-06-06 21:10:57 +00009 """\
10 Raised when an operation is attempted with proper permissions as
11 dictated by ACLs.
12 """
mblighe8819cd2008-02-15 16:48:40 +000013
14
showard7c785282008-05-29 19:45:12 +000015class Label(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +000016 """\
17 Required:
18 name: label name
mblighe8819cd2008-02-15 16:48:40 +000019
jadmanski0afbb632008-06-06 21:10:57 +000020 Optional:
21 kernel_config: url/path to kernel config to use for jobs run on this
22 label
23 platform: if True, this is a platform label (defaults to False)
24 """
25 name = dbmodels.CharField(maxlength=255, unique=True)
26 kernel_config = dbmodels.CharField(maxlength=255, blank=True)
27 platform = dbmodels.BooleanField(default=False)
28 invalid = dbmodels.BooleanField(default=False,
29 editable=settings.FULL_ADMIN)
mblighe8819cd2008-02-15 16:48:40 +000030
jadmanski0afbb632008-06-06 21:10:57 +000031 name_field = 'name'
32 objects = model_logic.ExtendedManager()
33 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +000034
jadmanski0afbb632008-06-06 21:10:57 +000035 def clean_object(self):
36 self.host_set.clear()
mblighe8819cd2008-02-15 16:48:40 +000037
38
jadmanski0afbb632008-06-06 21:10:57 +000039 def enqueue_job(self, job):
40 'Enqueue a job on any host of this label.'
41 queue_entry = HostQueueEntry(meta_host=self, job=job,
42 status=Job.Status.QUEUED,
43 priority=job.priority)
44 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +000045
46
jadmanski0afbb632008-06-06 21:10:57 +000047 class Meta:
48 db_table = 'labels'
mblighe8819cd2008-02-15 16:48:40 +000049
jadmanski0afbb632008-06-06 21:10:57 +000050 class Admin:
51 list_display = ('name', 'kernel_config')
52 # see Host.Admin
53 manager = model_logic.ValidObjectsManager()
mblighe8819cd2008-02-15 16:48:40 +000054
jadmanski0afbb632008-06-06 21:10:57 +000055 def __str__(self):
56 return self.name
mblighe8819cd2008-02-15 16:48:40 +000057
58
showardfb2a7fa2008-07-17 17:04:12 +000059class User(dbmodels.Model, model_logic.ModelExtensions):
60 """\
61 Required:
62 login :user login name
63
64 Optional:
65 access_level: 0=User (default), 1=Admin, 100=Root
66 """
67 ACCESS_ROOT = 100
68 ACCESS_ADMIN = 1
69 ACCESS_USER = 0
70
71 login = dbmodels.CharField(maxlength=255, unique=True)
72 access_level = dbmodels.IntegerField(default=ACCESS_USER, blank=True)
73
74 name_field = 'login'
75 objects = model_logic.ExtendedManager()
76
77
78 def save(self):
79 # is this a new object being saved for the first time?
80 first_time = (self.id is None)
81 user = thread_local.get_user()
82 if user and not user.is_superuser():
83 raise AclAccessViolation("You cannot modify users!")
84 super(User, self).save()
85 if first_time:
86 everyone = AclGroup.objects.get(name='Everyone')
87 everyone.users.add(self)
88
89
90 def is_superuser(self):
91 return self.access_level >= self.ACCESS_ROOT
92
93
94 class Meta:
95 db_table = 'users'
96
97 class Admin:
98 list_display = ('login', 'access_level')
99 search_fields = ('login',)
100
101 def __str__(self):
102 return self.login
103
104
showard7c785282008-05-29 19:45:12 +0000105class Host(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +0000106 """\
107 Required:
108 hostname
mblighe8819cd2008-02-15 16:48:40 +0000109
jadmanski0afbb632008-06-06 21:10:57 +0000110 optional:
111 locked: host is locked and will not be queued
mblighe8819cd2008-02-15 16:48:40 +0000112
jadmanski0afbb632008-06-06 21:10:57 +0000113 Internal:
114 synch_id: currently unused
115 status: string describing status of host
116 """
117 Status = enum.Enum('Verifying', 'Running', 'Ready', 'Repairing',
118 'Repair Failed', 'Dead', 'Rebooting',
119 string_values=True)
mblighe8819cd2008-02-15 16:48:40 +0000120
jadmanski0afbb632008-06-06 21:10:57 +0000121 hostname = dbmodels.CharField(maxlength=255, unique=True)
122 labels = dbmodels.ManyToManyField(Label, blank=True,
123 filter_interface=dbmodels.HORIZONTAL)
124 locked = dbmodels.BooleanField(default=False)
125 synch_id = dbmodels.IntegerField(blank=True, null=True,
126 editable=settings.FULL_ADMIN)
127 status = dbmodels.CharField(maxlength=255, default=Status.READY,
128 choices=Status.choices(),
129 editable=settings.FULL_ADMIN)
130 invalid = dbmodels.BooleanField(default=False,
131 editable=settings.FULL_ADMIN)
showardd5afc2f2008-08-12 17:19:44 +0000132 protection = dbmodels.SmallIntegerField(null=False, blank=True,
showarddf062562008-07-03 19:56:37 +0000133 choices=host_protections.choices,
134 default=host_protections.default)
showardfb2a7fa2008-07-17 17:04:12 +0000135 locked_by = dbmodels.ForeignKey(User, null=True, blank=True, editable=False)
136 lock_time = dbmodels.DateTimeField(null=True, blank=True, editable=False)
mblighe8819cd2008-02-15 16:48:40 +0000137
jadmanski0afbb632008-06-06 21:10:57 +0000138 name_field = 'hostname'
139 objects = model_logic.ExtendedManager()
140 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +0000141
showardb8471e32008-07-03 19:51:08 +0000142 @staticmethod
143 def create_one_time_host(hostname):
144 query = Host.objects.filter(hostname=hostname)
145 if query.count() == 0:
146 host = Host(hostname=hostname, invalid=True)
showarda8411af2008-08-07 22:35:58 +0000147 host.do_validate()
showardb8471e32008-07-03 19:51:08 +0000148 else:
149 host = query[0]
150 if not host.invalid:
151 raise model_logic.ValidationError({
152 'hostname' : '%s already exists!' % hostname
153 })
154 host.clean_object()
showarde65d2af2008-07-30 23:34:21 +0000155 AclGroup.objects.get(name='Everyone').hosts.add(host)
showardb8471e32008-07-03 19:51:08 +0000156 host.status = Host.Status.READY
showard1ab512b2008-07-30 23:39:04 +0000157 host.protection = host_protections.Protection.DO_NOT_REPAIR
showardb8471e32008-07-03 19:51:08 +0000158 host.save()
159 return host
mbligh5244cbb2008-04-24 20:39:52 +0000160
jadmanski0afbb632008-06-06 21:10:57 +0000161 def clean_object(self):
162 self.aclgroup_set.clear()
163 self.labels.clear()
mblighe8819cd2008-02-15 16:48:40 +0000164
165
jadmanski0afbb632008-06-06 21:10:57 +0000166 def save(self):
167 # extra spaces in the hostname can be a sneaky source of errors
168 self.hostname = self.hostname.strip()
169 # is this a new object being saved for the first time?
170 first_time = (self.id is None)
showard3dd47c22008-07-10 00:41:36 +0000171 if not first_time:
172 AclGroup.check_for_acl_violation_hosts([self])
showardfb2a7fa2008-07-17 17:04:12 +0000173 if self.locked and not self.locked_by:
174 self.locked_by = thread_local.get_user()
175 self.lock_time = datetime.now()
176 elif not self.locked and self.locked_by:
177 self.locked_by = None
178 self.lock_time = None
jadmanski0afbb632008-06-06 21:10:57 +0000179 super(Host, self).save()
180 if first_time:
181 everyone = AclGroup.objects.get(name='Everyone')
182 everyone.hosts.add(self)
mblighe8819cd2008-02-15 16:48:40 +0000183
showardb8471e32008-07-03 19:51:08 +0000184 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000185 AclGroup.check_for_acl_violation_hosts([self])
showardb8471e32008-07-03 19:51:08 +0000186 for queue_entry in self.hostqueueentry_set.all():
187 queue_entry.deleted = True
188 queue_entry.abort()
189 super(Host, self).delete()
190
mblighe8819cd2008-02-15 16:48:40 +0000191
jadmanski0afbb632008-06-06 21:10:57 +0000192 def enqueue_job(self, job):
193 ' Enqueue a job on this host.'
194 queue_entry = HostQueueEntry(host=self, job=job,
195 status=Job.Status.QUEUED,
196 priority=job.priority)
197 # allow recovery of dead hosts from the frontend
198 if not self.active_queue_entry() and self.is_dead():
199 self.status = Host.Status.READY
200 self.save()
201 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000202
showard08f981b2008-06-24 21:59:03 +0000203 block = IneligibleHostQueue(job=job, host=self)
204 block.save()
205
mblighe8819cd2008-02-15 16:48:40 +0000206
jadmanski0afbb632008-06-06 21:10:57 +0000207 def platform(self):
208 # TODO(showard): slighly hacky?
209 platforms = self.labels.filter(platform=True)
210 if len(platforms) == 0:
211 return None
212 return platforms[0]
213 platform.short_description = 'Platform'
mblighe8819cd2008-02-15 16:48:40 +0000214
215
jadmanski0afbb632008-06-06 21:10:57 +0000216 def is_dead(self):
217 return self.status == Host.Status.REPAIR_FAILED
mbligh3cab4a72008-03-05 23:19:09 +0000218
219
jadmanski0afbb632008-06-06 21:10:57 +0000220 def active_queue_entry(self):
221 active = list(self.hostqueueentry_set.filter(active=True))
222 if not active:
223 return None
224 assert len(active) == 1, ('More than one active entry for '
225 'host ' + self.hostname)
226 return active[0]
mblighe8819cd2008-02-15 16:48:40 +0000227
228
jadmanski0afbb632008-06-06 21:10:57 +0000229 class Meta:
230 db_table = 'hosts'
mblighe8819cd2008-02-15 16:48:40 +0000231
jadmanski0afbb632008-06-06 21:10:57 +0000232 class Admin:
233 # TODO(showard) - showing platform requires a SQL query for
234 # each row (since labels are many-to-many) - should we remove
235 # it?
236 list_display = ('hostname', 'platform', 'locked', 'status')
showarddf062562008-07-03 19:56:37 +0000237 list_filter = ('labels', 'locked', 'protection')
jadmanski0afbb632008-06-06 21:10:57 +0000238 search_fields = ('hostname', 'status')
239 # undocumented Django feature - if you set manager here, the
240 # admin code will use it, otherwise it'll use a default Manager
241 manager = model_logic.ValidObjectsManager()
mblighe8819cd2008-02-15 16:48:40 +0000242
jadmanski0afbb632008-06-06 21:10:57 +0000243 def __str__(self):
244 return self.hostname
mblighe8819cd2008-02-15 16:48:40 +0000245
246
showard7c785282008-05-29 19:45:12 +0000247class Test(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000248 """\
249 Required:
showard909c7a62008-07-15 21:52:38 +0000250 author: author name
251 description: description of the test
jadmanski0afbb632008-06-06 21:10:57 +0000252 name: test name
showard909c7a62008-07-15 21:52:38 +0000253 time: short, medium, long
254 test_class: This describes the class for your the test belongs in.
255 test_category: This describes the category for your tests
jadmanski0afbb632008-06-06 21:10:57 +0000256 test_type: Client or Server
257 path: path to pass to run_test()
258 synch_type: whether the test should run synchronously or asynchronously
showard909c7a62008-07-15 21:52:38 +0000259 sync_count: is a number >=1 (1 being the default). If it's 1, then it's an
260 async job. If it's >1 it's sync job for that number of machines
261 i.e. if sync_count = 2 it is a sync job that requires two
262 machines.
jadmanski0afbb632008-06-06 21:10:57 +0000263 Optional:
showard909c7a62008-07-15 21:52:38 +0000264 dependencies: What the test requires to run. Comma deliminated list
265 experimental: If this is set to True production servers will ignore the test
266 run_verify: Whether or not the scheduler should run the verify stage
jadmanski0afbb632008-06-06 21:10:57 +0000267 """
showard909c7a62008-07-15 21:52:38 +0000268 TestTime = enum.Enum('SHORT', 'MEDIUM', 'LONG', start_value=1)
jadmanski0afbb632008-06-06 21:10:57 +0000269 SynchType = enum.Enum('Asynchronous', 'Synchronous', start_value=1)
270 # TODO(showard) - this should be merged with Job.ControlType (but right
271 # now they use opposite values)
272 Types = enum.Enum('Client', 'Server', start_value=1)
mblighe8819cd2008-02-15 16:48:40 +0000273
jadmanski0afbb632008-06-06 21:10:57 +0000274 name = dbmodels.CharField(maxlength=255, unique=True)
showard909c7a62008-07-15 21:52:38 +0000275 author = dbmodels.CharField(maxlength=255)
276 test_class = dbmodels.CharField(maxlength=255)
277 test_category = dbmodels.CharField(maxlength=255)
278 dependencies = dbmodels.CharField(maxlength=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000279 description = dbmodels.TextField(blank=True)
showard909c7a62008-07-15 21:52:38 +0000280 experimental = dbmodels.BooleanField(default=True)
281 run_verify = dbmodels.BooleanField(default=True)
282 test_time = dbmodels.SmallIntegerField(choices=TestTime.choices(),
283 default=TestTime.MEDIUM)
jadmanski0afbb632008-06-06 21:10:57 +0000284 test_type = dbmodels.SmallIntegerField(choices=Types.choices())
showard909c7a62008-07-15 21:52:38 +0000285 sync_count = dbmodels.IntegerField(default=1)
jadmanski0afbb632008-06-06 21:10:57 +0000286 synch_type = dbmodels.SmallIntegerField(choices=SynchType.choices(),
287 default=SynchType.ASYNCHRONOUS)
showard909c7a62008-07-15 21:52:38 +0000288 path = dbmodels.CharField(maxlength=255, unique=True)
mblighe8819cd2008-02-15 16:48:40 +0000289
jadmanski0afbb632008-06-06 21:10:57 +0000290 name_field = 'name'
291 objects = model_logic.ExtendedManager()
mblighe8819cd2008-02-15 16:48:40 +0000292
293
jadmanski0afbb632008-06-06 21:10:57 +0000294 class Meta:
295 db_table = 'autotests'
mblighe8819cd2008-02-15 16:48:40 +0000296
jadmanski0afbb632008-06-06 21:10:57 +0000297 class Admin:
298 fields = (
299 (None, {'fields' :
showard909c7a62008-07-15 21:52:38 +0000300 ('name', 'author', 'test_category', 'test_class',
301 'test_time', 'synch_type', 'test_type', 'sync_count',
302 'path', 'dependencies', 'experimental', 'run_verify',
303 'description')}),
jadmanski0afbb632008-06-06 21:10:57 +0000304 )
showard909c7a62008-07-15 21:52:38 +0000305 list_display = ('name', 'test_type', 'description', 'synch_type')
jadmanski0afbb632008-06-06 21:10:57 +0000306 search_fields = ('name',)
mblighe8819cd2008-02-15 16:48:40 +0000307
jadmanski0afbb632008-06-06 21:10:57 +0000308 def __str__(self):
309 return self.name
mblighe8819cd2008-02-15 16:48:40 +0000310
311
showard2b9a88b2008-06-13 20:55:03 +0000312class Profiler(dbmodels.Model, model_logic.ModelExtensions):
313 """\
314 Required:
315 name: profiler name
316 test_type: Client or Server
317
318 Optional:
319 description: arbirary text description
320 """
321 name = dbmodels.CharField(maxlength=255, unique=True)
322 description = dbmodels.TextField(blank=True)
323
324 name_field = 'name'
325 objects = model_logic.ExtendedManager()
326
327
328 class Meta:
329 db_table = 'profilers'
330
331 class Admin:
332 list_display = ('name', 'description')
333 search_fields = ('name',)
334
335 def __str__(self):
336 return self.name
337
338
showard7c785282008-05-29 19:45:12 +0000339class AclGroup(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000340 """\
341 Required:
342 name: name of ACL group
mblighe8819cd2008-02-15 16:48:40 +0000343
jadmanski0afbb632008-06-06 21:10:57 +0000344 Optional:
345 description: arbitrary description of group
346 """
347 name = dbmodels.CharField(maxlength=255, unique=True)
348 description = dbmodels.CharField(maxlength=255, blank=True)
showard04f2cd82008-07-25 20:53:31 +0000349 users = dbmodels.ManyToManyField(User, blank=True,
jadmanski0afbb632008-06-06 21:10:57 +0000350 filter_interface=dbmodels.HORIZONTAL)
351 hosts = dbmodels.ManyToManyField(Host,
352 filter_interface=dbmodels.HORIZONTAL)
mblighe8819cd2008-02-15 16:48:40 +0000353
jadmanski0afbb632008-06-06 21:10:57 +0000354 name_field = 'name'
355 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000356
showard08f981b2008-06-24 21:59:03 +0000357 @staticmethod
showard3dd47c22008-07-10 00:41:36 +0000358 def check_for_acl_violation_hosts(hosts):
359 user = thread_local.get_user()
360 if user.is_superuser():
361 return None
362 accessible_host_ids = set(
363 host.id for host in Host.objects.filter(acl_group__users=user))
364 for host in hosts:
365 # Check if the user has access to this host,
366 # but only if it is not a metahost
367 if (isinstance(host, Host)
368 and int(host.id) not in accessible_host_ids):
369 raise AclAccessViolation("You do not have access to %s"
370 % str(host))
371
372 def check_for_acl_violation_acl_group(self):
373 user = thread_local.get_user()
374 if user.is_superuser():
375 return None
376 if not user in self.users.all():
377 raise AclAccessViolation("You do not have access to %s"
378 % self.name)
379
380 @staticmethod
showard08f981b2008-06-24 21:59:03 +0000381 def on_host_membership_change():
382 everyone = AclGroup.objects.get(name='Everyone')
383
showard3dd47c22008-07-10 00:41:36 +0000384 # find hosts that aren't in any ACL group and add them to Everyone
showard08f981b2008-06-24 21:59:03 +0000385 # TODO(showard): this is a bit of a hack, since the fact that this query
386 # works is kind of a coincidence of Django internals. This trick
387 # doesn't work in general (on all foreign key relationships). I'll
388 # replace it with a better technique when the need arises.
389 orphaned_hosts = Host.valid_objects.filter(acl_group__id__isnull=True)
390 everyone.hosts.add(*orphaned_hosts.distinct())
391
392 # find hosts in both Everyone and another ACL group, and remove them
393 # from Everyone
394 hosts_in_everyone = Host.valid_objects.filter_custom_join(
395 '_everyone', acl_group__name='Everyone')
396 acled_hosts = hosts_in_everyone.exclude(acl_group__name='Everyone')
397 everyone.hosts.remove(*acled_hosts.distinct())
398
399
400 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000401 if (self.name == 'Everyone'):
402 raise AclAccessViolation("You cannot delete 'Everyone'!")
403 self.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000404 super(AclGroup, self).delete()
405 self.on_host_membership_change()
406
407
showard04f2cd82008-07-25 20:53:31 +0000408 def add_current_user_if_empty(self):
409 if not self.users.count():
410 self.users.add(thread_local.get_user())
411
412
showard08f981b2008-06-24 21:59:03 +0000413 # if you have a model attribute called "Manipulator", Django will
414 # automatically insert it into the beginning of the superclass list
415 # for the model's manipulators
416 class Manipulator(object):
417 """
418 Custom manipulator to get notification when ACLs are changed through
419 the admin interface.
420 """
421 def save(self, new_data):
showard3dd47c22008-07-10 00:41:36 +0000422 user = thread_local.get_user()
showard31c570e2008-07-23 19:29:17 +0000423 if hasattr(self, 'original_object'):
424 if (not user.is_superuser()
425 and self.original_object.name == 'Everyone'):
426 raise AclAccessViolation("You cannot modify 'Everyone'!")
427 self.original_object.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000428 obj = super(AclGroup.Manipulator, self).save(new_data)
showard04f2cd82008-07-25 20:53:31 +0000429 if not hasattr(self, 'original_object'):
430 obj.users.add(thread_local.get_user())
431 obj.add_current_user_if_empty()
showard08f981b2008-06-24 21:59:03 +0000432 obj.on_host_membership_change()
433 return obj
showardeb3be4d2008-04-21 20:59:26 +0000434
jadmanski0afbb632008-06-06 21:10:57 +0000435 class Meta:
436 db_table = 'acl_groups'
mblighe8819cd2008-02-15 16:48:40 +0000437
jadmanski0afbb632008-06-06 21:10:57 +0000438 class Admin:
439 list_display = ('name', 'description')
440 search_fields = ('name',)
mblighe8819cd2008-02-15 16:48:40 +0000441
jadmanski0afbb632008-06-06 21:10:57 +0000442 def __str__(self):
443 return self.name
mblighe8819cd2008-02-15 16:48:40 +0000444
445# hack to make the column name in the many-to-many DB tables match the one
446# generated by ruby
447AclGroup._meta.object_name = 'acl_group'
448
449
showard7c785282008-05-29 19:45:12 +0000450class JobManager(model_logic.ExtendedManager):
jadmanski0afbb632008-06-06 21:10:57 +0000451 'Custom manager to provide efficient status counts querying.'
452 def get_status_counts(self, job_ids):
453 """\
454 Returns a dictionary mapping the given job IDs to their status
455 count dictionaries.
456 """
457 if not job_ids:
458 return {}
459 id_list = '(%s)' % ','.join(str(job_id) for job_id in job_ids)
460 cursor = connection.cursor()
461 cursor.execute("""
462 SELECT job_id, status, COUNT(*)
463 FROM host_queue_entries
464 WHERE job_id IN %s
465 GROUP BY job_id, status
466 """ % id_list)
467 all_job_counts = {}
468 for job_id in job_ids:
469 all_job_counts[job_id] = {}
470 for job_id, status, count in cursor.fetchall():
471 all_job_counts[job_id][status] = count
472 return all_job_counts
mblighe8819cd2008-02-15 16:48:40 +0000473
474
showard7c785282008-05-29 19:45:12 +0000475class Job(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000476 """\
477 owner: username of job owner
478 name: job name (does not have to be unique)
479 priority: Low, Medium, High, Urgent (or 0-3)
480 control_file: contents of control file
481 control_type: Client or Server
482 created_on: date of job creation
483 submitted_on: date of job submission
484 synch_type: Asynchronous or Synchronous (i.e. job must run on all hosts
485 simultaneously; used for server-side control files)
486 synch_count: ???
showard909c7a62008-07-15 21:52:38 +0000487 run_verify: Whether or not to run the verify phase
jadmanski0afbb632008-06-06 21:10:57 +0000488 synchronizing: for scheduler use
showard3bb499f2008-07-03 19:42:20 +0000489 timeout: hours until job times out
showard542e8402008-09-19 20:16:18 +0000490 email_list: list of people to email on completion delimited by any of:
491 white space, ',', ':', ';'
jadmanski0afbb632008-06-06 21:10:57 +0000492 """
493 Priority = enum.Enum('Low', 'Medium', 'High', 'Urgent')
494 ControlType = enum.Enum('Server', 'Client', start_value=1)
495 Status = enum.Enum('Created', 'Queued', 'Pending', 'Running',
496 'Completed', 'Abort', 'Aborting', 'Aborted',
showardd823b362008-07-24 16:35:46 +0000497 'Failed', 'Starting', string_values=True)
mblighe8819cd2008-02-15 16:48:40 +0000498
jadmanski0afbb632008-06-06 21:10:57 +0000499 owner = dbmodels.CharField(maxlength=255)
500 name = dbmodels.CharField(maxlength=255)
501 priority = dbmodels.SmallIntegerField(choices=Priority.choices(),
502 blank=True, # to allow 0
503 default=Priority.MEDIUM)
504 control_file = dbmodels.TextField()
505 control_type = dbmodels.SmallIntegerField(choices=ControlType.choices(),
506 blank=True) # to allow 0
507 created_on = dbmodels.DateTimeField(auto_now_add=True)
508 synch_type = dbmodels.SmallIntegerField(
509 blank=True, null=True, choices=Test.SynchType.choices())
510 synch_count = dbmodels.IntegerField(blank=True, null=True)
511 synchronizing = dbmodels.BooleanField(default=False)
showard909c7a62008-07-15 21:52:38 +0000512 run_verify = dbmodels.BooleanField(default=True)
showard3bb499f2008-07-03 19:42:20 +0000513 timeout = dbmodels.IntegerField()
showard542e8402008-09-19 20:16:18 +0000514 email_list = dbmodels.CharField(maxlength=250, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000515
516
jadmanski0afbb632008-06-06 21:10:57 +0000517 # custom manager
518 objects = JobManager()
mblighe8819cd2008-02-15 16:48:40 +0000519
520
jadmanski0afbb632008-06-06 21:10:57 +0000521 def is_server_job(self):
522 return self.control_type == self.ControlType.SERVER
mblighe8819cd2008-02-15 16:48:40 +0000523
524
jadmanski0afbb632008-06-06 21:10:57 +0000525 @classmethod
526 def create(cls, owner, name, priority, control_file, control_type,
showard542e8402008-09-19 20:16:18 +0000527 hosts, synch_type, timeout, run_verify, email_list):
jadmanski0afbb632008-06-06 21:10:57 +0000528 """\
529 Creates a job by taking some information (the listed args)
530 and filling in the rest of the necessary information.
531 """
showard3dd47c22008-07-10 00:41:36 +0000532 AclGroup.check_for_acl_violation_hosts(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000533 job = cls.add_object(
534 owner=owner, name=name, priority=priority,
535 control_file=control_file, control_type=control_type,
showard909c7a62008-07-15 21:52:38 +0000536 synch_type=synch_type, timeout=timeout,
showard542e8402008-09-19 20:16:18 +0000537 run_verify=run_verify, email_list=email_list)
mblighe8819cd2008-02-15 16:48:40 +0000538
jadmanski0afbb632008-06-06 21:10:57 +0000539 if job.synch_type == Test.SynchType.SYNCHRONOUS:
540 job.synch_count = len(hosts)
541 else:
542 if len(hosts) == 0:
543 errors = {'hosts':
544 'asynchronous jobs require at least'
545 + ' one host to run on'}
546 raise model_logic.ValidationError(errors)
547 job.save()
548 return job
mblighe8819cd2008-02-15 16:48:40 +0000549
550
jadmanski0afbb632008-06-06 21:10:57 +0000551 def queue(self, hosts):
552 'Enqueue a job on the given hosts.'
553 for host in hosts:
554 host.enqueue_job(self)
mblighe8819cd2008-02-15 16:48:40 +0000555
556
jadmanski0afbb632008-06-06 21:10:57 +0000557 def abort(self):
showard3dd47c22008-07-10 00:41:36 +0000558 user = thread_local.get_user()
559 if not user.is_superuser() and user.login != self.owner:
560 raise AclAccessViolation("You cannot abort other people's jobs!")
jadmanski0afbb632008-06-06 21:10:57 +0000561 for queue_entry in self.hostqueueentry_set.all():
showardb8471e32008-07-03 19:51:08 +0000562 queue_entry.abort()
mblighe8819cd2008-02-15 16:48:40 +0000563
564
jadmanski0afbb632008-06-06 21:10:57 +0000565 def user(self):
566 try:
567 return User.objects.get(login=self.owner)
568 except self.DoesNotExist:
569 return None
mblighe8819cd2008-02-15 16:48:40 +0000570
571
jadmanski0afbb632008-06-06 21:10:57 +0000572 class Meta:
573 db_table = 'jobs'
mblighe8819cd2008-02-15 16:48:40 +0000574
jadmanski0afbb632008-06-06 21:10:57 +0000575 if settings.FULL_ADMIN:
576 class Admin:
577 list_display = ('id', 'owner', 'name', 'control_type')
mblighe8819cd2008-02-15 16:48:40 +0000578
jadmanski0afbb632008-06-06 21:10:57 +0000579 def __str__(self):
580 return '%s (%s-%s)' % (self.name, self.id, self.owner)
mblighe8819cd2008-02-15 16:48:40 +0000581
582
showard7c785282008-05-29 19:45:12 +0000583class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000584 job = dbmodels.ForeignKey(Job)
585 host = dbmodels.ForeignKey(Host)
mblighe8819cd2008-02-15 16:48:40 +0000586
jadmanski0afbb632008-06-06 21:10:57 +0000587 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000588
jadmanski0afbb632008-06-06 21:10:57 +0000589 class Meta:
590 db_table = 'ineligible_host_queues'
mblighe8819cd2008-02-15 16:48:40 +0000591
jadmanski0afbb632008-06-06 21:10:57 +0000592 if settings.FULL_ADMIN:
593 class Admin:
594 list_display = ('id', 'job', 'host')
mblighe8819cd2008-02-15 16:48:40 +0000595
596
showard7c785282008-05-29 19:45:12 +0000597class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000598 job = dbmodels.ForeignKey(Job)
599 host = dbmodels.ForeignKey(Host, blank=True, null=True)
600 priority = dbmodels.SmallIntegerField()
601 status = dbmodels.CharField(maxlength=255)
602 meta_host = dbmodels.ForeignKey(Label, blank=True, null=True,
603 db_column='meta_host')
604 active = dbmodels.BooleanField(default=False)
605 complete = dbmodels.BooleanField(default=False)
showardb8471e32008-07-03 19:51:08 +0000606 deleted = dbmodels.BooleanField(default=False)
mblighe8819cd2008-02-15 16:48:40 +0000607
jadmanski0afbb632008-06-06 21:10:57 +0000608 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000609
mblighe8819cd2008-02-15 16:48:40 +0000610
jadmanski0afbb632008-06-06 21:10:57 +0000611 def is_meta_host_entry(self):
612 'True if this is a entry has a meta_host instead of a host.'
613 return self.host is None and self.meta_host is not None
mblighe8819cd2008-02-15 16:48:40 +0000614
showardb8471e32008-07-03 19:51:08 +0000615 def abort(self):
616 if self.active:
617 self.status = Job.Status.ABORT
618 elif not self.complete:
619 self.status = Job.Status.ABORTED
620 self.active = False
621 self.complete = True
622 self.save()
mblighe8819cd2008-02-15 16:48:40 +0000623
jadmanski0afbb632008-06-06 21:10:57 +0000624 class Meta:
625 db_table = 'host_queue_entries'
mblighe8819cd2008-02-15 16:48:40 +0000626
jadmanski0afbb632008-06-06 21:10:57 +0000627 if settings.FULL_ADMIN:
628 class Admin:
629 list_display = ('id', 'job', 'host', 'status',
630 'meta_host')