Skip to content

Commit a9dfd46

Browse files
committed
Fix + validate custom rule configs
1 parent c159f0a commit a9dfd46

File tree

5 files changed

+51
-30
lines changed

5 files changed

+51
-30
lines changed

api/jobs/gears.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ def suggest_for_files(gear, files):
105105
return suggested_files
106106

107107
def validate_gear_config(gear, config_):
108-
if len(gear.get('manifest', {}).get('config', {})) > 0:
109-
invocation = gear_tools.derive_invocation_schema(gear['manifest'])
108+
if len(gear.get('gear', {}).get('config', {})) > 0:
109+
invocation = gear_tools.derive_invocation_schema(gear['gear'])
110110
ci = gear_tools.isolate_config_invocation(invocation)
111111
validator = Draft4Validator(ci)
112112

api/jobs/handlers.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from ..auth.apikeys import JobApiKey
2323

24-
from .gears import validate_gear_config, get_gears, get_gear, get_invocation_schema, remove_gear, upsert_gear, suggest_container, get_gear_by_name, check_for_gear_insertion
24+
from .gears import validate_gear_config, get_gears, get_gear, get_invocation_schema, remove_gear, upsert_gear, suggest_container, get_gear_by_name, check_for_gear_insertion, fill_gear_default_values
2525
from .jobs import Job, Logs
2626
from .batch import check_state, update
2727
from .queue import Queue
@@ -182,7 +182,9 @@ def post(self, cid):
182182
self.abort(400, 'Cannot find gear for alg {}, alg not valid'.format(doc['alg']))
183183

184184
doc['project_id'] = cid
185-
doc.setdefault('config', gear['gear']['config'])
185+
186+
if 'config' in doc:
187+
validate_gear_config(gear, fill_gear_default_values(gear, doc['config']))
186188

187189
result = config.db.project_rules.insert_one(doc)
188190
return { '_id': result.inserted_id }
@@ -235,6 +237,11 @@ def put(self, cid, rid):
235237
except APINotFoundException:
236238
self.abort(400, 'Cannot find gear for alg {}, alg not valid'.format(updates['alg']))
237239

240+
if 'alg' in updates or 'config' in updates:
241+
gear = get_gear_by_name(updates.get('alg', doc['alg']))
242+
config_ = fill_gear_default_values(gear, updates.get('config', doc.get('config', {})))
243+
validate_gear_config(gear, config_)
244+
238245
doc.update(updates)
239246
config.db.project_rules.replace_one({'_id': bson.ObjectId(rid)}, doc)
240247

@@ -274,7 +281,7 @@ def add(self):
274281
if not self.superuser_request:
275282
uid = self.uid
276283

277-
job = Queue.enqueue_job(payload,self.origin, perm_check_uid=uid)
284+
job = Queue.enqueue_job(payload, self.origin, perm_check_uid=uid)
278285

279286
return { '_id': job.id_ }
280287

@@ -532,7 +539,7 @@ def post(self):
532539
gear = get_gear(gear_id)
533540
if gear.get('gear', {}).get('custom', {}).get('flywheel', {}).get('invalid', False):
534541
self.abort(400, 'Gear marked as invalid, will not run!')
535-
validate_gear_config(gear, config_)
542+
validate_gear_config(gear, fill_gear_default_values(gear, config_))
536543

537544
container_ids = []
538545
container_type = None

api/jobs/rules.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ def create_potential_jobs(db, container, container_type, file_):
199199
gear = gears.get_gear_by_name(alg_name)
200200
job = Job(str(gear['_id']), inputs, tags=['auto', alg_name])
201201

202+
if 'config' in rule:
203+
job.config = rule['config']
204+
202205
potential_jobs.append({
203206
'job': job,
204207
'rule': rule

raml/schemas/definitions/rule.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
"project_id": { "type": "string" },
66
"alg": { "type": "string" },
77
"name": { "type": "string" },
8-
9-
"config": {
10-
"type": "object",
11-
"additionalProperties": { "$ref": "http://json-schema.org/draft-04/schema" }
12-
},
8+
"config": { "type": "object" },
139

1410
"rule-items": {
1511
"type": "array",

test/integration_tests/python/test_rules.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ def test_site_rules(randstr, data_builder, as_admin, as_user, as_public):
33
gear = data_builder.create_gear(gear={'name': gear_name, 'version': '0.0.1'})
44

55
gear_2_name = randstr()
6-
gear_2 = data_builder.create_gear(gear={'name': gear_2_name, 'version': '0.0.1'})
6+
gear_2 = data_builder.create_gear(gear={'name': gear_2_name, 'version': '0.0.2'})
77

88
rule = {
99
'alg': gear_name,
@@ -211,8 +211,9 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
211211
# create versioned gear to cover code selecting latest gear
212212
# add gear config to latest gear to check rules inheriting it
213213
gear_name = randstr()
214+
gear_config = {'param': {'type': 'string', 'pattern': '^default|custom$', 'default': 'default'}}
214215
gear_1 = data_builder.create_gear(gear={'name': gear_name, 'version': '0.0.1'})
215-
gear_2 = data_builder.create_gear(gear={'name': gear_name, 'version': '0.0.2', 'config': {'param': {'type': 'string'}}})
216+
gear_2 = data_builder.create_gear(gear={'name': gear_name, 'version': '0.0.2', 'config': gear_config})
216217
project = data_builder.create_project()
217218

218219
bad_payload = {'test': 'rules'}
@@ -275,32 +276,31 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
275276
# set valid rule-item
276277
rule_json['all'] = [{'type': 'file.type', 'value': 'tabular data'}]
277278

278-
# try to add project rule w/ invalid gear config
279+
# try to add project rule w/ non-existent gear
279280
# NOTE this is a legacy rule
280-
rule_json['config'] = {'foo': 'bar'}
281281
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
282282
assert r.status_code == 400
283-
assert "'bar' is not of type u'object'" in r.json()['message']
284-
del rule_json['config']
283+
assert "Cannot find gear" in r.json()['message']
285284

286-
# try to add project rule w/ non-existent gear
285+
# try to add project rule w/ invalid config
287286
# NOTE this is a legacy rule
287+
rule_json['alg'] = gear_name
288+
rule_json['config'] = {'param': 'invalid'}
288289
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
289-
assert r.status_code == 400
290-
assert "Cannot find gear" in r.json()['message']
290+
assert r.status_code == 422
291+
assert r.json()['reason'] == 'config did not match manifest'
292+
del rule_json['config']
291293

292294
# add project rule w/ proper gear alg
293295
# NOTE this is a legacy rule
294-
rule_json['alg'] = gear_name
295296
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
296297
assert r.ok
297298
rule = r.json()['_id']
298299

299-
# get project rules (verify rule was added and uses default gear config)
300+
# get project rules
300301
r = as_admin.get('/projects/' + project + '/rules')
301302
assert r.ok
302303
assert r.json()[0]['alg'] == gear_name
303-
assert r.json()[0]['config'] == {'param': {'type': 'string'}}
304304

305305
# try to get single project rule using non-existent rule id
306306
r = as_admin.get('/projects/' + project + '/rules/000000000000000000000000')
@@ -323,8 +323,9 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
323323
assert r.status_code == 400
324324

325325
# try to update rule with invalid gear config
326-
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'config': {'foo': 'bar'}})
327-
assert r.status_code == 400
326+
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'config': {'param': 'invalid'}})
327+
assert r.status_code == 422
328+
assert r.json()['reason'] == 'config did not match manifest'
328329

329330
# update name of rule
330331
rule_name = 'improved-csv-trigger-rule'
@@ -340,11 +341,25 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
340341
r = as_admin.post('/projects/' + project + '/files', files=file_form('test2.csv'))
341342
assert r.ok
342343

343-
# test that job was created via rule
344+
# test that job was created via rule and uses gear default config
344345
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear_2})]
345346
assert len(gear_jobs) == 1
346347
assert len(gear_jobs[0]['inputs']) == 1
347348
assert gear_jobs[0]['inputs'][0]['name'] == 'test2.csv'
349+
assert gear_jobs[0]['config']['config'] == {'param': 'default'}
350+
351+
# update rule to have a custom config
352+
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'config': {'param': 'custom'}})
353+
assert r.ok
354+
355+
# upload another file that matches rule
356+
r = as_admin.post('/projects/' + project + '/files', files=file_form('test3.csv'))
357+
assert r.ok
358+
359+
# test that job was created via rule and custom config
360+
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear_2})]
361+
assert len(gear_jobs) == 2
362+
assert gear_jobs[1]['config']['config'] == {'param': 'custom'}
348363

349364
# try to delete rule of non-existent project
350365
r = as_admin.delete('/projects/000000000000000000000000/rules/000000000000000000000000')
@@ -385,7 +400,7 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
385400

386401
# test that job was not created via rule
387402
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear_2})]
388-
assert len(gear_jobs) == 1 # still 1 from before
403+
assert len(gear_jobs) == 2 # still 2 from before
389404

390405
# update test2.csv's metadata to include a valid measurement to spawn job
391406
metadata = {
@@ -413,9 +428,9 @@ def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with
413428

414429
# test that only one job was created via rule
415430
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear_2})]
416-
assert len(gear_jobs) == 2
417-
assert len(gear_jobs[1]['inputs']) == 1
418-
assert gear_jobs[1]['inputs'][0]['name'] == 'test3.txt'
431+
assert len(gear_jobs) == 3
432+
assert len(gear_jobs[2]['inputs']) == 1
433+
assert gear_jobs[2]['inputs'][0]['name'] == 'test3.txt'
419434

420435
# delete rule
421436
r = as_admin.delete('/projects/' + project + '/rules/' + rule2)

0 commit comments

Comments
 (0)