models.py 11.6 KB
Newer Older
1
2
from __future__ import unicode_literals

BERJON Matthieu's avatar
BERJON Matthieu committed
3
from django.contrib.auth.models import User
4
from django.db import models
5
6
from django.db.models.signals import post_save
from django.dispatch import receiver
7

CAMPION Sebastien's avatar
CAMPION Sebastien committed
8
from django.utils.crypto import get_random_string
BERJON Matthieu's avatar
BERJON Matthieu committed
9

10
import config.env
11

12
13
14
15
16
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.
17
18
19
20
21
22
23

    Args:
        length (int): number of characters for the token. (default = 32)

    Returns:
        token as a string of n characters.

24
25
26
27
    """
    return get_random_string(length)


BERJON Matthieu's avatar
BERJON Matthieu committed
28
29
30
31
32
33
34
35
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)
36
37

    class Meta:
BERJON Matthieu's avatar
BERJON Matthieu committed
38
        abstract = True
39
40


BERJON Matthieu's avatar
BERJON Matthieu committed
41
class AllgoUser(models.Model):
BERJON Matthieu's avatar
BERJON Matthieu committed
42
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
43
44
45
46
47
    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.
    """

48
    sshkey = models.TextField(null=True, blank=True)
49
    token = models.CharField(max_length=32, default=generate_token)
BERJON Matthieu's avatar
BERJON Matthieu committed
50
51

    # Relationships
52
53
    user = models.OneToOneField(
            User, on_delete=models.CASCADE, related_name="allgouser")
BERJON Matthieu's avatar
BERJON Matthieu committed
54
55

    class Meta:
56
        db_table = 'dj_users'
57
58
59
60
61
        verbose_name = 'allgo user'
        verbose_name_plural = 'allgo users'

    def __str__(self):
        return self.user.username
BERJON Matthieu's avatar
BERJON Matthieu committed
62

63
    def getApp(self):
64
        return [a.docker_name for a in Webapp.objects.filter(user_id=self.user.id)]
65

BERJON Matthieu's avatar
BERJON Matthieu committed
66

BERJON Matthieu's avatar
BERJON Matthieu committed
67
class DockerOs(models.Model):
BERJON Matthieu's avatar
BERJON Matthieu committed
68
69
70
    """
    Contains the different Operating Systems that a user can choose from to
    build its container.
BERJON Matthieu's avatar
BERJON Matthieu committed
71
    """
72

BERJON Matthieu's avatar
BERJON Matthieu committed
73
74
75
76
    # 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)
77
78

    class Meta:
79
        db_table = 'dj_docker_os'
80

81
82
83
    def __str__(self):
        return self.name + ' ' + self.version

84

BERJON Matthieu's avatar
BERJON Matthieu committed
85
86
87
88
89
90
91
92
93
94
95
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:
96
        db_table = 'dj_job_queues'
BERJON Matthieu's avatar
BERJON Matthieu committed
97

98
99
100
    def __str__(self):
        return self.name

BERJON Matthieu's avatar
BERJON Matthieu committed
101

BERJON Matthieu's avatar
BERJON Matthieu committed
102
103
104
105
106
107
class Webapp(TimeStampModel):
    """
    Webapp model related to the creation and management of a particular web
    app belonging to one or more users.
    """

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    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'),
    )

BERJON Matthieu's avatar
BERJON Matthieu committed
124
125
    # Fields
    name = models.CharField(unique=True, max_length=255, blank=True)
126
    description = models.TextField(blank=True, null=True)
BERJON Matthieu's avatar
BERJON Matthieu committed
127
128
129

    # Should given according to the number of users declared as admin to this
    # app
130
    contact = models.CharField(max_length=255, blank=True, null=True)
BERJON Matthieu's avatar
BERJON Matthieu committed
131
132

    # Logo
133
134
135
136
    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)
BERJON Matthieu's avatar
BERJON Matthieu committed
137
138

    # Default quota
139
    default_quota = models.IntegerField(blank=True, null=True)
BERJON Matthieu's avatar
BERJON Matthieu committed
140
141
142

    # Docker and parameters related stuff
    docker_name = models.CharField(max_length=255, blank=True)
143
    readme = models.IntegerField(blank=True, null=True)
BERJON Matthieu's avatar
BERJON Matthieu committed
144
    entrypoint = models.CharField(max_length=255, blank=True)
145
    exec_time = models.IntegerField(blank=True, null=True)
146
    private = models.BooleanField(default=1)
147
    access_token = models.CharField(max_length=255, blank=True, null=True)
148
    sandbox_state = models.IntegerField(null=True, choices=SANDBOX_STATE_CHOICES, default=STARTING)
BAIRE Anthony's avatar
BAIRE Anthony committed
149
    sandbox_version = models.ForeignKey('WebappVersion', null=True, blank=True, related_name='webappversions')
BERJON Matthieu's avatar
BERJON Matthieu committed
150
    notebook_gitrepo = models.CharField(max_length=255, blank=True, null=True)
151

BERJON Matthieu's avatar
BERJON Matthieu committed
152
    memory_limit = models.BigIntegerField(blank=True, null=True)
153

BERJON Matthieu's avatar
BERJON Matthieu committed
154
155
156
    # Relationships

    # A webapp has one docker os type
157
    docker_os = models.ForeignKey(DockerOs, on_delete=models.CASCADE, related_name="webappdockeros")
CAMPION Sebastien's avatar
CAMPION Sebastien committed
158
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="webappuser")
159
    job_queue = models.ForeignKey(JobQueue, on_delete=models.CASCADE, related_name="webappjobqueue")
160
161

    class Meta:
162
        db_table = 'dj_webapps'
163

164
165
166
    def __str__(self):
        return self.name

167
168
169
170
171
172
173
    @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):
BAIRE Anthony's avatar
BAIRE Anthony committed
174
175
            allgo_user = actor.user
            return None if allgo_user is None else allgo_user.user
176
177
178
179
180
181
182
        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

183
184
        `client_ip` is client IP address (used for limiting admin/open_bar
        access to the adresses listed in ALLGO_ALLOWED_IP_ADMIN)
185
        """
186
187
188
        if isinstance(actor, Runner) and actor.open_bar and (client_ip in
                config.env.ALLGO_ALLOWED_IP_ADMIN.split(',')):
            return True
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204

        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

BERJON Matthieu's avatar
BERJON Matthieu committed
205
206

class WebappParameter(TimeStampModel):
BERJON Matthieu's avatar
BERJON Matthieu committed
207
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
208
    Given parameters for a specific webapp
BERJON Matthieu's avatar
BERJON Matthieu committed
209
    """
210

BERJON Matthieu's avatar
BERJON Matthieu committed
211
212
    # Fields
    value = models.CharField(max_length=255, blank=True, null=True)
213
    name = models.CharField(max_length=255, blank=True, null=True)
BERJON Matthieu's avatar
BERJON Matthieu committed
214
215
216
217
    detail = models.CharField(max_length=255, blank=True, null=True)

    # Relationships
    # a Webapp parameters corresponds to one webapp
BERJON Matthieu's avatar
BERJON Matthieu committed
218
    webapp = models.OneToOneField(Webapp, related_name="webappparameters")
219
220

    class Meta:
221
        db_table = 'dj_webapp_parameters'
222

BERJON Matthieu's avatar
BERJON Matthieu committed
223
224
class WebappVersion(TimeStampModel):
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
225
    Version of a given webapp
BERJON Matthieu's avatar
BERJON Matthieu committed
226
227
    """

BERJON Matthieu's avatar
BERJON Matthieu committed
228
    SANDBOX = 0
229
    COMMITTED = 1
BERJON Matthieu's avatar
BERJON Matthieu committed
230
231
232
233
234
235
    READY = 2
    ERROR = 3
    REPLACED = 4

    SANDBOX_STATE_CHOICES = (
        (SANDBOX, 'SANDBOX'),
236
        (COMMITTED, 'COMMITTED'),
BERJON Matthieu's avatar
BERJON Matthieu committed
237
238
239
240
        (READY, 'READY'),
        (ERROR, 'ERROR'),
        (REPLACED, 'REPLACED'),
    )
BERJON Matthieu's avatar
BERJON Matthieu committed
241
242
243
244
    # 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)
BERJON Matthieu's avatar
BERJON Matthieu committed
245
    state = models.IntegerField(null=True, choices=SANDBOX_STATE_CHOICES)
BERJON Matthieu's avatar
BERJON Matthieu committed
246
    published = models.IntegerField()
247
    url = models.URLField()
248

BERJON Matthieu's avatar
BERJON Matthieu committed
249
    webapp = models.ForeignKey('Webapp', on_delete=models.CASCADE, related_name="webapp")
250
251

    class Meta:
252
        db_table = 'dj_webapp_versions'
253

254
255
    def __str__(self):
        return self.number
256
257


CAMPION Sebastien's avatar
CAMPION Sebastien committed
258
class Runner(TimeStampModel):
BAIRE Anthony's avatar
BAIRE Anthony committed
259
260
261
262
263
264
265
266
    """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
    """
267
    token = models.CharField(unique=True, max_length=255, blank=False, default=generate_token)
CAMPION Sebastien's avatar
CAMPION Sebastien committed
268
269
270
271
272
    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")
273
    open_bar = models.BooleanField(default=False)
CAMPION Sebastien's avatar
CAMPION Sebastien committed
274

BAIRE Anthony's avatar
BAIRE Anthony committed
275
    user = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name="runneruser")
276

CAMPION Sebastien's avatar
CAMPION Sebastien committed
277
278
    class Meta:
        db_table = 'dj_runners'
279

280
281
282
    def __str__(self):
        return self.hostname

CAMPION Sebastien's avatar
CAMPION Sebastien committed
283

BERJON Matthieu's avatar
BERJON Matthieu committed
284
285
class Quota(TimeStampModel):
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
286
    Quota given to a certain user for a given webapp.
287

BERJON Matthieu's avatar
BERJON Matthieu committed
288
289
    default quantity = 1000 KB = 1 MB

BERJON Matthieu's avatar
BERJON Matthieu committed
290
291
292
    Todo
        - write a function that calculates the quota left to the user
    """
293

BERJON Matthieu's avatar
BERJON Matthieu committed
294
    # Fields
BERJON Matthieu's avatar
BERJON Matthieu committed
295
    quantity = models.BigIntegerField(blank=True, null=True, default=1000)
296

BERJON Matthieu's avatar
BERJON Matthieu committed
297
    # Relationships
BERJON Matthieu's avatar
BERJON Matthieu committed
298
299
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quotauser")
    webapp = models.ForeignKey(Webapp, on_delete=models.CASCADE, related_name="quotawebapp")
300
301

    class Meta:
302
        db_table = 'dj_quotas'
303
304


BERJON Matthieu's avatar
BERJON Matthieu committed
305
306
class Job(TimeStampModel):
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
307
    Jobs ran with the specific data (user, webapp, queue type, ...)
BERJON Matthieu's avatar
BERJON Matthieu committed
308
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    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'),
    )
326

BERJON Matthieu's avatar
BERJON Matthieu committed
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    NONE = 0
    SUCCESS = 1
    ERROR = 2
    ABORTED = 3
    TIMEOUT = 4

    JOB_RESULT_CHOICES = (
            (NONE, 'NONE'),
            (SUCCESS, 'SUCCESS'),
            (ERROR, 'ERROR'),
            (ABORTED, 'ABORTED'),
            (TIMEOUT, 'TIMEOUT'),
    )

BERJON Matthieu's avatar
BERJON Matthieu committed
341
342
343
344
345
346
    # 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)
BERJON Matthieu's avatar
BERJON Matthieu committed
347
    state = models.IntegerField(choices=JOB_STATE_CHOICES, default=NEW)
BERJON Matthieu's avatar
BERJON Matthieu committed
348
    result = models.IntegerField(choices=JOB_RESULT_CHOICES, default=NONE)
349
    container_id = models.CharField(max_length=64, null=True)   # docker container id
350

BERJON Matthieu's avatar
BERJON Matthieu committed
351
    # Relationships
BERJON Matthieu's avatar
BERJON Matthieu committed
352
353
    queue = models.ForeignKey(JobQueue, related_name="job_queue")
    webapp = models.ForeignKey(Webapp, related_name="job_webapp")
BERJON Matthieu's avatar
BERJON Matthieu committed
354
    user = models.ForeignKey(User, related_name="user")
BERJON Matthieu's avatar
BERJON Matthieu committed
355

356
    runner = models.ForeignKey(Runner, related_name="runner", blank=True, null=True)
CAMPION Sebastien's avatar
CAMPION Sebastien committed
357

358
    class Meta:
359
        db_table = 'dj_jobs'
360
361


BERJON Matthieu's avatar
BERJON Matthieu committed
362
363
    #  def __str__(self):
    #      return self.webapp
364
365


BERJON Matthieu's avatar
BERJON Matthieu committed
366
class JobUploads(TimeStampModel):
BERJON Matthieu's avatar
BERJON Matthieu committed
367
    """
BERJON Matthieu's avatar
BERJON Matthieu committed
368
    Data uploaded by the user to feed a given webapp
BERJON Matthieu's avatar
BERJON Matthieu committed
369
    """
370

BERJON Matthieu's avatar
BERJON Matthieu committed
371
372
373
374
    # 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)
375
    job_file_updated_at = models.DateTimeField(blank=True, auto_now_add=True, null=True)
376

BERJON Matthieu's avatar
BERJON Matthieu committed
377
    # Relationships
378
    job = models.ForeignKey(Job, related_name="jobupload_job", null=True)
379
380

    class Meta:
381
        db_table = 'dj_job_uploads'
382
383
384
385
386
387
388
389
390
391
392


@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()