class User(dbmodels.Model, model_logic.ModelExtensions): """\ Required: login :user login name Optional: access_level: 0=User (default), 1=Admin, 100=Root """ ACCESS_ROOT = 100 ACCESS_ADMIN = 1 ACCESS_USER = 0 AUTOTEST_SYSTEM = 'autotest_system' login = dbmodels.CharField(max_length=255, unique=True) access_level = dbmodels.IntegerField(default=ACCESS_USER, blank=True) # user preferences reboot_before = dbmodels.SmallIntegerField( choices=model_attributes.RebootBefore.choices(), blank=True, default=DEFAULT_REBOOT_BEFORE) reboot_after = dbmodels.SmallIntegerField( choices=model_attributes.RebootAfter.choices(), blank=True, default=DEFAULT_REBOOT_AFTER) drone_set = dbmodels.ForeignKey(DroneSet, null=True, blank=True) show_experimental = dbmodels.BooleanField(default=False) name_field = 'login' objects = model_logic.ExtendedManager() def save(self, *args, **kwargs): # is this a new object being saved for the first time? first_time = (self.id is None) user = thread_local.get_user() if user and not user.is_superuser() and user.login != self.login: raise AclAccessViolation("You cannot modify user " + self.login) super(User, self).save(*args, **kwargs) if first_time: everyone = AclGroup.objects.get(name='Everyone') everyone.users.add(self) def is_superuser(self): return self.access_level >= self.ACCESS_ROOT @classmethod def current_user(cls): user = thread_local.get_user() if user is None: user, _ = cls.objects.get_or_create(login=cls.AUTOTEST_SYSTEM) user.access_level = cls.ACCESS_ROOT user.save() return user class Meta: db_table = 'afe_users' def __unicode__(self): return unicode(self.login)
class Drone(dbmodels.Model, model_logic.ModelExtensions): """ A scheduler drone hostname: the drone's hostname """ hostname = dbmodels.CharField(max_length=255, unique=True) name_field = 'hostname' objects = model_logic.ExtendedManager() def save(self, *args, **kwargs): if not User.current_user().is_superuser(): raise Exception('Only superusers may edit drones') super(Drone, self).save(*args, **kwargs) def delete(self): if not User.current_user().is_superuser(): raise Exception('Only superusers may delete drones') super(Drone, self).delete() class Meta: db_table = 'afe_drones' def __unicode__(self): return unicode(self.hostname)
class Test(dbmodels.Model, model_logic.ModelExtensions): """\ Required: author: author name description: description of the test name: test name time: short, medium, long test_class: This describes the class for your the test belongs in. test_category: This describes the category for your tests test_type: Client or Server path: path to pass to run_test() sync_count: is a number >=1 (1 being the default). If it's 1, then it's an async job. If it's >1 it's sync job for that number of machines i.e. if sync_count = 2 it is a sync job that requires two machines. Optional: dependencies: What the test requires to run. Comma deliminated list dependency_labels: many-to-many relationship with labels corresponding to test dependencies. experimental: If this is set to True production servers will ignore the test run_verify: Whether or not the scheduler should run the verify stage """ TestTime = enum.Enum('SHORT', 'MEDIUM', 'LONG', start_value=1) TestTypes = model_attributes.TestTypes # TODO(showard) - this should be merged with Job.ControlType (but right # now they use opposite values) name = dbmodels.CharField(max_length=255, unique=True) author = dbmodels.CharField(max_length=255) test_class = dbmodels.CharField(max_length=255) test_category = dbmodels.CharField(max_length=255) dependencies = dbmodels.CharField(max_length=255, blank=True) description = dbmodels.TextField(blank=True) experimental = dbmodels.BooleanField(default=True) run_verify = dbmodels.BooleanField(default=True) test_time = dbmodels.SmallIntegerField(choices=TestTime.choices(), default=TestTime.MEDIUM) test_type = dbmodels.SmallIntegerField(choices=TestTypes.choices()) sync_count = dbmodels.IntegerField(default=1) path = dbmodels.CharField(max_length=255, unique=True) dependency_labels = ( dbmodels.ManyToManyField(Label, blank=True, db_table='afe_autotests_dependency_labels')) name_field = 'name' objects = model_logic.ExtendedManager() def admin_description(self): escaped_description = saxutils.escape(self.description) return '<span style="white-space:pre">%s</span>' % escaped_description admin_description.allow_tags = True admin_description.short_description = 'Description' class Meta: db_table = 'afe_autotests' def __unicode__(self): return unicode(self.name)
class Test(dbmodels.Model, model_logic.ModelExtensions, model_logic.ModelWithAttributes): test_idx = dbmodels.AutoField(primary_key=True) job = dbmodels.ForeignKey(Job, db_column='job_idx') test = dbmodels.CharField(max_length=300) subdir = dbmodels.CharField(blank=True, max_length=300) kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx') status = dbmodels.ForeignKey(Status, db_column='status') reason = dbmodels.CharField(blank=True, max_length=3072) machine = dbmodels.ForeignKey(Machine, db_column='machine_idx') finished_time = dbmodels.DateTimeField(null=True, blank=True) started_time = dbmodels.DateTimeField(null=True, blank=True) objects = model_logic.ExtendedManager() def _get_attribute_model_and_args(self, attribute): return TestAttribute, dict(test=self, attribute=attribute, user_created=True) def set_attribute(self, attribute, value): # ensure non-user-created attributes remain immutable try: TestAttribute.objects.get(test=self, attribute=attribute, user_created=False) raise ValueError('Attribute %s already exists for test %s and is ' 'immutable' % (attribute, self.test_idx)) except TestAttribute.DoesNotExist: super(Test, self).set_attribute(attribute, value) class Meta: db_table = 'tko_tests'
class ServerRole(dbmodels.Model, model_logic.ModelExtensions): """Role associated with hosts.""" # Valid roles for a server. ROLE_LIST = [ 'afe', 'scheduler', 'host_scheduler', 'drone', 'devserver', 'database', 'database_slave', 'suite_scheduler', 'crash_server', 'shard', 'golo_proxy', 'sentinel', 'reserve' ] ROLE = enum.Enum(*ROLE_LIST, string_values=True) # When deleting any of following roles from a primary server, a working # backup must be available if user_server_db is enabled in global config. ROLES_REQUIRE_BACKUP = [ ROLE.SCHEDULER, ROLE.HOST_SCHEDULER, ROLE.DATABASE, ROLE.SUITE_SCHEDULER, ROLE.DRONE ] # Roles that must be assigned to a single primary server in an Autotest # instance ROLES_REQUIRE_UNIQUE_INSTANCE = [ ROLE.SCHEDULER, ROLE.HOST_SCHEDULER, ROLE.DATABASE, ROLE.SUITE_SCHEDULER ] server = dbmodels.ForeignKey(Server, related_name='roles') role = dbmodels.CharField(max_length=128, choices=ROLE.choices()) objects = model_logic.ExtendedManager() class Meta: """Metadata for the ServerRole class.""" db_table = 'server_roles'
class IneligibleHostQueue(dbmodels.Model, model_logic.ModelExtensions): job = dbmodels.ForeignKey(Job) host = dbmodels.ForeignKey(Host) objects = model_logic.ExtendedManager() class Meta: db_table = 'afe_ineligible_host_queues'
class HostAttribute(dbmodels.Model): """Arbitrary keyvals associated with hosts.""" host = dbmodels.ForeignKey(Host) attribute = dbmodels.CharField(max_length=90) value = dbmodels.CharField(max_length=300) objects = model_logic.ExtendedManager() class Meta: db_table = 'afe_host_attributes'
class TestAttribute(dbmodels.Model, model_logic.ModelExtensions): test = dbmodels.ForeignKey(Test, db_column='test_idx') attribute = dbmodels.CharField(max_length=90) value = dbmodels.CharField(blank=True, max_length=300) user_created = dbmodels.BooleanField(default=False) objects = model_logic.ExtendedManager() class Meta: db_table = 'tko_test_attributes'
class JobKeyval(dbmodels.Model, model_logic.ModelExtensions): """Keyvals associated with jobs""" job = dbmodels.ForeignKey(Job) key = dbmodels.CharField(max_length=90) value = dbmodels.CharField(max_length=300) objects = model_logic.ExtendedManager() class Meta: db_table = 'afe_job_keyvals'
class IterationResult(dbmodels.Model, model_logic.ModelExtensions): # see comment on IterationAttribute regarding primary_key=True test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True) iteration = dbmodels.IntegerField() attribute = dbmodels.CharField(max_length=90) value = dbmodels.FloatField(null=True, blank=True) objects = model_logic.ExtendedManager() class Meta: db_table = 'tko_iteration_result'
class TestLabel(dbmodels.Model, model_logic.ModelExtensions): name = dbmodels.CharField(max_length=80, unique=True) description = dbmodels.TextField(blank=True) tests = dbmodels.ManyToManyField(Test, blank=True, db_table='tko_test_labels_tests') name_field = 'name' objects = model_logic.ExtendedManager() class Meta: db_table = 'tko_test_labels'
class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions): # this isn't really a primary key, but it's necessary to appease Django # and is harmless as long as we're careful test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True) iteration = dbmodels.IntegerField() attribute = dbmodels.CharField(max_length=90) value = dbmodels.CharField(blank=True, max_length=300) objects = model_logic.ExtendedManager() class Meta: db_table = 'tko_iteration_attributes'
class ServerAttribute(dbmodels.Model, model_logic.ModelExtensions): """Attribute associated with hosts.""" server = dbmodels.ForeignKey(Server, related_name='attributes') attribute = dbmodels.CharField(max_length=128) value = dbmodels.TextField(null=True, blank=True) date_modified = dbmodels.DateTimeField(null=True, blank=True) objects = model_logic.ExtendedManager() class Meta: """Metadata for the ServerAttribute class.""" db_table = 'server_attributes'
class AbortedHostQueueEntry(dbmodels.Model, model_logic.ModelExtensions): queue_entry = dbmodels.OneToOneField(HostQueueEntry, primary_key=True) aborted_by = dbmodels.ForeignKey(User) aborted_on = dbmodels.DateTimeField() objects = model_logic.ExtendedManager() def save(self, *args, **kwargs): self.aborted_on = datetime.now() super(AbortedHostQueueEntry, self).save(*args, **kwargs) class Meta: db_table = 'afe_aborted_host_queue_entries'
class Job(dbmodels.Model, model_logic.ModelExtensions): job_idx = dbmodels.AutoField(primary_key=True) tag = dbmodels.CharField(unique=True, max_length=100) label = dbmodels.CharField(max_length=300) username = dbmodels.CharField(max_length=240) machine = dbmodels.ForeignKey(Machine, db_column='machine_idx') queued_time = dbmodels.DateTimeField(null=True, blank=True) started_time = dbmodels.DateTimeField(null=True, blank=True) finished_time = dbmodels.DateTimeField(null=True, blank=True) afe_job_id = dbmodels.IntegerField(null=True, default=None) objects = model_logic.ExtendedManager() class Meta: db_table = 'tko_jobs'
class Profiler(dbmodels.Model, model_logic.ModelExtensions): """\ Required: name: profiler name test_type: Client or Server Optional: description: arbirary text description """ name = dbmodels.CharField(max_length=255, unique=True) description = dbmodels.TextField(blank=True) name_field = 'name' objects = model_logic.ExtendedManager() class Meta: db_table = 'afe_profilers' def __unicode__(self): return unicode(self.name)
class RecurringRun(dbmodels.Model, model_logic.ModelExtensions): """\ job: job to use as a template owner: owner of the instantiated template start_date: Run the job at scheduled date loop_period: Re-run (loop) the job periodically (in every loop_period seconds) loop_count: Re-run (loop) count """ job = dbmodels.ForeignKey(Job) owner = dbmodels.ForeignKey(User) start_date = dbmodels.DateTimeField() loop_period = dbmodels.IntegerField(blank=True) loop_count = dbmodels.IntegerField(blank=True) objects = model_logic.ExtendedManager() class Meta: db_table = 'afe_recurring_run' def __unicode__(self): return u'RecurringRun(job %s, start %s, period %s, count %s)' % ( self.job.id, self.start_date, self.loop_period, self.loop_count)
class AclGroup(dbmodels.Model, model_logic.ModelExtensions): """\ Required: name: name of ACL group Optional: description: arbitrary description of group """ name = dbmodels.CharField(max_length=255, unique=True) description = dbmodels.CharField(max_length=255, blank=True) users = dbmodels.ManyToManyField(User, blank=False, db_table='afe_acl_groups_users') hosts = dbmodels.ManyToManyField(Host, blank=True, db_table='afe_acl_groups_hosts') name_field = 'name' objects = model_logic.ExtendedManager() @staticmethod def check_for_acl_violation_hosts(hosts): user = User.current_user() if user.is_superuser(): return accessible_host_ids = set( host.id for host in Host.objects.filter(aclgroup__users=user)) for host in hosts: # Check if the user has access to this host, # but only if it is not a metahost or a one-time-host no_access = (isinstance(host, Host) and not host.invalid and int(host.id) not in accessible_host_ids) if no_access: raise AclAccessViolation("%s does not have access to %s" % (str(user), str(host))) @staticmethod def check_abort_permissions(queue_entries): """ look for queue entries that aren't abortable, meaning * the job isn't owned by this user, and * the machine isn't ACL-accessible, or * the machine is in the "Everyone" ACL """ user = User.current_user() if user.is_superuser(): return not_owned = queue_entries.exclude(job__owner=user.login) # I do this using ID sets instead of just Django filters because # filtering on M2M dbmodels is broken in Django 0.96. It's better in # 1.0. # TODO: Use Django filters, now that we're using 1.0. accessible_ids = set( entry.id for entry in not_owned.filter(host__aclgroup__users__login=user.login)) public_ids = set(entry.id for entry in not_owned.filter(host__aclgroup__name='Everyone')) cannot_abort = [entry for entry in not_owned.select_related() if entry.id not in accessible_ids or entry.id in public_ids] if len(cannot_abort) == 0: return entry_names = ', '.join('%s-%s/%s' % (entry.job.id, entry.job.owner, entry.host_or_metahost_name()) for entry in cannot_abort) raise AclAccessViolation('You cannot abort the following job entries: ' + entry_names) def check_for_acl_violation_acl_group(self): user = User.current_user() if user.is_superuser(): return if self.name == 'Everyone': raise AclAccessViolation("You cannot modify 'Everyone'!") if not user in self.users.all(): raise AclAccessViolation("You do not have access to %s" % self.name) @staticmethod def on_host_membership_change(): everyone = AclGroup.objects.get(name='Everyone') # find hosts that aren't in any ACL group and add them to Everyone # TODO(showard): this is a bit of a hack, since the fact that this query # works is kind of a coincidence of Django internals. This trick # doesn't work in general (on all foreign key relationships). I'll # replace it with a better technique when the need arises. orphaned_hosts = Host.valid_objects.filter(aclgroup__id__isnull=True) everyone.hosts.add(*orphaned_hosts.distinct()) # find hosts in both Everyone and another ACL group, and remove them # from Everyone hosts_in_everyone = Host.valid_objects.filter(aclgroup__name='Everyone') acled_hosts = set() for host in hosts_in_everyone: # Has an ACL group other than Everyone if host.aclgroup_set.count() > 1: acled_hosts.add(host) everyone.hosts.remove(*acled_hosts) def delete(self): if (self.name == 'Everyone'): raise AclAccessViolation("You cannot delete 'Everyone'!") self.check_for_acl_violation_acl_group() super(AclGroup, self).delete() self.on_host_membership_change() def add_current_user_if_empty(self): if not self.users.count(): self.users.add(User.current_user()) def perform_after_save(self, change): if not change: self.users.add(User.current_user()) self.add_current_user_if_empty() self.on_host_membership_change() def save(self, *args, **kwargs): change = bool(self.id) if change: # Check the original object for an ACL violation AclGroup.objects.get(id=self.id).check_for_acl_violation_acl_group() super(AclGroup, self).save(*args, **kwargs) self.perform_after_save(change) class Meta: db_table = 'afe_acl_groups' def __unicode__(self): return unicode(self.name)
class DroneSet(dbmodels.Model, model_logic.ModelExtensions): """ A set of scheduler drones These will be used by the scheduler to decide what drones a job is allowed to run on. name: the drone set's name drones: the drones that are part of the set """ DRONE_SETS_ENABLED = global_config.global_config.get_config_value( 'SCHEDULER', 'drone_sets_enabled', type=bool, default=False) DEFAULT_DRONE_SET_NAME = global_config.global_config.get_config_value( 'SCHEDULER', 'default_drone_set_name', default=None) name = dbmodels.CharField(max_length=255, unique=True) drones = dbmodels.ManyToManyField(Drone, db_table='afe_drone_sets_drones') name_field = 'name' objects = model_logic.ExtendedManager() def save(self, *args, **kwargs): if not User.current_user().is_superuser(): raise Exception('Only superusers may edit drone sets') super(DroneSet, self).save(*args, **kwargs) def delete(self): if not User.current_user().is_superuser(): raise Exception('Only superusers may delete drone sets') super(DroneSet, self).delete() @classmethod def drone_sets_enabled(cls): return cls.DRONE_SETS_ENABLED @classmethod def default_drone_set_name(cls): return cls.DEFAULT_DRONE_SET_NAME @classmethod def get_default(cls): return cls.smart_get(cls.DEFAULT_DRONE_SET_NAME) @classmethod def resolve_name(cls, drone_set_name): """ Returns the name of one of these, if not None, in order of preference: 1) the drone set given, 2) the current user's default drone set, or 3) the global default drone set or returns None if drone sets are disabled """ if not cls.drone_sets_enabled(): return None user = User.current_user() user_drone_set_name = user.drone_set and user.drone_set.name return drone_set_name or user_drone_set_name or cls.get_default().name def get_drone_hostnames(self): """ Gets the hostnames of all drones in this drone set """ return set(self.drones.all().values_list('hostname', flat=True)) class Meta: db_table = 'afe_drone_sets' def __unicode__(self): return unicode(self.name)
class SpecialTask(dbmodels.Model, model_logic.ModelExtensions): """\ Tasks to run on hosts at the next time they are in the Ready state. Use this for high-priority tasks, such as forced repair or forced reinstall. host: host to run this task on task: special task to run time_requested: date and time the request for this task was made is_active: task is currently running is_complete: task has finished running time_started: date and time the task started queue_entry: Host queue entry waiting on this task (or None, if task was not started in preparation of a job) """ Task = enum.Enum('Verify', 'Cleanup', 'Repair', string_values=True) host = dbmodels.ForeignKey(Host, blank=False, null=False) task = dbmodels.CharField(max_length=64, choices=Task.choices(), blank=False, null=False) requested_by = dbmodels.ForeignKey(User) time_requested = dbmodels.DateTimeField(auto_now_add=True, blank=False, null=False) is_active = dbmodels.BooleanField(default=False, blank=False, null=False) is_complete = dbmodels.BooleanField(default=False, blank=False, null=False) time_started = dbmodels.DateTimeField(null=True, blank=True) queue_entry = dbmodels.ForeignKey(HostQueueEntry, blank=True, null=True) success = dbmodels.BooleanField(default=False, blank=False, null=False) objects = model_logic.ExtendedManager() def save(self, **kwargs): if self.queue_entry: self.requested_by = User.objects.get( login=self.queue_entry.job.owner) super(SpecialTask, self).save(**kwargs) def execution_path(self): """@see HostQueueEntry.execution_path()""" return 'hosts/%s/%s-%s' % (self.host.hostname, self.id, self.task.lower()) # property to emulate HostQueueEntry.status @property def status(self): """ Return a host queue entry status appropriate for this task. Although SpecialTasks are not HostQueueEntries, it is helpful to the user to present similar statuses. """ if self.is_complete: if self.success: return HostQueueEntry.Status.COMPLETED return HostQueueEntry.Status.FAILED if self.is_active: return HostQueueEntry.Status.RUNNING return HostQueueEntry.Status.QUEUED # property to emulate HostQueueEntry.started_on @property def started_on(self): return self.time_started @classmethod def schedule_special_task(cls, host, task): """ Schedules a special task on a host if the task is not already scheduled. """ existing_tasks = SpecialTask.objects.filter(host__id=host.id, task=task, is_active=False, is_complete=False) if existing_tasks: return existing_tasks[0] special_task = SpecialTask(host=host, task=task, requested_by=User.current_user()) special_task.save() return special_task def activate(self): """ Sets a task as active and sets the time started to the current time. """ logging.info('Starting: %s', self) self.is_active = True self.time_started = datetime.now() self.save() def finish(self, success): """ Sets a task as completed """ logging.info('Finished: %s', self) self.is_active = False self.is_complete = True self.success = success self.save() class Meta: db_table = 'afe_special_tasks' def __unicode__(self): result = u'Special Task %s (host %s, task %s, time %s)' % ( self.id, self.host, self.task, self.time_requested) if self.is_complete: result += u' (completed)' elif self.is_active: result += u' (active)' return result
class HostQueueEntry(dbmodels.Model, model_logic.ModelExtensions): Status = host_queue_entry_states.Status ACTIVE_STATUSES = host_queue_entry_states.ACTIVE_STATUSES COMPLETE_STATUSES = host_queue_entry_states.COMPLETE_STATUSES job = dbmodels.ForeignKey(Job) host = dbmodels.ForeignKey(Host, blank=True, null=True) status = dbmodels.CharField(max_length=255) meta_host = dbmodels.ForeignKey(Label, blank=True, null=True, db_column='meta_host') active = dbmodels.BooleanField(default=False) complete = dbmodels.BooleanField(default=False) deleted = dbmodels.BooleanField(default=False) execution_subdir = dbmodels.CharField(max_length=255, blank=True, default='') # If atomic_group is set, this is a virtual HostQueueEntry that will # be expanded into many actual hosts within the group at schedule time. atomic_group = dbmodels.ForeignKey(AtomicGroup, blank=True, null=True) aborted = dbmodels.BooleanField(default=False) started_on = dbmodels.DateTimeField(null=True, blank=True) objects = model_logic.ExtendedManager() def __init__(self, *args, **kwargs): super(HostQueueEntry, self).__init__(*args, **kwargs) self._record_attributes(['status']) @classmethod def create(cls, job, host=None, meta_host=None, atomic_group=None, is_template=False): if is_template: status = cls.Status.TEMPLATE else: status = cls.Status.QUEUED return cls(job=job, host=host, meta_host=meta_host, atomic_group=atomic_group, status=status) def save(self, *args, **kwargs): self._set_active_and_complete() super(HostQueueEntry, self).save(*args, **kwargs) self._check_for_updated_attributes() def execution_path(self): """ Path to this entry's results (relative to the base results directory). """ return os.path.join(self.job.tag(), self.execution_subdir) def host_or_metahost_name(self): if self.host: return self.host.hostname elif self.meta_host: return self.meta_host.name else: assert self.atomic_group, "no host, meta_host or atomic group!" return self.atomic_group.name def _set_active_and_complete(self): if self.status in self.ACTIVE_STATUSES: self.active, self.complete = True, False elif self.status in self.COMPLETE_STATUSES: self.active, self.complete = False, True else: self.active, self.complete = False, False def on_attribute_changed(self, attribute, old_value): assert attribute == 'status' logging.info('%s/%d (%d) -> %s' % (self.host, self.job.id, self.id, self.status)) def is_meta_host_entry(self): 'True if this is a entry has a meta_host instead of a host.' return self.host is None and self.meta_host is not None def log_abort(self, user): abort_log = AbortedHostQueueEntry(queue_entry=self, aborted_by=user) abort_log.save() def abort(self): # this isn't completely immune to race conditions since it's not atomic, # but it should be safe given the scheduler's behavior. if not self.complete and not self.aborted: self.log_abort(User.current_user()) self.aborted = True self.save() @classmethod def compute_full_status(cls, status, aborted, complete): if aborted and not complete: return 'Aborted (%s)' % status return status def full_status(self): return self.compute_full_status(self.status, self.aborted, self.complete) def _postprocess_object_dict(self, object_dict): object_dict['full_status'] = self.full_status() class Meta: db_table = 'afe_host_queue_entries' def __unicode__(self): hostname = None if self.host: hostname = self.host.hostname return u"%s/%d (%d)" % (hostname, self.job.id, self.id)
class Server(dbmodels.Model, model_logic.ModelExtensions): """Models a server.""" DETAIL_FMT = ('Hostname : %(hostname)s\n' 'Status : %(status)s\n' 'Roles : %(roles)s\n' 'Attributes : %(attributes)s\n' 'Date Created : %(date_created)s\n' 'Date Modified: %(date_modified)s\n' 'Note : %(note)s\n') STATUS_LIST = ['primary', 'backup', 'repair_required'] STATUS = enum.Enum(*STATUS_LIST, string_values=True) hostname = dbmodels.CharField(unique=True, max_length=128) cname = dbmodels.CharField(null=True, blank=True, default=None, max_length=128) status = dbmodels.CharField(unique=False, max_length=128, choices=STATUS.choices()) date_created = dbmodels.DateTimeField(null=True, blank=True) date_modified = dbmodels.DateTimeField(null=True, blank=True) note = dbmodels.TextField(null=True, blank=True) objects = model_logic.ExtendedManager() class Meta: """Metadata for class Server.""" db_table = 'servers' def __unicode__(self): """A string representation of the Server object. """ roles = ','.join([r.role for r in self.roles.all()]) attributes = dict([(a.attribute, a.value) for a in self.attributes.all()]) return self.DETAIL_FMT % { 'hostname': self.hostname, 'status': self.status, 'roles': roles, 'attributes': attributes, 'date_created': self.date_created, 'date_modified': self.date_modified, 'note': self.note } def get_role_names(self): """Get a list of role names of the server. @return: A list of role names of the server. """ return [r.role for r in self.roles.all()] def get_details(self): """Get a dictionary with all server details. For example: { 'hostname': 'server1', 'status': 'backup', 'roles': ['drone', 'scheduler'], 'attributes': {'max_processes': 300} } @return: A dictionary with all server details. """ details = {} details['hostname'] = self.hostname details['status'] = self.status details['roles'] = self.get_role_names() attributes = dict([(a.attribute, a.value) for a in self.attributes.all()]) details['attributes'] = attributes details['date_created'] = self.date_created details['date_modified'] = self.date_modified details['note'] = self.note return details