Skip to content

Commit 685aca2

Browse files
committed
S3File model: add version and change task-run to work-id
1 parent 4ff1ab5 commit 685aca2

File tree

7 files changed

+137
-32
lines changed

7 files changed

+137
-32
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 2.1.4 on 2019-03-06 00:17
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('home', '0021_auto_20190304_0300'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='s3file',
15+
name='version',
16+
field=models.IntegerField(default=1),
17+
preserve_default=False,
18+
),
19+
migrations.AddField(
20+
model_name='s3file',
21+
name='work_id',
22+
field=models.TextField(default=1),
23+
preserve_default=False,
24+
),
25+
migrations.RemoveField(
26+
model_name='s3file',
27+
name='task_run',
28+
),
29+
migrations.AlterUniqueTogether(
30+
name='s3file',
31+
unique_together={('version', 'what', 'where', 'work_id')},
32+
),
33+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 2.1.4 on 2019-03-06 02:31
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('home', '0022_auto_20190306_0017'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='s3file',
15+
options={'ordering': ('-version',)},
16+
),
17+
migrations.AlterField(
18+
model_name='s3file',
19+
name='work_id',
20+
field=models.TextField(blank=True, null=True),
21+
),
22+
]

missioncontrol/home/models.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from django.contrib.postgres.fields import JSONField, HStoreField
1414
from django.db import models
1515
from django import forms
16-
from django.db.models import Q
16+
from django.db.models import Q, Max
1717
from django.db.models.signals import pre_save
1818
from django.dispatch import receiver
1919
from django.utils import timezone, dateformat
@@ -448,11 +448,10 @@ def to_dict(self):
448448
return retval
449449

450450
def _get_file_cid_if_exists(self, what):
451-
try:
452-
f = self.files.get(what=what)
453-
except S3File.DoesNotExist:
454-
return None
455-
return f.cid
451+
f = S3File.objects.filter(what=what, work_id=self.uuid).first()
452+
if f:
453+
return f.cid
454+
return None
456455

457456
@property
458457
def stdout(self):
@@ -486,22 +485,28 @@ class S3File(models.Model, Serializable):
486485
'Can be blank if instantaneous file.',
487486
null=True, blank=True)
488487
created = ISODateTimeField(auto_now_add=True)
489-
task_run = models.ForeignKey(TaskRun, to_field='uuid', related_name='files',
490-
on_delete=models.PROTECT, null=True, blank=True)
488+
work_id = models.TextField(null=True, blank=True)
489+
version = models.IntegerField()
490+
491+
class Meta:
492+
unique_together = ('version', 'what', 'where', 'work_id')
493+
ordering = ('-version', )
491494

492495
# s3://bucket/some_path
493496
@property
494497
def prefix(self):
495498
path = settings.FILE_STORAGE_PATH
496499
if path.startswith('s3://'):
497500
return '/'.join(path.split('/')[3:])
501+
# TODO
498502
raise NotImplementedError("Not yet implemented non s3 paths")
499503

500504
@property
501505
def bucket(self):
502506
path = settings.FILE_STORAGE_PATH
503507
if path.startswith('s3://'):
504508
return path.split('/')[2]
509+
# TODO
505510
raise NotImplementedError("Not yet implemented non s3 paths")
506511

507512
@property
@@ -529,6 +534,20 @@ def get_upload_url(self):
529534

530535
return post
531536

537+
def save(self, *args, **kwargs):
538+
# Set version if not given
539+
if self.version is None:
540+
prev_version = S3File.objects.filter(
541+
what=self.what, where=self.where, work_id=self.work_id
542+
).aggregate(max_version=Max('version'))
543+
544+
if prev_version['max_version'] is None:
545+
self.version = 1
546+
else:
547+
self.version = prev_version['max_version'] + 1
548+
549+
super().save(*args, **kwargs)
550+
532551

533552

534553
class CachedAccess(models.Model):

missioncontrol/openapi/openapi.yaml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,9 +1125,9 @@ paths:
11251125
schema:
11261126
type: string
11271127
- in: query
1128-
name: task_run
1128+
name: work_id
11291129
description:
1130-
Only return files with this task_run uuid.
1130+
Only return files with this work_id uuid.
11311131
schema:
11321132
type: string
11331133
format: uuid
@@ -1151,6 +1151,14 @@ paths:
11511151
Only return files with this path name
11521152
schema:
11531153
type: string
1154+
- in: query
1155+
name: version
1156+
description:
1157+
Only return files with this version number
1158+
# TODO add a flag to return just the latest
1159+
# version numbers
1160+
schema:
1161+
type: string
11541162
responses:
11551163
200:
11561164
description: A list of files
@@ -1542,15 +1550,14 @@ components:
15421550
type: string
15431551
format: date-time
15441552
nullable: true
1545-
task_run:
1553+
work_id:
15461554
type: string
15471555
format: uuid
15481556
nullable: true
1549-
post_url_fields:
1550-
"$ref": "#/components/schemas/PostUrlFields"
1557+
version:
1558+
type: integer
15511559
readOnly: true
15521560

1553-
15541561
PostUrlFields:
15551562
properties:
15561563
url:

missioncontrol/tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def simple_file(some_hash):
140140
'what': 'stdout',
141141
'start': "2018-11-25T01:00:00.000000Z",
142142
'end': None,
143-
'task_run': None,
143+
'work_id': None,
144144
'path': '/some/path/for/files',
145145
'where': 'somewhere hidden',
146146
}
@@ -160,4 +160,8 @@ def yet_another_uuid():
160160

161161
@pytest.fixture
162162
def some_hash():
163+
return hashlib.blake2b(uuid4().bytes).hexdigest()
164+
165+
@pytest.fixture
166+
def another_hash():
163167
return hashlib.blake2b(uuid4().bytes).hexdigest()

missioncontrol/tests/test_files.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ def test_file_put_get(boto3_mock, test_client, some_hash, simple_file):
2424

2525
assert response.status_code == 200, response.get_data()
2626
expected = simple_file.copy()
27-
expected['post_url_fields'] = post_values
28-
2927

3028
formatter = dateformat.DateFormat(created)
3129
expected['created'] = formatter.format(settings.DATETIME_FORMAT)
30+
expected['version'] = 1
3231
assert response.json == expected
3332

3433
try:
@@ -70,7 +69,7 @@ def test_file_search_empty(test_client, some_uuid):
7069
'what': 'a thing',
7170
'cid': 'nope',
7271
'where': 'nowhere',
73-
'task_run': some_uuid,
72+
'work_id': some_uuid,
7473
'range_start': "2018-11-25T00:00:00.000000Z",
7574
'range_end': "2018-11-25T00:00:00.000000Z",
7675
})
@@ -121,7 +120,7 @@ def create_asset(asset_type, asset):
121120
json=simple_task_run)
122121
assert response.status_code == 201, response.get_data()
123122

124-
simple_file['task_run'] = some_uuid
123+
simple_file['work_id'] = some_uuid
125124
created = timezone.now()
126125
with patch('django.utils.timezone.now', return_value=created):
127126
response = test_client.put(f'/api/v0/files/{some_hash}/', json=simple_file)
@@ -130,6 +129,7 @@ def create_asset(asset_type, asset):
130129
expected = simple_file.copy()
131130
formatter = dateformat.DateFormat(created)
132131
expected['created'] = formatter.format(settings.DATETIME_FORMAT)
132+
expected['version'] = 1
133133

134134
search_query = simple_file.copy()
135135
search_query['range_start'] = search_query.pop('start')
@@ -142,7 +142,7 @@ def create_asset(asset_type, asset):
142142

143143
# Make sure changing some of the searches *DON'T* find it.
144144
simple_query_false = simple_file.copy()
145-
simple_query_false['task_run'] = uuid.uuid4()
145+
simple_query_false['work_id'] = uuid.uuid4()
146146
simple_query_false['range_start'] = simple_query_false.pop('start')
147147
response = test_client.get(f'/api/v0/files/', query_string=simple_query_false)
148148
assert response.status_code == 200, response.get_data()
@@ -159,4 +159,26 @@ def create_asset(asset_type, asset):
159159
expected['stdout'] = simple_file['cid']
160160
expected['stderr'] = None
161161

162-
assert response.json == expected
162+
assert response.json == expected
163+
164+
165+
@pytest.mark.django_db
166+
def test_version_increment(test_client, simple_file, some_hash, another_hash):
167+
created = timezone.now()
168+
with patch('django.utils.timezone.now', return_value=created):
169+
response = test_client.put(f'/api/v0/files/{some_hash}/', json=simple_file)
170+
assert response.status_code == 201, response.get_data()
171+
result1 = response.json
172+
173+
simple_file['cid'] = another_hash
174+
with patch('django.utils.timezone.now', return_value=created):
175+
response = test_client.put(f'/api/v0/files/{another_hash}/', json=simple_file)
176+
assert response.status_code == 201, response.get_data()
177+
result2 = response.json
178+
179+
assert result1.pop('version') == 1
180+
assert result2.pop('version') == 2
181+
182+
assert result1.pop('cid') == some_hash
183+
assert result2.pop('cid') == another_hash
184+
assert result1 == result2

missioncontrol/v0/files.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import requests
22

3+
from connexion.exceptions import ProblemException
34
from django.conf import settings
45
from django.db.models import Q
56

6-
from home.models import S3File, TaskRun
7+
from home.models import S3File
78

89
def search(**kwargs):
910
# remove some unused ones
@@ -31,19 +32,16 @@ def get_data(cid):
3132
def get(cid):
3233
obj = S3File.objects.get(cid=cid)
3334
retval = obj.to_dict()
34-
retval['post_url_fields'] = obj.get_upload_url()
3535
return retval
3636

3737
def put(cid, file_body):
38-
task_run_uuid = file_body.pop('task_run', None)
39-
task_run = None
40-
if task_run_uuid:
41-
# See if it exists
42-
task_run = TaskRun.objects.get(uuid=task_run_uuid)
43-
file_body['task_run'] = task_run
44-
38+
body_cid = file_body.pop('cid', None)
39+
if body_cid is not None and cid != body_cid:
40+
raise ProblemException(
41+
status=409,
42+
title='Conflict',
43+
detail='cid in url does not match body',
44+
)
4545
obj, created = S3File.objects.update_or_create(cid=cid, defaults=file_body)
46-
4746
retval = obj.to_dict()
48-
retval['post_url_fields'] = obj.get_upload_url()
4947
return retval, 201 if created else 200

0 commit comments

Comments
 (0)