blob: e9bc2c8f438a4d3fc05e16e6e31c76856826630c [file] [log] [blame]
showard7c785282008-05-29 19:45:12 +00001from django.db import models as dbmodels, connection
showarddf062562008-07-03 19:56:37 +00002from frontend.afe import model_logic
showard3dd47c22008-07-10 00:41:36 +00003from frontend import settings, thread_local
showarddf062562008-07-03 19:56:37 +00004from autotest_lib.client.common_lib import enum, host_protections
mblighe8819cd2008-02-15 16:48:40 +00005
6
mblighe8819cd2008-02-15 16:48:40 +00007class AclAccessViolation(Exception):
jadmanski0afbb632008-06-06 21:10:57 +00008 """\
9 Raised when an operation is attempted with proper permissions as
10 dictated by ACLs.
11 """
mblighe8819cd2008-02-15 16:48:40 +000012
13
showard7c785282008-05-29 19:45:12 +000014class Label(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +000015 """\
16 Required:
17 name: label name
mblighe8819cd2008-02-15 16:48:40 +000018
jadmanski0afbb632008-06-06 21:10:57 +000019 Optional:
20 kernel_config: url/path to kernel config to use for jobs run on this
21 label
22 platform: if True, this is a platform label (defaults to False)
23 """
24 name = dbmodels.CharField(maxlength=255, unique=True)
25 kernel_config = dbmodels.CharField(maxlength=255, blank=True)
26 platform = dbmodels.BooleanField(default=False)
27 invalid = dbmodels.BooleanField(default=False,
28 editable=settings.FULL_ADMIN)
mblighe8819cd2008-02-15 16:48:40 +000029
jadmanski0afbb632008-06-06 21:10:57 +000030 name_field = 'name'
31 objects = model_logic.ExtendedManager()
32 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +000033
jadmanski0afbb632008-06-06 21:10:57 +000034 def clean_object(self):
35 self.host_set.clear()
mblighe8819cd2008-02-15 16:48:40 +000036
37
jadmanski0afbb632008-06-06 21:10:57 +000038 def enqueue_job(self, job):
39 'Enqueue a job on any host of this label.'
40 queue_entry = HostQueueEntry(meta_host=self, job=job,
41 status=Job.Status.QUEUED,
42 priority=job.priority)
43 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +000044
45
jadmanski0afbb632008-06-06 21:10:57 +000046 class Meta:
47 db_table = 'labels'
mblighe8819cd2008-02-15 16:48:40 +000048
jadmanski0afbb632008-06-06 21:10:57 +000049 class Admin:
50 list_display = ('name', 'kernel_config')
51 # see Host.Admin
52 manager = model_logic.ValidObjectsManager()
mblighe8819cd2008-02-15 16:48:40 +000053
jadmanski0afbb632008-06-06 21:10:57 +000054 def __str__(self):
55 return self.name
mblighe8819cd2008-02-15 16:48:40 +000056
57
showard7c785282008-05-29 19:45:12 +000058class Host(model_logic.ModelWithInvalid, dbmodels.Model):
jadmanski0afbb632008-06-06 21:10:57 +000059 """\
60 Required:
61 hostname
mblighe8819cd2008-02-15 16:48:40 +000062
jadmanski0afbb632008-06-06 21:10:57 +000063 optional:
64 locked: host is locked and will not be queued
mblighe8819cd2008-02-15 16:48:40 +000065
jadmanski0afbb632008-06-06 21:10:57 +000066 Internal:
67 synch_id: currently unused
68 status: string describing status of host
69 """
70 Status = enum.Enum('Verifying', 'Running', 'Ready', 'Repairing',
71 'Repair Failed', 'Dead', 'Rebooting',
72 string_values=True)
mblighe8819cd2008-02-15 16:48:40 +000073
jadmanski0afbb632008-06-06 21:10:57 +000074 hostname = dbmodels.CharField(maxlength=255, unique=True)
75 labels = dbmodels.ManyToManyField(Label, blank=True,
76 filter_interface=dbmodels.HORIZONTAL)
77 locked = dbmodels.BooleanField(default=False)
78 synch_id = dbmodels.IntegerField(blank=True, null=True,
79 editable=settings.FULL_ADMIN)
80 status = dbmodels.CharField(maxlength=255, default=Status.READY,
81 choices=Status.choices(),
82 editable=settings.FULL_ADMIN)
83 invalid = dbmodels.BooleanField(default=False,
84 editable=settings.FULL_ADMIN)
showarddf062562008-07-03 19:56:37 +000085 protection = dbmodels.SmallIntegerField(null=False,
86 choices=host_protections.choices,
87 default=host_protections.default)
mblighe8819cd2008-02-15 16:48:40 +000088
jadmanski0afbb632008-06-06 21:10:57 +000089 name_field = 'hostname'
90 objects = model_logic.ExtendedManager()
91 valid_objects = model_logic.ValidObjectsManager()
mbligh5244cbb2008-04-24 20:39:52 +000092
showardb8471e32008-07-03 19:51:08 +000093 @staticmethod
94 def create_one_time_host(hostname):
95 query = Host.objects.filter(hostname=hostname)
96 if query.count() == 0:
97 host = Host(hostname=hostname, invalid=True)
98 else:
99 host = query[0]
100 if not host.invalid:
101 raise model_logic.ValidationError({
102 'hostname' : '%s already exists!' % hostname
103 })
104 host.clean_object()
105 host.status = Host.Status.READY
106 host.save()
107 return host
mbligh5244cbb2008-04-24 20:39:52 +0000108
jadmanski0afbb632008-06-06 21:10:57 +0000109 def clean_object(self):
110 self.aclgroup_set.clear()
111 self.labels.clear()
mblighe8819cd2008-02-15 16:48:40 +0000112
113
jadmanski0afbb632008-06-06 21:10:57 +0000114 def save(self):
115 # extra spaces in the hostname can be a sneaky source of errors
116 self.hostname = self.hostname.strip()
117 # is this a new object being saved for the first time?
118 first_time = (self.id is None)
showard3dd47c22008-07-10 00:41:36 +0000119 if not first_time:
120 AclGroup.check_for_acl_violation_hosts([self])
jadmanski0afbb632008-06-06 21:10:57 +0000121 super(Host, self).save()
122 if first_time:
123 everyone = AclGroup.objects.get(name='Everyone')
124 everyone.hosts.add(self)
mblighe8819cd2008-02-15 16:48:40 +0000125
showardb8471e32008-07-03 19:51:08 +0000126 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000127 AclGroup.check_for_acl_violation_hosts([self])
showardb8471e32008-07-03 19:51:08 +0000128 for queue_entry in self.hostqueueentry_set.all():
129 queue_entry.deleted = True
130 queue_entry.abort()
131 super(Host, self).delete()
132
mblighe8819cd2008-02-15 16:48:40 +0000133
jadmanski0afbb632008-06-06 21:10:57 +0000134 def enqueue_job(self, job):
135 ' Enqueue a job on this host.'
136 queue_entry = HostQueueEntry(host=self, job=job,
137 status=Job.Status.QUEUED,
138 priority=job.priority)
139 # allow recovery of dead hosts from the frontend
140 if not self.active_queue_entry() and self.is_dead():
141 self.status = Host.Status.READY
142 self.save()
143 queue_entry.save()
mblighe8819cd2008-02-15 16:48:40 +0000144
showard08f981b2008-06-24 21:59:03 +0000145 block = IneligibleHostQueue(job=job, host=self)
146 block.save()
147
mblighe8819cd2008-02-15 16:48:40 +0000148
jadmanski0afbb632008-06-06 21:10:57 +0000149 def platform(self):
150 # TODO(showard): slighly hacky?
151 platforms = self.labels.filter(platform=True)
152 if len(platforms) == 0:
153 return None
154 return platforms[0]
155 platform.short_description = 'Platform'
mblighe8819cd2008-02-15 16:48:40 +0000156
157
jadmanski0afbb632008-06-06 21:10:57 +0000158 def is_dead(self):
159 return self.status == Host.Status.REPAIR_FAILED
mbligh3cab4a72008-03-05 23:19:09 +0000160
161
jadmanski0afbb632008-06-06 21:10:57 +0000162 def active_queue_entry(self):
163 active = list(self.hostqueueentry_set.filter(active=True))
164 if not active:
165 return None
166 assert len(active) == 1, ('More than one active entry for '
167 'host ' + self.hostname)
168 return active[0]
mblighe8819cd2008-02-15 16:48:40 +0000169
170
jadmanski0afbb632008-06-06 21:10:57 +0000171 class Meta:
172 db_table = 'hosts'
mblighe8819cd2008-02-15 16:48:40 +0000173
jadmanski0afbb632008-06-06 21:10:57 +0000174 class Admin:
175 # TODO(showard) - showing platform requires a SQL query for
176 # each row (since labels are many-to-many) - should we remove
177 # it?
178 list_display = ('hostname', 'platform', 'locked', 'status')
showarddf062562008-07-03 19:56:37 +0000179 list_filter = ('labels', 'locked', 'protection')
jadmanski0afbb632008-06-06 21:10:57 +0000180 search_fields = ('hostname', 'status')
181 # undocumented Django feature - if you set manager here, the
182 # admin code will use it, otherwise it'll use a default Manager
183 manager = model_logic.ValidObjectsManager()
mblighe8819cd2008-02-15 16:48:40 +0000184
jadmanski0afbb632008-06-06 21:10:57 +0000185 def __str__(self):
186 return self.hostname
mblighe8819cd2008-02-15 16:48:40 +0000187
188
showard7c785282008-05-29 19:45:12 +0000189class Test(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000190 """\
191 Required:
showard909c7a62008-07-15 21:52:38 +0000192 author: author name
193 description: description of the test
jadmanski0afbb632008-06-06 21:10:57 +0000194 name: test name
showard909c7a62008-07-15 21:52:38 +0000195 time: short, medium, long
196 test_class: This describes the class for your the test belongs in.
197 test_category: This describes the category for your tests
jadmanski0afbb632008-06-06 21:10:57 +0000198 test_type: Client or Server
199 path: path to pass to run_test()
200 synch_type: whether the test should run synchronously or asynchronously
showard909c7a62008-07-15 21:52:38 +0000201 sync_count: is a number >=1 (1 being the default). If it's 1, then it's an
202 async job. If it's >1 it's sync job for that number of machines
203 i.e. if sync_count = 2 it is a sync job that requires two
204 machines.
jadmanski0afbb632008-06-06 21:10:57 +0000205 Optional:
showard909c7a62008-07-15 21:52:38 +0000206 dependencies: What the test requires to run. Comma deliminated list
207 experimental: If this is set to True production servers will ignore the test
208 run_verify: Whether or not the scheduler should run the verify stage
jadmanski0afbb632008-06-06 21:10:57 +0000209 """
showard909c7a62008-07-15 21:52:38 +0000210 TestTime = enum.Enum('SHORT', 'MEDIUM', 'LONG', start_value=1)
jadmanski0afbb632008-06-06 21:10:57 +0000211 SynchType = enum.Enum('Asynchronous', 'Synchronous', start_value=1)
212 # TODO(showard) - this should be merged with Job.ControlType (but right
213 # now they use opposite values)
214 Types = enum.Enum('Client', 'Server', start_value=1)
mblighe8819cd2008-02-15 16:48:40 +0000215
jadmanski0afbb632008-06-06 21:10:57 +0000216 name = dbmodels.CharField(maxlength=255, unique=True)
showard909c7a62008-07-15 21:52:38 +0000217 author = dbmodels.CharField(maxlength=255)
218 test_class = dbmodels.CharField(maxlength=255)
219 test_category = dbmodels.CharField(maxlength=255)
220 dependencies = dbmodels.CharField(maxlength=255, blank=True)
jadmanski0afbb632008-06-06 21:10:57 +0000221 description = dbmodels.TextField(blank=True)
showard909c7a62008-07-15 21:52:38 +0000222 experimental = dbmodels.BooleanField(default=True)
223 run_verify = dbmodels.BooleanField(default=True)
224 test_time = dbmodels.SmallIntegerField(choices=TestTime.choices(),
225 default=TestTime.MEDIUM)
jadmanski0afbb632008-06-06 21:10:57 +0000226 test_type = dbmodels.SmallIntegerField(choices=Types.choices())
showard909c7a62008-07-15 21:52:38 +0000227 sync_count = dbmodels.IntegerField(default=1)
jadmanski0afbb632008-06-06 21:10:57 +0000228 synch_type = dbmodels.SmallIntegerField(choices=SynchType.choices(),
229 default=SynchType.ASYNCHRONOUS)
showard909c7a62008-07-15 21:52:38 +0000230 path = dbmodels.CharField(maxlength=255, unique=True)
mblighe8819cd2008-02-15 16:48:40 +0000231
jadmanski0afbb632008-06-06 21:10:57 +0000232 name_field = 'name'
233 objects = model_logic.ExtendedManager()
mblighe8819cd2008-02-15 16:48:40 +0000234
235
jadmanski0afbb632008-06-06 21:10:57 +0000236 class Meta:
237 db_table = 'autotests'
mblighe8819cd2008-02-15 16:48:40 +0000238
jadmanski0afbb632008-06-06 21:10:57 +0000239 class Admin:
240 fields = (
241 (None, {'fields' :
showard909c7a62008-07-15 21:52:38 +0000242 ('name', 'author', 'test_category', 'test_class',
243 'test_time', 'synch_type', 'test_type', 'sync_count',
244 'path', 'dependencies', 'experimental', 'run_verify',
245 'description')}),
jadmanski0afbb632008-06-06 21:10:57 +0000246 )
showard909c7a62008-07-15 21:52:38 +0000247 list_display = ('name', 'test_type', 'description', 'synch_type')
jadmanski0afbb632008-06-06 21:10:57 +0000248 search_fields = ('name',)
mblighe8819cd2008-02-15 16:48:40 +0000249
jadmanski0afbb632008-06-06 21:10:57 +0000250 def __str__(self):
251 return self.name
mblighe8819cd2008-02-15 16:48:40 +0000252
253
showard2b9a88b2008-06-13 20:55:03 +0000254class Profiler(dbmodels.Model, model_logic.ModelExtensions):
255 """\
256 Required:
257 name: profiler name
258 test_type: Client or Server
259
260 Optional:
261 description: arbirary text description
262 """
263 name = dbmodels.CharField(maxlength=255, unique=True)
264 description = dbmodels.TextField(blank=True)
265
266 name_field = 'name'
267 objects = model_logic.ExtendedManager()
268
269
270 class Meta:
271 db_table = 'profilers'
272
273 class Admin:
274 list_display = ('name', 'description')
275 search_fields = ('name',)
276
277 def __str__(self):
278 return self.name
279
280
showard7c785282008-05-29 19:45:12 +0000281class User(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000282 """\
283 Required:
284 login :user login name
mblighe8819cd2008-02-15 16:48:40 +0000285
jadmanski0afbb632008-06-06 21:10:57 +0000286 Optional:
287 access_level: 0=User (default), 1=Admin, 100=Root
288 """
289 ACCESS_ROOT = 100
290 ACCESS_ADMIN = 1
291 ACCESS_USER = 0
mblighe8819cd2008-02-15 16:48:40 +0000292
jadmanski0afbb632008-06-06 21:10:57 +0000293 login = dbmodels.CharField(maxlength=255, unique=True)
294 access_level = dbmodels.IntegerField(default=ACCESS_USER, blank=True)
mblighe8819cd2008-02-15 16:48:40 +0000295
jadmanski0afbb632008-06-06 21:10:57 +0000296 name_field = 'login'
297 objects = model_logic.ExtendedManager()
mblighe8819cd2008-02-15 16:48:40 +0000298
299
jadmanski0afbb632008-06-06 21:10:57 +0000300 def save(self):
301 # is this a new object being saved for the first time?
302 first_time = (self.id is None)
showard3dd47c22008-07-10 00:41:36 +0000303 user = thread_local.get_user()
304 if user and not user.is_superuser():
305 raise AclAccessViolation("You cannot modify users!")
jadmanski0afbb632008-06-06 21:10:57 +0000306 super(User, self).save()
307 if first_time:
308 everyone = AclGroup.objects.get(name='Everyone')
309 everyone.users.add(self)
mblighe8819cd2008-02-15 16:48:40 +0000310
311
showard3dd47c22008-07-10 00:41:36 +0000312 def is_superuser(self):
313 return self.access_level >= self.ACCESS_ROOT
mblighe8819cd2008-02-15 16:48:40 +0000314
315
jadmanski0afbb632008-06-06 21:10:57 +0000316 class Meta:
317 db_table = 'users'
mblighe8819cd2008-02-15 16:48:40 +0000318
jadmanski0afbb632008-06-06 21:10:57 +0000319 class Admin:
320 list_display = ('login', 'access_level')
321 search_fields = ('login',)
mblighe8819cd2008-02-15 16:48:40 +0000322
jadmanski0afbb632008-06-06 21:10:57 +0000323 def __str__(self):
324 return self.login
mblighe8819cd2008-02-15 16:48:40 +0000325
326
showard7c785282008-05-29 19:45:12 +0000327class AclGroup(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000328 """\
329 Required:
330 name: name of ACL group
mblighe8819cd2008-02-15 16:48:40 +0000331
jadmanski0afbb632008-06-06 21:10:57 +0000332 Optional:
333 description: arbitrary description of group
334 """
335 name = dbmodels.CharField(maxlength=255, unique=True)
336 description = dbmodels.CharField(maxlength=255, blank=True)
337 users = dbmodels.ManyToManyField(User,
338 filter_interface=dbmodels.HORIZONTAL)
339 hosts = dbmodels.ManyToManyField(Host,
340 filter_interface=dbmodels.HORIZONTAL)
mblighe8819cd2008-02-15 16:48:40 +0000341
jadmanski0afbb632008-06-06 21:10:57 +0000342 name_field = 'name'
343 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000344
showard08f981b2008-06-24 21:59:03 +0000345 @staticmethod
showard3dd47c22008-07-10 00:41:36 +0000346 def check_for_acl_violation_hosts(hosts):
347 user = thread_local.get_user()
348 if user.is_superuser():
349 return None
350 accessible_host_ids = set(
351 host.id for host in Host.objects.filter(acl_group__users=user))
352 for host in hosts:
353 # Check if the user has access to this host,
354 # but only if it is not a metahost
355 if (isinstance(host, Host)
356 and int(host.id) not in accessible_host_ids):
357 raise AclAccessViolation("You do not have access to %s"
358 % str(host))
359
360 def check_for_acl_violation_acl_group(self):
361 user = thread_local.get_user()
362 if user.is_superuser():
363 return None
364 if not user in self.users.all():
365 raise AclAccessViolation("You do not have access to %s"
366 % self.name)
367
368 @staticmethod
showard08f981b2008-06-24 21:59:03 +0000369 def on_host_membership_change():
370 everyone = AclGroup.objects.get(name='Everyone')
371
showard3dd47c22008-07-10 00:41:36 +0000372 # find hosts that aren't in any ACL group and add them to Everyone
showard08f981b2008-06-24 21:59:03 +0000373 # TODO(showard): this is a bit of a hack, since the fact that this query
374 # works is kind of a coincidence of Django internals. This trick
375 # doesn't work in general (on all foreign key relationships). I'll
376 # replace it with a better technique when the need arises.
377 orphaned_hosts = Host.valid_objects.filter(acl_group__id__isnull=True)
378 everyone.hosts.add(*orphaned_hosts.distinct())
379
380 # find hosts in both Everyone and another ACL group, and remove them
381 # from Everyone
382 hosts_in_everyone = Host.valid_objects.filter_custom_join(
383 '_everyone', acl_group__name='Everyone')
384 acled_hosts = hosts_in_everyone.exclude(acl_group__name='Everyone')
385 everyone.hosts.remove(*acled_hosts.distinct())
386
387
388 def delete(self):
showard3dd47c22008-07-10 00:41:36 +0000389 if (self.name == 'Everyone'):
390 raise AclAccessViolation("You cannot delete 'Everyone'!")
391 self.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000392 super(AclGroup, self).delete()
393 self.on_host_membership_change()
394
395
396 # if you have a model attribute called "Manipulator", Django will
397 # automatically insert it into the beginning of the superclass list
398 # for the model's manipulators
399 class Manipulator(object):
400 """
401 Custom manipulator to get notification when ACLs are changed through
402 the admin interface.
403 """
404 def save(self, new_data):
showard3dd47c22008-07-10 00:41:36 +0000405 user = thread_local.get_user()
406 if (not user.is_superuser()
407 and self.original_object.name == 'Everyone'):
408 raise AclAccessViolation("You cannot modify 'Everyone'!")
409 self.original_object.check_for_acl_violation_acl_group()
showard08f981b2008-06-24 21:59:03 +0000410 obj = super(AclGroup.Manipulator, self).save(new_data)
411 obj.on_host_membership_change()
412 return obj
showardeb3be4d2008-04-21 20:59:26 +0000413
jadmanski0afbb632008-06-06 21:10:57 +0000414 class Meta:
415 db_table = 'acl_groups'
mblighe8819cd2008-02-15 16:48:40 +0000416
jadmanski0afbb632008-06-06 21:10:57 +0000417 class Admin:
418 list_display = ('name', 'description')
419 search_fields = ('name',)
mblighe8819cd2008-02-15 16:48:40 +0000420
jadmanski0afbb632008-06-06 21:10:57 +0000421 def __str__(self):
422 return self.name
mblighe8819cd2008-02-15 16:48:40 +0000423
424# hack to make the column name in the many-to-many DB tables match the one
425# generated by ruby
426AclGroup._meta.object_name = 'acl_group'
427
428
showard7c785282008-05-29 19:45:12 +0000429class JobManager(model_logic.ExtendedManager):
jadmanski0afbb632008-06-06 21:10:57 +0000430 'Custom manager to provide efficient status counts querying.'
431 def get_status_counts(self, job_ids):
432 """\
433 Returns a dictionary mapping the given job IDs to their status
434 count dictionaries.
435 """
436 if not job_ids:
437 return {}
438 id_list = '(%s)' % ','.join(str(job_id) for job_id in job_ids)
439 cursor = connection.cursor()
440 cursor.execute("""
441 SELECT job_id, status, COUNT(*)
442 FROM host_queue_entries
443 WHERE job_id IN %s
444 GROUP BY job_id, status
445 """ % id_list)
446 all_job_counts = {}
447 for job_id in job_ids:
448 all_job_counts[job_id] = {}
449 for job_id, status, count in cursor.fetchall():
450 all_job_counts[job_id][status] = count
451 return all_job_counts
mblighe8819cd2008-02-15 16:48:40 +0000452
453
showard7c785282008-05-29 19:45:12 +0000454class Job(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000455 """\
456 owner: username of job owner
457 name: job name (does not have to be unique)
458 priority: Low, Medium, High, Urgent (or 0-3)
459 control_file: contents of control file
460 control_type: Client or Server
461 created_on: date of job creation
462 submitted_on: date of job submission
463 synch_type: Asynchronous or Synchronous (i.e. job must run on all hosts
464 simultaneously; used for server-side control files)
465 synch_count: ???
showard909c7a62008-07-15 21:52:38 +0000466 run_verify: Whether or not to run the verify phase
jadmanski0afbb632008-06-06 21:10:57 +0000467 synchronizing: for scheduler use
showard3bb499f2008-07-03 19:42:20 +0000468 timeout: hours until job times out
jadmanski0afbb632008-06-06 21:10:57 +0000469 """
470 Priority = enum.Enum('Low', 'Medium', 'High', 'Urgent')
471 ControlType = enum.Enum('Server', 'Client', start_value=1)
472 Status = enum.Enum('Created', 'Queued', 'Pending', 'Running',
473 'Completed', 'Abort', 'Aborting', 'Aborted',
474 'Failed', string_values=True)
mblighe8819cd2008-02-15 16:48:40 +0000475
jadmanski0afbb632008-06-06 21:10:57 +0000476 owner = dbmodels.CharField(maxlength=255)
477 name = dbmodels.CharField(maxlength=255)
478 priority = dbmodels.SmallIntegerField(choices=Priority.choices(),
479 blank=True, # to allow 0
480 default=Priority.MEDIUM)
481 control_file = dbmodels.TextField()
482 control_type = dbmodels.SmallIntegerField(choices=ControlType.choices(),
483 blank=True) # to allow 0
484 created_on = dbmodels.DateTimeField(auto_now_add=True)
485 synch_type = dbmodels.SmallIntegerField(
486 blank=True, null=True, choices=Test.SynchType.choices())
487 synch_count = dbmodels.IntegerField(blank=True, null=True)
488 synchronizing = dbmodels.BooleanField(default=False)
showard909c7a62008-07-15 21:52:38 +0000489 run_verify = dbmodels.BooleanField(default=True)
showard3bb499f2008-07-03 19:42:20 +0000490 timeout = dbmodels.IntegerField()
mblighe8819cd2008-02-15 16:48:40 +0000491
492
jadmanski0afbb632008-06-06 21:10:57 +0000493 # custom manager
494 objects = JobManager()
mblighe8819cd2008-02-15 16:48:40 +0000495
496
jadmanski0afbb632008-06-06 21:10:57 +0000497 def is_server_job(self):
498 return self.control_type == self.ControlType.SERVER
mblighe8819cd2008-02-15 16:48:40 +0000499
500
jadmanski0afbb632008-06-06 21:10:57 +0000501 @classmethod
502 def create(cls, owner, name, priority, control_file, control_type,
showard909c7a62008-07-15 21:52:38 +0000503 hosts, synch_type, timeout, run_verify):
jadmanski0afbb632008-06-06 21:10:57 +0000504 """\
505 Creates a job by taking some information (the listed args)
506 and filling in the rest of the necessary information.
507 """
showard3dd47c22008-07-10 00:41:36 +0000508 AclGroup.check_for_acl_violation_hosts(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000509 job = cls.add_object(
510 owner=owner, name=name, priority=priority,
511 control_file=control_file, control_type=control_type,
showard909c7a62008-07-15 21:52:38 +0000512 synch_type=synch_type, timeout=timeout,
513 run_verify=run_verify)
mblighe8819cd2008-02-15 16:48:40 +0000514
jadmanski0afbb632008-06-06 21:10:57 +0000515 if job.synch_type == Test.SynchType.SYNCHRONOUS:
516 job.synch_count = len(hosts)
517 else:
518 if len(hosts) == 0:
519 errors = {'hosts':
520 'asynchronous jobs require at least'
521 + ' one host to run on'}
522 raise model_logic.ValidationError(errors)
523 job.save()
524 return job
mblighe8819cd2008-02-15 16:48:40 +0000525
526
jadmanski0afbb632008-06-06 21:10:57 +0000527 def queue(self, hosts):
528 'Enqueue a job on the given hosts.'
529 for host in hosts:
530 host.enqueue_job(self)
mblighe8819cd2008-02-15 16:48:40 +0000531
532
jadmanski0afbb632008-06-06 21:10:57 +0000533 def requeue(self, new_owner):
534 'Creates a new job identical to this one'
535 hosts = [queue_entry.meta_host or queue_entry.host
showardb8471e32008-07-03 19:51:08 +0000536 for queue_entry
537 in self.hostqueueentry_set.filter(deleted=False)]
jadmanski0afbb632008-06-06 21:10:57 +0000538 new_job = Job.create(
539 owner=new_owner, name=self.name, priority=self.priority,
540 control_file=self.control_file,
541 control_type=self.control_type, hosts=hosts,
showard909c7a62008-07-15 21:52:38 +0000542 synch_type=self.synch_type, timeout=self.timeout,
543 run_verify=self.run_verify)
jadmanski0afbb632008-06-06 21:10:57 +0000544 new_job.queue(hosts)
545 return new_job
mbligh3cab4a72008-03-05 23:19:09 +0000546
547
jadmanski0afbb632008-06-06 21:10:57 +0000548 def abort(self):
showard3dd47c22008-07-10 00:41:36 +0000549 user = thread_local.get_user()
550 if not user.is_superuser() and user.login != self.owner:
551 raise AclAccessViolation("You cannot abort other people's jobs!")
jadmanski0afbb632008-06-06 21:10:57 +0000552 for queue_entry in self.hostqueueentry_set.all():
showardb8471e32008-07-03 19:51:08 +0000553 queue_entry.abort()
mblighe8819cd2008-02-15 16:48:40 +0000554
555
jadmanski0afbb632008-06-06 21:10:57 +0000556 def user(self):
557 try:
558 return User.objects.get(login=self.owner)
559 except self.DoesNotExist:
560 return None
mblighe8819cd2008-02-15 16:48:40 +0000561
562
jadmanski0afbb632008-06-06 21:10:57 +0000563 class Meta:
564 db_table = 'jobs'
mblighe8819cd2008-02-15 16:48:40 +0000565
jadmanski0afbb632008-06-06 21:10:57 +0000566 if settings.FULL_ADMIN:
567 class Admin:
568 list_display = ('id', 'owner', 'name', 'control_type')
mblighe8819cd2008-02-15 16:48:40 +0000569
jadmanski0afbb632008-06-06 21:10:57 +0000570 def __str__(self):
571 return '%s (%s-%s)' % (self.name, self.id, self.owner)
mblighe8819cd2008-02-15 16:48:40 +0000572
573
showard7c785282008-05-29 19:45:12 +0000574class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000575 job = dbmodels.ForeignKey(Job)
576 host = dbmodels.ForeignKey(Host)
mblighe8819cd2008-02-15 16:48:40 +0000577
jadmanski0afbb632008-06-06 21:10:57 +0000578 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000579
jadmanski0afbb632008-06-06 21:10:57 +0000580 class Meta:
581 db_table = 'ineligible_host_queues'
mblighe8819cd2008-02-15 16:48:40 +0000582
jadmanski0afbb632008-06-06 21:10:57 +0000583 if settings.FULL_ADMIN:
584 class Admin:
585 list_display = ('id', 'job', 'host')
mblighe8819cd2008-02-15 16:48:40 +0000586
587
showard7c785282008-05-29 19:45:12 +0000588class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions):
jadmanski0afbb632008-06-06 21:10:57 +0000589 job = dbmodels.ForeignKey(Job)
590 host = dbmodels.ForeignKey(Host, blank=True, null=True)
591 priority = dbmodels.SmallIntegerField()
592 status = dbmodels.CharField(maxlength=255)
593 meta_host = dbmodels.ForeignKey(Label, blank=True, null=True,
594 db_column='meta_host')
595 active = dbmodels.BooleanField(default=False)
596 complete = dbmodels.BooleanField(default=False)
showardb8471e32008-07-03 19:51:08 +0000597 deleted = dbmodels.BooleanField(default=False)
mblighe8819cd2008-02-15 16:48:40 +0000598
jadmanski0afbb632008-06-06 21:10:57 +0000599 objects = model_logic.ExtendedManager()
showardeb3be4d2008-04-21 20:59:26 +0000600
mblighe8819cd2008-02-15 16:48:40 +0000601
jadmanski0afbb632008-06-06 21:10:57 +0000602 def is_meta_host_entry(self):
603 'True if this is a entry has a meta_host instead of a host.'
604 return self.host is None and self.meta_host is not None
mblighe8819cd2008-02-15 16:48:40 +0000605
showardb8471e32008-07-03 19:51:08 +0000606 def abort(self):
607 if self.active:
608 self.status = Job.Status.ABORT
609 elif not self.complete:
610 self.status = Job.Status.ABORTED
611 self.active = False
612 self.complete = True
613 self.save()
mblighe8819cd2008-02-15 16:48:40 +0000614
jadmanski0afbb632008-06-06 21:10:57 +0000615 class Meta:
616 db_table = 'host_queue_entries'
mblighe8819cd2008-02-15 16:48:40 +0000617
jadmanski0afbb632008-06-06 21:10:57 +0000618 if settings.FULL_ADMIN:
619 class Admin:
620 list_display = ('id', 'job', 'host', 'status',
621 'meta_host')