from __future__ import unicode_literals from django.contrib.auth.models import User from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.crypto import get_random_string import config.env def generate_token(length=32): """ Generate a random string according to its length. This function has been created to ensure that the function is called every time an object is created. Args: length (int): number of characters for the token. (default = 32) Returns: token as a string of n characters. """ return get_random_string(length) class TimeStampModel(models.Model): """ An abstract base class model that provides self-updating ``created_at`` and ``updated_at`` fields. """ created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now_add=True) class Meta: abstract = True class AllgoUser(models.Model): """ Class linked to the Django user management. If there is a need to add a field related to a user, it should be added here and not in the auth_user table. """ sshkey = models.TextField(null=True, blank=True) token = models.CharField(max_length=32, default=generate_token) # Relationships user = models.OneToOneField( User, on_delete=models.CASCADE, related_name="allgouser") class Meta: db_table = 'dj_users' verbose_name = 'allgo user' verbose_name_plural = 'allgo users' def __str__(self): return self.user.username def getApp(self): return [a.docker_name for a in Webapp.objects.filter(user_id=self.user.id)] class DockerOs(models.Model): """ Contains the different Operating Systems that a user can choose from to build its container. """ # Fields name = models.CharField(max_length=255, blank=True) version = models.CharField(max_length=255, blank=True) docker_name = models.CharField(max_length=255, blank=True) class Meta: db_table = 'dj_docker_os' def __str__(self): return self.name + ' ' + self.version class JobQueue(TimeStampModel): """ Data regarding the type of queues available for the system. """ # Fields name = models.CharField(max_length=255) timeout = models.IntegerField(blank=True, null=True) is_default = models.IntegerField() class Meta: db_table = 'dj_job_queues' def __str__(self): return self.name class Webapp(TimeStampModel): """ Webapp model related to the creation and management of a particular web app belonging to one or more users. """ IDLE = 0 RUNNING = 1 STARTING = 2 START_ERROR = 3 STOPPING = 4 STOP_ERROR = 5 SANDBOX_STATE_CHOICES = ( (IDLE, 'IDLE'), (RUNNING, 'RUNNING'), (STARTING, 'STARTING'), (START_ERROR, 'START_ERROR'), (STOPPING, 'STOPPING'), (STOP_ERROR, 'STOP_ERROR'), ) # Fields name = models.CharField(unique=True, max_length=255, blank=True) description = models.TextField(blank=True, null=True) # Should given according to the number of users declared as admin to this # app contact = models.CharField(max_length=255, blank=True, null=True) # Logo logo_file_name = models.CharField(max_length=255, blank=True, null=True) logo_content_type = models.CharField(max_length=255, blank=True, null=True) logo_file_size = models.IntegerField(blank=True, null=True) logo_updated_at = models.DateTimeField(blank=True, null=True) # Default quota default_quota = models.IntegerField(blank=True, null=True) # Docker and parameters related stuff docker_name = models.CharField(max_length=255, blank=True) readme = models.IntegerField(blank=True, null=True) entrypoint = models.CharField(max_length=255, blank=True) exec_time = models.IntegerField(blank=True, null=True) private = models.BooleanField(default=1) access_token = models.CharField(max_length=255, blank=True, null=True) sandbox_state = models.IntegerField(null=True, choices=SANDBOX_STATE_CHOICES, default=STARTING) sandbox_version = models.ForeignKey('WebappVersion', null=True, blank=True, related_name='webappversions') notebook_gitrepo = models.CharField(max_length=255, blank=True, null=True) memory_limit = models.BigIntegerField(blank=True, null=True) # Relationships # A webapp has one docker os type docker_os = models.ForeignKey(DockerOs, on_delete=models.CASCADE, related_name="webappdockeros") user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="webappuser") job_queue = models.ForeignKey(JobQueue, on_delete=models.CASCADE, related_name="webappjobqueue") class Meta: db_table = 'dj_webapps' def __str__(self): return self.name @staticmethod def _resolve_user(actor): if actor is None or isinstance(actor, User): return actor elif isinstance(actor, AllgoUser): return actor.user elif isinstance(actor, Runner): allgo_user = actor.user return None if allgo_user is None else allgo_user.user raise TypeError() def is_pullable_by(self, actor, *, client_ip=None): """Return True if the given actor is allowed to pull an image of this webapp `actor` may be a User, AllgoUser, Runner or None `client_ip` is client IP address (used for limiting admin/open_bar access to the adresses listed in ALLGO_ALLOWED_IP_ADMIN) """ if isinstance(actor, Runner) and actor.open_bar and (client_ip in config.env.ALLGO_ALLOWED_IP_ADMIN.split(',')): return True user = self._resolve_user(actor) if user == self.user: return True elif user is not None and user.is_superuser and client_ip: return client_ip in config.env.ALLGO_ALLOWED_IP_ADMIN.split(',') return False def is_pushable_by(self, actor): """Return True if the given actor is allowed to push an image of this webapp `actor` may be a User, AllgoUser, Runner or None """ return self._resolve_user(actor) == self.user class WebappParameter(TimeStampModel): """ Given parameters for a specific webapp """ # Fields value = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255, blank=True, null=True) detail = models.CharField(max_length=255, blank=True, null=True) # Relationships # a Webapp parameters corresponds to one webapp webapp = models.OneToOneField(Webapp, related_name="webappparameters") class Meta: db_table = 'dj_webapp_parameters' class WebappVersion(TimeStampModel): """ Version of a given webapp """ SANDBOX = 0 COMMITTED = 1 READY = 2 ERROR = 3 REPLACED = 4 SANDBOX_STATE_CHOICES = ( (SANDBOX, 'SANDBOX'), (COMMITTED, 'COMMITTED'), (READY, 'READY'), (ERROR, 'ERROR'), (REPLACED, 'REPLACED'), ) # Fields number = models.CharField(max_length=255, blank=True) changelog = models.CharField(max_length=255, blank=True) docker_image_size = models.FloatField(blank=True, null=True) state = models.IntegerField(null=True, choices=SANDBOX_STATE_CHOICES) published = models.IntegerField() url = models.URLField() webapp = models.ForeignKey('Webapp', on_delete=models.CASCADE, related_name="webapp") class Meta: db_table = 'dj_webapp_versions' def __str__(self): return self.number class Runner(TimeStampModel): """Allgo job runners Notes: - a runner is owned by a user and it can only run webapps that this user is allowed to pull - if `open_bar` is True and the source IP (of the http request) is listed in ALLGO_ALLOWED_IP_ADMIN, then this runner can pull all images """ token = models.CharField(unique=True, max_length=255, blank=False, default=generate_token) hostname = models.CharField(max_length=255, blank=True, null=True) cpu_count = models.IntegerField(blank=True, null=True) mem_in_GB = models.IntegerField(blank=True, null=True) last_seen = models.DateTimeField(null=True) webapps = models.ManyToManyField(Webapp, related_name="webapps") open_bar = models.BooleanField(default=False) user = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name="runneruser") class Meta: db_table = 'dj_runners' def __str__(self): return self.hostname class Quota(TimeStampModel): """ Quota given to a certain user for a given webapp. default quantity = 1000 KB = 1 MB Todo - write a function that calculates the quota left to the user """ # Fields quantity = models.BigIntegerField(blank=True, null=True, default=1000) # Relationships user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quotauser") webapp = models.ForeignKey(Webapp, on_delete=models.CASCADE, related_name="quotawebapp") class Meta: db_table = 'dj_quotas' class Job(TimeStampModel): """ Jobs ran with the specific data (user, webapp, queue type, ...) """ NEW = 0 WAITING = 1 RUNNING = 2 DONE = 3 ARCHIVED = 4 DELETED = 5 ABORTING = 6 JOB_STATE_CHOICES = ( (NEW, 'NEW'), (WAITING, 'WAITING'), (RUNNING, 'RUNNING'), (ARCHIVED, 'ARCHIVED'), (DONE, 'DONE'), (DELETED, 'DELETED'), (ABORTING, 'ABORTING'), ) NONE = 0 SUCCESS = 1 ERROR = 2 ABORTED = 3 TIMEOUT = 4 JOB_RESULT_CHOICES = ( (NONE, 'NONE'), (SUCCESS, 'SUCCESS'), (ERROR, 'ERROR'), (ABORTED, 'ABORTED'), (TIMEOUT, 'TIMEOUT'), ) # Fields param = models.CharField(max_length=255, blank=True, null=True) datasize = models.IntegerField(blank=True, null=True) version = models.CharField(max_length=255, blank=True, null=True) exec_time = models.IntegerField(blank=True, null=True) access_token = models.CharField(max_length=255, blank=True, null=True) state = models.IntegerField(choices=JOB_STATE_CHOICES, default=NEW) result = models.IntegerField(choices=JOB_RESULT_CHOICES, default=NONE) container_id = models.CharField(max_length=64, null=True) # docker container id # Relationships queue = models.ForeignKey(JobQueue, related_name="job_queue") webapp = models.ForeignKey(Webapp, related_name="job_webapp") user = models.ForeignKey(User, related_name="user") runner = models.ForeignKey(Runner, related_name="runner", blank=True, null=True) class Meta: db_table = 'dj_jobs' # def __str__(self): # return self.webapp class JobUploads(TimeStampModel): """ Data uploaded by the user to feed a given webapp """ # Fields job_file_file_name = models.CharField(max_length=255, blank=True) job_file_content_type = models.CharField(max_length=255, blank=True) job_file_file_size = models.IntegerField(blank=True, null=True) job_file_updated_at = models.DateTimeField(blank=True, auto_now_add=True, null=True) # Relationships job = models.ForeignKey(Job, related_name="jobupload_job", null=True) class Meta: db_table = 'dj_job_uploads' @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: AllgoUser.objects.create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): instance.allgouser.save()