Repository: virtool/virtool
Issue: Backend validation of job resource settings values

Validate settings update requests for the following:
- values in **Resources Limits** section exceed real limits
- values in **Task-specific Limits** section exceed values in **Resource Limits** section

Changed files:
virtool/app_settings.py, virtool/handlers/settings.py

diff --git a/virtool/app_settings.py b/virtool/app_settings.py
index 6bc4540c4..58bcbdcb1 100644
--- a/virtool/app_settings.py
+++ b/virtool/app_settings.py
@@ -80,8 +80,8 @@ SCHEMA = {
 
 
     # Github
-    "github_token": {"type": "string", "default": None, "nullable": True},
-    "github_username": {"type": "string", "default": None, "nullable": True},
+    "github_token": {"type": "string", "default": ""},
+    "github_username": {"type": "string", "default": ""},
     "software_channel": {"type": "string", "default": "stable", "allowed": ["stable", "alpha", "beta"]},
 
     # Accounts
@@ -94,13 +94,26 @@ SCHEMA = {
 
     # Virus settings
     "restrict_source_types": get_default_boolean(True),
-    "allowed_source_types": {"type": "list", "default": ["clone", "culture", "genotype", "isolate", "strain"]},
+    "allowed_source_types": {"type": "list", "default": ["isolate", "strain"]},
 
     # Analysis settings
     "use_internal_control": get_default_boolean(False),
     "internal_control_id": {"type": "string", "default": ""}
 }
 
+TASK_SPECIFIC_LIMIT_KEYS = [
+    "create_sample_mem",
+    "create_sample_proc",
+    "create_subtraction_proc",
+    "create_subtraction_mem",
+    "nuvs_proc",
+    "nuvs_mem",
+    "pathoscope_bowtie_proc",
+    "pathoscope_bowtie_mem",
+    "rebuild_index_proc",
+    "rebuild_index_mem"
+]
+
 
 async def write_settings_file(path, settings_dict):
     validated = Settings.validate(settings_dict)
diff --git a/virtool/handlers/settings.py b/virtool/handlers/settings.py
index 8c2c85aea..d62d59406 100644
--- a/virtool/handlers/settings.py
+++ b/virtool/handlers/settings.py
@@ -4,8 +4,9 @@ import os
 from cerberus import Validator
 
 import virtool.app_settings
+import virtool.job_resources
 import virtool.utils
-from virtool.handlers.utils import invalid_input, json_response, not_found, protected, validation
+from virtool.handlers.utils import conflict, invalid_input, json_response, not_found, protected, validation
 
 
 async def get_all(req):
@@ -23,27 +24,59 @@ async def get_one(req):
 
 
 @protected("modify_settings")
+@validation(virtool.app_settings.SCHEMA)
 async def update(req):
     """
     Update application settings based on request data.
 
     """
-    data = await req.json()
+    raw_data = await req.json()
+    data = {key: req["data"][key] for key in raw_data}
 
-    keys = data.keys()
+    proc = data.get("proc", False)
+    mem = data.get("mem", False)
 
-    v = Validator(virtool.app_settings.SCHEMA)
+    settings = req.app["settings"].data
 
-    if not v(data):
-        return invalid_input(v.errors)
+    if proc or mem:
 
-    settings = req.app["settings"]
+        resources = virtool.job_resources.get()
+
+        if proc:
+            if proc > len(resources["proc"]):
+                return conflict("Exceeds system processor count")
+
+            task_proc = max(settings[key] for key in virtool.app_settings.TASK_SPECIFIC_LIMIT_KEYS if "_proc" in key)
+
+            if proc < task_proc:
+                return conflict("Less than a task-specific proc limit")
+
+        if mem:
+            if mem > resources["mem"]["total"] / 1000000000:
+                return conflict("Exceeds system memory")
+
+            task_mem = max(settings[key] for key in virtool.app_settings.TASK_SPECIFIC_LIMIT_KEYS if "_mem" in key)
+
+            if mem < task_mem:
+                return conflict("Less than a task-specific mem limit")
+
+    for key in virtool.app_settings.TASK_SPECIFIC_LIMIT_KEYS:
+        if key in data:
+            value = data[key]
+
+            if "_proc" in key and value > proc:
+                return conflict("Exceeds proc resource limit")
+
+            if "_mem" in key and value > mem:
+                return conflict("Exceeds mem resource specific limit")
+
+    app_settings = req.app["settings"]
 
-    settings.data.update({key: v.document[key] for key in keys})
+    app_settings.update(data)
 
-    await settings.write()
+    await app_settings.write()
 
-    return json_response(settings.data)
+    return json_response(app_settings.data)
 
 
 async def check_proxy(req):