From c59b006f5cd4b6508f605c0f1723bc59e5a1cc0d Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 9 Aug 2022 14:08:07 +0800 Subject: [PATCH 01/61] Adds submission_methods REST endpoint to fetch submission method from datamanager. Adds associated client routine. Adds associated rest test. --- daliuge-common/dlg/clients.py | 3 +++ daliuge-engine/dlg/manager/rest.py | 5 +++++ daliuge-engine/test/manager/test_rest.py | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/daliuge-common/dlg/clients.py b/daliuge-common/dlg/clients.py index ed9f2bb82..e19c18305 100644 --- a/daliuge-common/dlg/clients.py +++ b/daliuge-common/dlg/clients.py @@ -240,6 +240,9 @@ def shutdown_node_manager(self): def get_log_file(self, sessionId): return self._request(f"/sessions/{sessionId}/logs", "GET") + def get_submission_method(self): + return self._get_json("/submission_method") + class CompositeManagerClient(BaseDROPManagerClient): def nodes(self): diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index 2e8454f96..6002b5a2f 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -145,6 +145,7 @@ def __init__(self, dm, maxreqsize=10): # Mappings app = self.app + app.get("/api/submission_method", callback=self.submit_methods) app.post("/api/stop", callback=self.stop_manager) app.post("/api/sessions", callback=self.createSession) app.get("/api/sessions", callback=self.getSessions) @@ -187,6 +188,10 @@ def initializeSpecifics(self, app): The default implementation does nothing. """ + @daliuge_aware + def submit_methods(self): + return {"methods": ["REST"]} + def _stop_manager(self): self.dm.shutdown() self.stop() diff --git a/daliuge-engine/test/manager/test_rest.py b/daliuge-engine/test/manager/test_rest.py index 1921d53fc..492596cb3 100644 --- a/daliuge-engine/test/manager/test_rest.py +++ b/daliuge-engine/test/manager/test_rest.py @@ -264,3 +264,8 @@ def test_reprostatus_get(self): response = c.session_repro_status(sid) self.assertTrue(response) c.destroySession(sid) + + def test_submit_method(self): + c = NodeManagerClient(hostname) + response = c.get_submission_method() + self.assertEqual(response, {"methods": ["REST"]}) From 2c6ed82d5c81a483e7139f342549662259ae568b Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 9 Aug 2022 16:14:11 +0800 Subject: [PATCH 02/61] Adds submission_method endpoint to lg_web. Abstracts request url parsing to separate function. Adds associated lg_web test. Adds GET submission_method endpoint to CompositeManagerClient --- daliuge-common/dlg/clients.py | 3 + daliuge-translator/dlg/dropmake/web/lg_web.py | 90 ++++++++++++++----- .../test/dropmake/test_lgweb.py | 7 ++ 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/daliuge-common/dlg/clients.py b/daliuge-common/dlg/clients.py index e19c18305..6d79da41e 100644 --- a/daliuge-common/dlg/clients.py +++ b/daliuge-common/dlg/clients.py @@ -254,6 +254,9 @@ def add_node(self, node): def remove_node(self, node): self._DELETE(f"/nodes/{node}") + def get_submission_method(self): + return self._get_json("/submission_method") + class DataIslandManagerClient(CompositeManagerClient): """ diff --git a/daliuge-translator/dlg/dropmake/web/lg_web.py b/daliuge-translator/dlg/dropmake/web/lg_web.py index cac9edc27..ab50f4ecc 100644 --- a/daliuge-translator/dlg/dropmake/web/lg_web.py +++ b/daliuge-translator/dlg/dropmake/web/lg_web.py @@ -26,7 +26,9 @@ import logging import optparse import os +import re import signal +import subprocess import sys import threading import time @@ -141,6 +143,28 @@ def _repo_contents(root_dir): return contents +def _check_k8s_avail() -> bool: + """ + Checks if this process has access to a k8s interface. + """ + try: + output = subprocess.run( + ["kubectl version"], capture_output=True, shell=True + ).stdout + pattern = re.compile(r"^Client Version:.*\nServer Version:.*") + return bool(re.match(pattern, output.decode(encoding="utf-8"))) + except subprocess.SubprocessError: + return False + + +def _check_mgr_avail(mhost, mport, mprefix): + mgr_client = CompositeManagerClient( + host=mhost, port=mport, url_prefix=mprefix, timeout=15 + ) + response = mgr_client.get_submission_method() + return response + + @route("/static/") def server_static(filepath): staticRoot = pkg_resources.resource_filename( @@ -317,6 +341,7 @@ def gen_pg_helm(): RESTful interface to deploy a PGT as a K8s helm chart. """ # Get pgt_data + # TODO: @pritchardn de-couple engine from translator - this is dirty from ...deploy.start_helm_cluster import start_helm pgt_id = request.query.get("pgt_id") @@ -342,6 +367,31 @@ def gen_pg_helm(): return "Inspect your k8s dashboard for deployment status" +def parse_mgr_url(query): + mhost = "" + mport = -1 + mprefix = "" + if "dlg_mgr_url" in query: + murl = query["dlg_mgr_url"][0] + mparse = urlparse(murl) + try: + (mhost, mport) = mparse.netloc.split(":") + mport = int(mport) + except: + mhost = mparse.netloc + if mparse.scheme == "http": + mport = 80 + elif mparse.scheme == "https": + mport = 443 + mprefix = mparse.path + if mprefix.endswith("/"): + mprefix = mprefix[:-1] + else: + mhost = request.query.get("dlg_mgr_host") + if request.query.get("dlg_mgr_port"): + mport = int(request.query.get("dlg_mgr_port")) + return mhost, mport, mprefix + @get("/gen_pg") def gen_pg(): """ @@ -365,30 +415,9 @@ def gen_pg(): num_partitions = 0 num_partitions = len(list(filter(lambda n: "isGroup" in n, pgtpj["nodeDataArray"]))) surl = urlparse(request.url) - - mhost = "" - mport = -1 - mprefix = "" q = parse_qs(surl.query) - if "dlg_mgr_url" in q: - murl = q["dlg_mgr_url"][0] - mparse = urlparse(murl) - try: - (mhost, mport) = mparse.netloc.split(":") - mport = int(mport) - except: - mhost = mparse.netloc - if mparse.scheme == "http": - mport = 80 - elif mparse.scheme == "https": - mport = 443 - mprefix = mparse.path - if mprefix.endswith("/"): - mprefix = mprefix[:-1] - else: - mhost = request.query.get("dlg_mgr_host") - if request.query.get("dlg_mgr_port"): - mport = int(request.query.get("dlg_mgr_port")) + + mhost, mport, mprefix = parse_mgr_url(q) logger.debug("Manager host: %s", mhost) logger.debug("Manager port: %s", mport) @@ -612,6 +641,21 @@ def gen_pgt_post(): # return "Graph partition exception {1}: {0}".format(trace_msg, lg_name) +@route("/submission_method", method="GET") +def get_submission_method(): + surl = urlparse(request.url) + query = parse_qs(surl.query) + mhost, mport, mprefix = parse_mgr_url(query) + # return {'mhost': mhost, 'mport': mport, 'mprefix': mprefix} + available_methods = [] + if _check_k8s_avail(): + available_methods.append("HELM") + if mhost is not None: + if _check_mgr_avail(mhost, mport, mprefix): + available_methods.extend(["SERVER"]) + return {"methods": available_methods} + + def unroll_and_partition_with_params(lg, algo_params_source): # Get the 'test' parameter # NB: the test parameter is a string, so convert to boolean diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 700e92b02..d942a0fca 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -284,3 +284,10 @@ def test_show_schedule_mat(self): def test_get_gantt_chart(self): self._test_pgt_action("pgt_gantt_chart", True) + + def test_get_submission_methods(self): + import json + c = RestClient("localhost", lgweb_port, timeout=10) + response = c._GET("/submission_method") + response_content = json.load(response) + self.assertEqual(response_content, {'methods': []}) From d19352379bcbd4eeee2f9aba1f808a5d7bacd0c7 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 11 Aug 2022 15:32:33 +0800 Subject: [PATCH 03/61] CORS for managers now includes 127.0.0.1 and localhost options. --- daliuge-engine/dlg/manager/rest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index 6002b5a2f..a63ff84b3 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -30,6 +30,7 @@ import json import logging import os +import re import tarfile import threading @@ -75,9 +76,12 @@ def fwrapper(*args, **kwargs): if res is not None: bottle.response.content_type = "application/json" # set CORS headers + origin = bottle.request.headers.raw("Origin") + if not re.match(r"http://((localhost)|(127.0.0.1)):80[0-9][0-9]", origin): + origin = "http://localhost:8084" bottle.response.headers[ "Access-Control-Allow-Origin" - ] = "http://localhost:8084" + ] = origin bottle.response.headers["Access-Control-Allow-Credentials"] = "true" bottle.response.headers[ "Access-Control-Allow-Methods" From 164b0181a4bec0089cb36d17a5274f568243a9e1 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 11 Aug 2022 15:43:30 +0800 Subject: [PATCH 04/61] Adds (very permissive) CORS decorator for /api/submission method. Updates associated test. --- daliuge-translator/dlg/dropmake/web/lg_web.py | 18 +++++++++++++++++- daliuge-translator/test/dropmake/test_lgweb.py | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/lg_web.py b/daliuge-translator/dlg/dropmake/web/lg_web.py index ab50f4ecc..acdfb7400 100644 --- a/daliuge-translator/dlg/dropmake/web/lg_web.py +++ b/daliuge-translator/dlg/dropmake/web/lg_web.py @@ -165,6 +165,20 @@ def _check_mgr_avail(mhost, mport, mprefix): return response +def enable_cors(fn): + def _enable_cors(*args, **kwargs): + # set CORS headers + response.headers['Access-Control-Allow-Origin'] = '*' # TODO: change to be restrictive + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' + + if bottle.request.method != 'OPTIONS': + # actual request; reply with the actual response + return fn(*args, **kwargs) + + return _enable_cors + + @route("/static/") def server_static(filepath): staticRoot = pkg_resources.resource_filename( @@ -641,8 +655,10 @@ def gen_pgt_post(): # return "Graph partition exception {1}: {0}".format(trace_msg, lg_name) -@route("/submission_method", method="GET") +@route("/api/submission_method", method="GET") +@enable_cors def get_submission_method(): + logger.debug("Received submission_method request") surl = urlparse(request.url) query = parse_qs(surl.query) mhost, mport, mprefix = parse_mgr_url(query) diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index d942a0fca..4d387a4b9 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -288,6 +288,6 @@ def test_get_gantt_chart(self): def test_get_submission_methods(self): import json c = RestClient("localhost", lgweb_port, timeout=10) - response = c._GET("/submission_method") + response = c._GET("/api/submission_method") response_content = json.load(response) self.assertEqual(response_content, {'methods': []}) From 0761706ef7cdd5f388399f03ddc0f5677e1198d8 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 11 Aug 2022 15:44:23 +0800 Subject: [PATCH 05/61] Beginning to implement method requesting. --- daliuge-translator/dlg/dropmake/web/main.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 8090f3b24..5976efded 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -169,6 +169,20 @@ async function checkUrlStatus (url) { }) } +async function checkUrlSubmissionMethods(url) { + return new Promise((resolve, reject) => { + $.ajax({ + url: url, + type: 'GET', + success: function( response ) { + resolve(response) + }, + timeout: 2000 + }); + }) +} + + async function manualCheckUrlStatus (clickPos) { var badUrl = false //if the event is triggered by click the check icon manually @@ -326,6 +340,8 @@ function fillOutSettings() { deployMethodsArray.forEach(async element => { var urlReachable = await checkUrlStatus(element.url) + var availableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method') + console.log(availableMethods); var ReachableIcon = "" if(urlReachable){ From ed4e0727c84f589e1d7e7ead79b58d5da1c5f201 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 16 Aug 2022 10:42:53 +0800 Subject: [PATCH 06/61] Changes LG_web submission_method logic to only return "SERVER" if it can reach the provided mgr_url --- daliuge-translator/dlg/dropmake/web/lg_web.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/lg_web.py b/daliuge-translator/dlg/dropmake/web/lg_web.py index acdfb7400..dcc712663 100644 --- a/daliuge-translator/dlg/dropmake/web/lg_web.py +++ b/daliuge-translator/dlg/dropmake/web/lg_web.py @@ -667,8 +667,10 @@ def get_submission_method(): if _check_k8s_avail(): available_methods.append("HELM") if mhost is not None: - if _check_mgr_avail(mhost, mport, mprefix): - available_methods.extend(["SERVER"]) + host_available_methods = _check_mgr_avail(mhost, mport, mprefix) + if host_available_methods: + if "BROWSER" in host_available_methods["methods"]: + available_methods.extend(["SERVER"]) return {"methods": available_methods} From 17fc5eb2b53d52aac14fcaa2461388ee5b065a91 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 16 Aug 2022 10:43:22 +0800 Subject: [PATCH 07/61] DIMs now return "BROWSER" as their deploymenet method - implying they can be contacted directly. --- daliuge-engine/dlg/manager/rest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index a63ff84b3..f4ecf5858 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -77,7 +77,9 @@ def fwrapper(*args, **kwargs): bottle.response.content_type = "application/json" # set CORS headers origin = bottle.request.headers.raw("Origin") - if not re.match(r"http://((localhost)|(127.0.0.1)):80[0-9][0-9]", origin): + if origin is None: + origin = "http://localhost:8084" + elif not re.match(r"http://((localhost)|(127.0.0.1)):80[0-9][0-9]", origin): origin = "http://localhost:8084" bottle.response.headers[ "Access-Control-Allow-Origin" @@ -194,7 +196,7 @@ def initializeSpecifics(self, app): @daliuge_aware def submit_methods(self): - return {"methods": ["REST"]} + return {"methods": ["BROWSER"]} def _stop_manager(self): self.dm.shutdown() From 297924a33c05a7744e792b2af3d08e75fcadac0a Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 16 Aug 2022 10:45:08 +0800 Subject: [PATCH 08/61] Adds basic implementation of automatic method filling. Upon opening the settings modal, the fields are populated after pinging each listed method. The first available method is always selected. Adding new methods is slightly problematic, one must come back into the setting modal to populate the fields - building it on the fly seems to be a chunk more work. --- daliuge-translator/dlg/dropmake/web/main.js | 84 +++++++++++++-------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 5976efded..5cf578667 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -55,6 +55,13 @@ $(document).ready(function () { }) }); +function getCurrentPageUrl(){ + const pathElements = window.location.href.split('/'); + const protocol = pathElements[0]; + const host = pathElements[2]; + return protocol + '//' + host; +} + function openSettingsModal(){ //needed for the dropdown option to open the settings modal, the pure bootstrap method used on the settings gear button proved inconsistent $('#settingsModal').modal("show") @@ -304,6 +311,34 @@ function saveSettings() { updateDeployOptionsDropdown() } +function buildDeployMethodEntry(method, selected){ + let optionValue = ""; + let displayValue = ""; + switch(method){ + case "SERVER": + optionValue = "direct"; + displayValue = "Direct"; + break; + case "BROWSER": + optionValue = "rest-direct"; + displayValue = "Rest-Direct"; + break; + case "OOD": + optionValue = "rest-ood"; + displayValue = "Rest-OOD"; + break; + case "HELM": + optionValue = "helm"; + displayValue = "Helm"; + break; + } + if(selected){ + return `` + } else { + return `` + } +} + function fillOutSettings() { //get setting values from local storage var manager_url = window.localStorage.getItem("manager_url"); @@ -339,9 +374,12 @@ function fillOutSettings() { console.log("filling out settings, GET Errors and Cors warning from Url check") deployMethodsArray.forEach(async element => { + let i; var urlReachable = await checkUrlStatus(element.url) - var availableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method') - console.log(availableMethods); + var directlyAvailableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method') + var translatorAvailableMethods = await checkUrlSubmissionMethods(getCurrentPageUrl() + '/api/submission_method?dlg_mgr_url=' + element.url); + console.log(directlyAvailableMethods); + console.log(translatorAvailableMethods); var ReachableIcon = "" if(urlReachable){ @@ -350,30 +388,22 @@ function fillOutSettings() { ReachableIcon = `
close
` } - var directOption = '' - var helmOption = '' - var restOODOption = '' - var restDirectOption = '' - - if(element.deployMethod === "direct"){ - directOption = '' - }else if(element.deployMethod === "helm"){ - helmOption = '' - }else if(element.deployMethod === "rest-ood"){ - restOODOption = '' - } else if(element.deployMethod === "rest-direct"){ - restDirectOption = '' + const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); + var availableOptions = []; + console.log(allAvailableMethods); + for (i = 0; i < allAvailableMethods.length; i++) { + const deploy_option = allAvailableMethods[i]; + availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)) } - var deplpoyMethodRow = '
'+ '
'+ `
`+ ReachableIcon+ - '
' + for(i = 0; i < availableOptions.length; i++){ + deplpoyMethodRow += availableOptions[i] + } + deplpoyMethodRow += '
'+ ''+ ''+ @@ -384,21 +414,11 @@ function fillOutSettings() { function addDeployMethod(){ var deployMethodManagerDiv = $("#DeployMethodManager") - - var directOption = '' - var helmOption = '' - var restOODOption = '' - var restDirectOption = '' - var deplpoyMethodRow = '
'+ '
'+ - `
`+ + `
`+ ``+ '
'+ ''+ ''+ From 29f655268bdca160d23772003661c0c8358eef4c Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 16 Aug 2022 11:10:57 +0800 Subject: [PATCH 09/61] Changes deployMethod values to match what comes from the submission_method api. Now stores user deployment choice made from available choices. Fixes a small logic bug in doing so. --- daliuge-translator/dlg/dropmake/web/main.js | 28 +++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 5cf578667..31377f2be 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -87,17 +87,17 @@ async function initiateDeploy(method, selected, clickedName){ }); return } - if(method === "direct"){ + if(method === "SERVER"){ $("#gen_pg_button").val("Generate & Deploy Physical Graph") $("#dlg_mgr_deploy").prop("checked", true) $("#pg_form").submit(); - }else if(method === "helm"){ + }else if(method === "HELM"){ $("#gen_helm_button").val("Generate & Deploy Physical Graph") $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() - }else if(method === "rest-ood"){ + }else if(method === "OOD"){ restDeploy() - } else if(method === "rest-direct"){ + } else if(method === "BROWSER"){ directRestDeploy() } } @@ -292,6 +292,7 @@ function saveSettings() { deployMethod : $(this).find(".deployMethodMethod option:selected").val(), active : $(this).find(".deployMethodActive").val() } + console.log($(this).find(".deployMethodMethod option:selected").val()) deployMethodsArray.push(deployMethod) } }) @@ -312,30 +313,25 @@ function saveSettings() { } function buildDeployMethodEntry(method, selected){ - let optionValue = ""; let displayValue = ""; switch(method){ case "SERVER": - optionValue = "direct"; displayValue = "Direct"; break; case "BROWSER": - optionValue = "rest-direct"; displayValue = "Rest-Direct"; break; case "OOD": - optionValue = "rest-ood"; displayValue = "Rest-OOD"; break; case "HELM": - optionValue = "helm"; displayValue = "Helm"; break; } if(selected){ - return `` + return `` } else { - return `` + return `` } } @@ -358,7 +354,7 @@ function fillOutSettings() { { name : "default deployment", url : "http://localhost:8001/", - deployMethod : "direct", + deployMethod : "SERVER", active : true } ] @@ -393,7 +389,13 @@ function fillOutSettings() { console.log(allAvailableMethods); for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; - availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)) + if(element.deployMethod !== "undefined"){ + // If a choice has already been made, go with that. + availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); + } else { + // By default, select the first listed. + availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); + } } var deplpoyMethodRow = '
'+ '
'+ From 03534793f18672ba502d30a5440bb68717a26817 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 19 Aug 2022 10:53:50 +0800 Subject: [PATCH 10/61] Changes helm match string. --- daliuge-engine/dlg/deploy/deployment_utils.py | 2 +- daliuge-translator/dlg/dropmake/web/lg_web.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/daliuge-engine/dlg/deploy/deployment_utils.py b/daliuge-engine/dlg/deploy/deployment_utils.py index 2d13ba1aa..24d193d55 100644 --- a/daliuge-engine/dlg/deploy/deployment_utils.py +++ b/daliuge-engine/dlg/deploy/deployment_utils.py @@ -118,7 +118,7 @@ def check_k8s_env(): output = subprocess.run( ["kubectl version"], capture_output=True, shell=True ).stdout - pattern = re.compile(r"^Client Version:.*\nServer Version:.*") + pattern = re.compile(r"^Client Version:.*Server Version:.*") return re.match(pattern, output.decode(encoding="utf-8")) except subprocess.SubprocessError: return False diff --git a/daliuge-translator/dlg/dropmake/web/lg_web.py b/daliuge-translator/dlg/dropmake/web/lg_web.py index dcc712663..1f71d682e 100644 --- a/daliuge-translator/dlg/dropmake/web/lg_web.py +++ b/daliuge-translator/dlg/dropmake/web/lg_web.py @@ -151,8 +151,9 @@ def _check_k8s_avail() -> bool: output = subprocess.run( ["kubectl version"], capture_output=True, shell=True ).stdout - pattern = re.compile(r"^Client Version:.*\nServer Version:.*") - return bool(re.match(pattern, output.decode(encoding="utf-8"))) + output = output.decode(encoding="utf-8").replace("\n", "") + pattern = re.compile(r"^Client Version:.*Server Version:.*") + return bool(re.match(pattern, output)) except subprocess.SubprocessError: return False From 0857b59314d88feb33fe6edf051ca0128b79d99c Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 19 Aug 2022 11:07:01 +0800 Subject: [PATCH 11/61] Fixing indentation of main.js --- daliuge-translator/dlg/dropmake/web/main.js | 249 +++++++++++--------- 1 file changed, 133 insertions(+), 116 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 31377f2be..7e01e1b12 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -25,28 +25,43 @@ $(document).ready(function () { //keyboard shortcuts var keyboardShortcuts = [] - keyboardShortcuts.push({name:"Open Settings", shortcut:"O", code:79, action: "$('#settingsModal').modal('toggle')"}) - keyboardShortcuts.push({name:"Deploy", shortcut:"D", code:75, action: "$('#shortcutsModal').modal('toggle')"}) - keyboardShortcuts.push({name:"Open Keyboardshortcuts Modal", shortcut:"K", code:68, action: "$('#activeDeployMethodButton').click()"}) + keyboardShortcuts.push({ + name: "Open Settings", + shortcut: "O", + code: 79, + action: "$('#settingsModal').modal('toggle')" + }) + keyboardShortcuts.push({ + name: "Deploy", + shortcut: "D", + code: 75, + action: "$('#shortcutsModal').modal('toggle')" + }) + keyboardShortcuts.push({ + name: "Open Keyboardshortcuts Modal", + shortcut: "K", + code: 68, + action: "$('#activeDeployMethodButton').click()" + }) //fill out keyboard shortcuts modal - keyboardShortcuts.forEach(element => { - var shortCutItem = '
'+ - '
'+ - ''+element.name+''+ - ''+element.shortcut+''+ - '
'+ - '
' + keyboardShortcuts.forEach(element => { + var shortCutItem = '
' + + '
' + + '' + element.name + '' + + '' + element.shortcut + '' + + '
' + + '
' $("#shortcutsModal .modal-body .row").append(shortCutItem) }) //keyboard shortcuts execution - $(document).keydown(function(e){ - if($("input").is(":focus")){ + $(document).keydown(function (e) { + if ($("input").is(":focus")) { return } - keyboardShortcuts.forEach(element => { - + keyboardShortcuts.forEach(element => { + if (e.which == element.code) //open settings modal on o { eval(element.action) @@ -55,60 +70,60 @@ $(document).ready(function () { }) }); -function getCurrentPageUrl(){ +function getCurrentPageUrl() { const pathElements = window.location.href.split('/'); const protocol = pathElements[0]; const host = pathElements[2]; return protocol + '//' + host; } -function openSettingsModal(){ +function openSettingsModal() { //needed for the dropdown option to open the settings modal, the pure bootstrap method used on the settings gear button proved inconsistent $('#settingsModal').modal("show") } -async function initiateDeploy(method, selected, clickedName){ +async function initiateDeploy(method, selected, clickedName) { var clickedUrl JSON.parse(window.localStorage.getItem("deployMethods")).forEach(element => { - if(element.name === clickedName){ + if (element.name === clickedName) { clickedUrl = element.url } }) - if (selected === false){ + if (selected === false) { await changeSelectedDeployMethod(clickedName, clickedUrl) } var activeUrlReachable = await checkUrlStatus(clickedUrl) - if(!activeUrlReachable){ - $("#warning-alert").fadeTo(2000, 1000).slideUp(200, function() { + if (!activeUrlReachable) { + $("#warning-alert").fadeTo(2000, 1000).slideUp(200, function () { $("#warning-alert").slideUp(200); }); return } - if(method === "SERVER"){ + if (method === "SERVER") { $("#gen_pg_button").val("Generate & Deploy Physical Graph") $("#dlg_mgr_deploy").prop("checked", true) $("#pg_form").submit(); - }else if(method === "HELM"){ + } else if (method === "HELM") { $("#gen_helm_button").val("Generate & Deploy Physical Graph") $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() - }else if(method === "OOD"){ + } else if (method === "OOD") { restDeploy() - } else if(method === "BROWSER"){ + } else if (method === "BROWSER") { directRestDeploy() } } -async function changeSelectedDeployMethod(name,manager_url) { +async function changeSelectedDeployMethod(name, manager_url) { return new Promise((resolve, reject) => { var deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")) $("#managerUrlInput").val(manager_url); deployMethodsArray.forEach(element => { element.active = "false" - if(element.name === name){ + if (element.name === name) { element.active = "true" } }) @@ -125,16 +140,16 @@ function updateDeployOptionsDropdown() { //add deployment options JSON.parse(localStorage.getItem("deployMethods")).forEach(element => { - if(element.active === "false"){ + if (element.active === "false") { //dropdown options $("#deployDropdowns .dropdown-menu").prepend( - ``+element.name+`` + `` + element.name + `` ) - }else { - selectedUrl=element.url + } else { + selectedUrl = element.url //active option $("#deployDropdowns").prepend( - `Deploy: `+element.name+`` + `Deploy: ` + element.name + `` ) checkActiveDeployMethod(selectedUrl) } @@ -145,7 +160,7 @@ function updateDeployOptionsDropdown() { var newHost = newUrl.hostname; var newPrefix = newUrl.pathname; var newProtocol = newUrl.protocol; - console.log("URL set to:'" + newUrl + "'"); + console.log("URL set to:'" + newUrl + "'"); console.log("Protocol set to:'" + newProtocol + "'"); console.log("Host set to:'" + newHost + "'"); console.log("Port set to:'" + newPort + "'"); @@ -159,16 +174,17 @@ function updateDeployOptionsDropdown() { } -async function checkUrlStatus (url) { +async function checkUrlStatus(url) { return new Promise((resolve, reject) => { - $.ajax({url: url, + $.ajax({ + url: url, type: 'HEAD', dataType: 'jsonp', - complete: function(jqXHR, textStatus){ - if(jqXHR.status === 200){ - resolve(true) - }else{ - resolve(false) + complete: function (jqXHR, textStatus) { + if (jqXHR.status === 200) { + resolve(true) + } else { + resolve(false) } }, timeout: 2000 @@ -181,7 +197,7 @@ async function checkUrlSubmissionMethods(url) { $.ajax({ url: url, type: 'GET', - success: function( response ) { + success: function (response) { resolve(response) }, timeout: 2000 @@ -190,18 +206,18 @@ async function checkUrlSubmissionMethods(url) { } -async function manualCheckUrlStatus (clickPos) { +async function manualCheckUrlStatus(clickPos) { var badUrl = false //if the event is triggered by click the check icon manually - if(clickPos === "icon"){ + if (clickPos === "icon") { var url = $(event.target).parent().find($('.deployMethodUrl')).val() var target = $(event.target) - }else{ + } else { //if the event is triggered by focus leave on the input var url = $(event.target).parent().parent().find($('.deployMethodUrl')).val() var target = $(event.target).parent().parent().find($('.urlStatusIcon')) } - + //check if jquery deems the url constructed properly try { @@ -210,30 +226,30 @@ async function manualCheckUrlStatus (clickPos) { badUrl = true } - if(badUrl){ + if (badUrl) { $("#settingsModalErrorMessage").html('Please ensure deploy methods URLs are valid') return - }else{ + } else { //if the url is deemed contructed well, here we use an ajax call to see if the url is reachable var urlStatus = await checkUrlStatus(url) $("#settingsModalErrorMessage").html('') - if(urlStatus){ + if (urlStatus) { target.replaceWith(`
done
`) - }else{ + } else { target.replaceWith(`
close
`) - } + } } } -async function checkActiveDeployMethod(url){ +async function checkActiveDeployMethod(url) { $("#activeDeployMethodButton").removeClass("activeDeployMethodButtonOnline") $("#activeDeployMethodButton").removeClass("activeDeployMethodButtonOffline") var urlStatus = await checkUrlStatus(url) - if(urlStatus){ + if (urlStatus) { $("#activeDeployMethodButton").addClass("activeDeployMethodButtonOnline") - }else{ + } else { $("#activeDeployMethodButton").addClass("activeDeployMethodButtonOffline") - } + } } function saveSettings() { @@ -241,66 +257,66 @@ function saveSettings() { var settingsDeployMethods = $("#DeployMethodManager .input-group")//deploy method rows selector var deployMethodsArray = []//temp array of deploy method rows values - + //errors var errorFillingOut = false var duplicateName = false var emptyName = false var badUrl = false - settingsDeployMethods.each(function(){ + settingsDeployMethods.each(function () { //error detection - if($(this).find(".deployMethodName").val().trim() === ""){ + if ($(this).find(".deployMethodName").val().trim() === "") { emptyName = true } try { new URL($(this).find(".deployMethodUrl").val()); - } catch (error) { - console.log("faulty Url: ",$(this).find(".deployMethodUrl").val()) - badUrl = true - } + } catch (error) { + console.log("faulty Url: ", $(this).find(".deployMethodUrl").val()) + badUrl = true + } //duplicate name check, the name is used as an id of sorts deployMethodsArray.forEach(element => { - if ($(this).find(".deployMethodName").val() === element.name){ + if ($(this).find(".deployMethodName").val() === element.name) { duplicateName = true return } }) //error Handling - if(duplicateName){ + if (duplicateName) { errorFillingOut = true; $("#settingsModalErrorMessage").html('Please ensure there are no duplicate deploy method names') } - if(emptyName){ + if (emptyName) { errorFillingOut = true; $("#settingsModalErrorMessage").html('Please ensure deploy methods are named') } - if(badUrl){ + if (badUrl) { errorFillingOut = true; $("#settingsModalErrorMessage").html('Please ensure deploy methods URLs are valid') } - if(!errorFillingOut){ - deployMethod = - { - name : $(this).find(".deployMethodName").val(), - url : $(this).find(".deployMethodUrl").val(), - deployMethod : $(this).find(".deployMethodMethod option:selected").val(), - active : $(this).find(".deployMethodActive").val() - } + if (!errorFillingOut) { + deployMethod = + { + name: $(this).find(".deployMethodName").val(), + url: $(this).find(".deployMethodUrl").val(), + deployMethod: $(this).find(".deployMethodMethod option:selected").val(), + active: $(this).find(".deployMethodActive").val() + } console.log($(this).find(".deployMethodMethod option:selected").val()) deployMethodsArray.push(deployMethod) - } + } }) //if errors in previous step abort saving - if(errorFillingOut){ + if (errorFillingOut) { return; - }else{ + } else { $("#settingsModalErrorMessage").html('') } //save to local storage @@ -312,9 +328,9 @@ function saveSettings() { updateDeployOptionsDropdown() } -function buildDeployMethodEntry(method, selected){ +function buildDeployMethodEntry(method, selected) { let displayValue = ""; - switch(method){ + switch (method) { case "SERVER": displayValue = "Direct"; break; @@ -328,7 +344,7 @@ function buildDeployMethodEntry(method, selected){ displayValue = "Helm"; break; } - if(selected){ + if (selected) { return `` } else { return `` @@ -349,17 +365,17 @@ function fillOutSettings() { } //setting up initial default deploy method - if(!localStorage.getItem("deployMethods")){ + if (!localStorage.getItem("deployMethods")) { var deployMethodsArray = [ { - name : "default deployment", - url : "http://localhost:8001/", - deployMethod : "SERVER", - active : true + name: "default deployment", + url: "http://localhost:8001/", + deployMethod: "SERVER", + active: true } ] localStorage.setItem('deployMethods', JSON.stringify(deployMethodsArray)) - }else{ + } else { //get deploy methods from local storage var deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")) } @@ -377,10 +393,10 @@ function fillOutSettings() { console.log(directlyAvailableMethods); console.log(translatorAvailableMethods); var ReachableIcon = "" - - if(urlReachable){ + + if (urlReachable) { ReachableIcon = `
done
` - }else{ + } else { ReachableIcon = `
close
` } @@ -389,7 +405,7 @@ function fillOutSettings() { console.log(allAvailableMethods); for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; - if(element.deployMethod !== "undefined"){ + if (element.deployMethod !== "undefined") { // If a choice has already been made, go with that. availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); } else { @@ -397,38 +413,38 @@ function fillOutSettings() { availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); } } - var deplpoyMethodRow = '
'+ - '
'+ - `
`+ - ReachableIcon+ - '
' + + `
` + + ReachableIcon + + '
'+ - ''+ - ''+ - '
' + '
' + + '' + + '' + + '
' deployMethodManagerDiv.append(deplpoyMethodRow) }); } -function addDeployMethod(){ +function addDeployMethod() { var deployMethodManagerDiv = $("#DeployMethodManager") - var deplpoyMethodRow = '
'+ - '
'+ - `
`+ - ``+ - '
'+ - ''+ - ''+ - '
' + var deplpoyMethodRow = '
' + + '
' + + `
` + + `` + + '
' + + '' + + '' + + '
' deployMethodManagerDiv.append(deplpoyMethodRow) } -function removeDeployMethod (e){ +function removeDeployMethod(e) { $(e.target).parent().remove() } @@ -512,7 +528,7 @@ function handleFetchErrors(response) { return response; } -async function directRestDeploy(){ +async function directRestDeploy() { // fetch manager host and port from local storage murl = window.localStorage.getItem("manager_url"); if (!murl) { @@ -522,7 +538,8 @@ async function directRestDeploy(){ fillOutSettings() murl = window.localStorage.getItem("manager_url"); }) - }; + } + ; var manager_url = new URL(murl); console.log("In Direct REST Deploy"); @@ -558,13 +575,13 @@ async function directRestDeploy(){ }) .then(handleFetchErrors) .then(response => response.json()) - .catch(function (error){ + .catch(function (error) { showMessageModal('Error', error + "\nGetting Nodes unsuccessful"); }) console.log(nodes) const pgt_url = "/gen_pg?tpl_nodes_len=" + nodes.length.toString() + "&pgt_id=" + pgtName; - console.log("sending request to ", pgt_url); + console.log("sending request to ", pgt_url); console.log("graph name:", pgtName); const pgt = await fetch(pgt_url, { method: 'GET', @@ -676,7 +693,8 @@ async function restDeploy() { fillOutSettings() murl = window.localStorage.getItem("manager_url"); }) - }; + } + ; var manager_url = new URL(murl); console.log("In REST Deploy") @@ -754,7 +772,6 @@ async function restDeploy() { }); - // All the rest here is when the managers are actually running // TODO: need to enable this again. From f971f087a5931251bc79a92bc3d9407bcbedea56 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 19 Aug 2022 11:30:25 +0800 Subject: [PATCH 12/61] Minor adjustment of submission_method test. --- daliuge-engine/test/manager/test_rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-engine/test/manager/test_rest.py b/daliuge-engine/test/manager/test_rest.py index 492596cb3..076c6c742 100644 --- a/daliuge-engine/test/manager/test_rest.py +++ b/daliuge-engine/test/manager/test_rest.py @@ -268,4 +268,4 @@ def test_reprostatus_get(self): def test_submit_method(self): c = NodeManagerClient(hostname) response = c.get_submission_method() - self.assertEqual(response, {"methods": ["REST"]}) + self.assertEqual({"methods": ["BROWSER"]}, response) From 3defafa37356ad3f04ba8067c268d6626a903995 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 19 Aug 2022 11:30:33 +0800 Subject: [PATCH 13/61] Minor refactor of main.js --- daliuge-translator/dlg/dropmake/web/main.js | 259 ++++++-------------- 1 file changed, 70 insertions(+), 189 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 7e01e1b12..a1f0710f2 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -24,7 +24,7 @@ $(document).ready(function () { $("#aboutModal #aboutLicense").load("/static/license.html") //keyboard shortcuts - var keyboardShortcuts = [] + const keyboardShortcuts = []; keyboardShortcuts.push({ name: "Open Settings", shortcut: "O", @@ -46,12 +46,12 @@ $(document).ready(function () { //fill out keyboard shortcuts modal keyboardShortcuts.forEach(element => { - var shortCutItem = '
' + + const shortCutItem = '
' + '
' + '' + element.name + '' + '' + element.shortcut + '' + '
' + - '
' + '
'; $("#shortcutsModal .modal-body .row").append(shortCutItem) }) @@ -62,7 +62,7 @@ $(document).ready(function () { } keyboardShortcuts.forEach(element => { - if (e.which == element.code) //open settings modal on o + if (e.which === element.code) //open settings modal on o { eval(element.action) } @@ -83,7 +83,7 @@ function openSettingsModal() { } async function initiateDeploy(method, selected, clickedName) { - var clickedUrl + let clickedUrl; JSON.parse(window.localStorage.getItem("deployMethods")).forEach(element => { if (element.name === clickedName) { clickedUrl = element.url @@ -94,7 +94,7 @@ async function initiateDeploy(method, selected, clickedName) { await changeSelectedDeployMethod(clickedName, clickedUrl) } - var activeUrlReachable = await checkUrlStatus(clickedUrl) + const activeUrlReachable = await checkUrlStatus(clickedUrl); if (!activeUrlReachable) { $("#warning-alert").fadeTo(2000, 1000).slideUp(200, function () { @@ -111,15 +111,15 @@ async function initiateDeploy(method, selected, clickedName) { $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() } else if (method === "OOD") { - restDeploy() + await restDeploy() } else if (method === "BROWSER") { - directRestDeploy() + await directRestDeploy() } } async function changeSelectedDeployMethod(name, manager_url) { - return new Promise((resolve, reject) => { - var deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")) + return new Promise((resolve) => { + const deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")); $("#managerUrlInput").val(manager_url); deployMethodsArray.forEach(element => { element.active = "false" @@ -136,7 +136,7 @@ async function changeSelectedDeployMethod(name, manager_url) { function updateDeployOptionsDropdown() { //remove old options $(".deployMethodMenuItem").remove() - var selectedUrl + let selectedUrl; //add deployment options JSON.parse(localStorage.getItem("deployMethods")).forEach(element => { @@ -155,11 +155,11 @@ function updateDeployOptionsDropdown() { } }) - var newUrl = new URL(selectedUrl); - var newPort = newUrl.port; - var newHost = newUrl.hostname; - var newPrefix = newUrl.pathname; - var newProtocol = newUrl.protocol; + const newUrl = new URL(selectedUrl); + const newPort = newUrl.port; + const newHost = newUrl.hostname; + const newPrefix = newUrl.pathname; + const newProtocol = newUrl.protocol; console.log("URL set to:'" + newUrl + "'"); console.log("Protocol set to:'" + newProtocol + "'"); console.log("Host set to:'" + newHost + "'"); @@ -175,7 +175,7 @@ function updateDeployOptionsDropdown() { } async function checkUrlStatus(url) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { $.ajax({ url: url, type: 'HEAD', @@ -193,7 +193,7 @@ async function checkUrlStatus(url) { } async function checkUrlSubmissionMethods(url) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { $.ajax({ url: url, type: 'GET', @@ -207,15 +207,17 @@ async function checkUrlSubmissionMethods(url) { async function manualCheckUrlStatus(clickPos) { - var badUrl = false + let target; + let url; + let badUrl = false //if the event is triggered by click the check icon manually if (clickPos === "icon") { - var url = $(event.target).parent().find($('.deployMethodUrl')).val() - var target = $(event.target) + url = $(event.target).parent().find($('.deployMethodUrl')).val(); + target = $(event.target); } else { //if the event is triggered by focus leave on the input - var url = $(event.target).parent().parent().find($('.deployMethodUrl')).val() - var target = $(event.target).parent().parent().find($('.urlStatusIcon')) + url = $(event.target).parent().parent().find($('.deployMethodUrl')).val(); + target = $(event.target).parent().parent().find($('.urlStatusIcon')); } @@ -228,7 +230,6 @@ async function manualCheckUrlStatus(clickPos) { if (badUrl) { $("#settingsModalErrorMessage").html('Please ensure deploy methods URLs are valid') - return } else { //if the url is deemed contructed well, here we use an ajax call to see if the url is reachable var urlStatus = await checkUrlStatus(url) @@ -242,9 +243,10 @@ async function manualCheckUrlStatus(clickPos) { } async function checkActiveDeployMethod(url) { - $("#activeDeployMethodButton").removeClass("activeDeployMethodButtonOnline") - $("#activeDeployMethodButton").removeClass("activeDeployMethodButtonOffline") - var urlStatus = await checkUrlStatus(url) + let deployButton = $("#activeDeployMethodButton"); + deployButton.removeClass("activeDeployMethodButtonOnline") + deployButton.removeClass("activeDeployMethodButtonOffline") + const urlStatus = await checkUrlStatus(url); if (urlStatus) { $("#activeDeployMethodButton").addClass("activeDeployMethodButtonOnline") } else { @@ -255,14 +257,14 @@ async function checkActiveDeployMethod(url) { function saveSettings() { //need a check function to confirm settings have been filled out correctly - var settingsDeployMethods = $("#DeployMethodManager .input-group")//deploy method rows selector - var deployMethodsArray = []//temp array of deploy method rows values + const settingsDeployMethods = $("#DeployMethodManager .input-group");//deploy method rows selector + const deployMethodsArray = [];//temp array of deploy method rows values //errors - var errorFillingOut = false - var duplicateName = false - var emptyName = false - var badUrl = false + let errorFillingOut = false; + let duplicateName = false; + let emptyName = false; + let badUrl = false; settingsDeployMethods.each(function () { @@ -282,7 +284,6 @@ function saveSettings() { deployMethodsArray.forEach(element => { if ($(this).find(".deployMethodName").val() === element.name) { duplicateName = true - return } }) @@ -300,6 +301,7 @@ function saveSettings() { $("#settingsModalErrorMessage").html('Please ensure deploy methods URLs are valid') } + let deployMethod; if (!errorFillingOut) { deployMethod = { @@ -352,8 +354,9 @@ function buildDeployMethodEntry(method, selected) { } function fillOutSettings() { - //get setting values from local storage - var manager_url = window.localStorage.getItem("manager_url"); + let deployMethodsArray; +//get setting values from local storage + const manager_url = window.localStorage.getItem("manager_url"); $("#settingsModalErrorMessage").html('') @@ -366,33 +369,31 @@ function fillOutSettings() { //setting up initial default deploy method if (!localStorage.getItem("deployMethods")) { - var deployMethodsArray = [ + deployMethodsArray = [ { name: "default deployment", url: "http://localhost:8001/", deployMethod: "SERVER", active: true } - ] + ]; localStorage.setItem('deployMethods', JSON.stringify(deployMethodsArray)) } else { //get deploy methods from local storage - var deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")) + deployMethodsArray = JSON.parse(localStorage.getItem("deployMethods")); } //fill out settings list rom deploy methods array - var deployMethodManagerDiv = $("#DeployMethodManager") + const deployMethodManagerDiv = $("#DeployMethodManager"); deployMethodManagerDiv.empty() console.log("filling out settings, GET Errors and Cors warning from Url check") deployMethodsArray.forEach(async element => { let i; - var urlReachable = await checkUrlStatus(element.url) - var directlyAvailableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method') - var translatorAvailableMethods = await checkUrlSubmissionMethods(getCurrentPageUrl() + '/api/submission_method?dlg_mgr_url=' + element.url); - console.log(directlyAvailableMethods); - console.log(translatorAvailableMethods); - var ReachableIcon = "" + const urlReachable = await checkUrlStatus(element.url); + const directlyAvailableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method'); + const translatorAvailableMethods = await checkUrlSubmissionMethods(getCurrentPageUrl() + '/api/submission_method?dlg_mgr_url=' + element.url); + let ReachableIcon; if (urlReachable) { ReachableIcon = `
done
` @@ -401,7 +402,7 @@ function fillOutSettings() { } const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); - var availableOptions = []; + const availableOptions = []; console.log(allAvailableMethods); for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; @@ -413,11 +414,11 @@ function fillOutSettings() { availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); } } - var deplpoyMethodRow = '
' + + let deplpoyMethodRow = '
' + '
' + `
` + ReachableIcon + - '
'; for (i = 0; i < availableOptions.length; i++) { deplpoyMethodRow += availableOptions[i] } @@ -431,16 +432,16 @@ function fillOutSettings() { } function addDeployMethod() { - var deployMethodManagerDiv = $("#DeployMethodManager") - var deplpoyMethodRow = '
' + + const deployMethodManagerDiv = $("#DeployMethodManager"); + const deplpoyMethodRow = '
' + '
' + - `
` + + `
` + `` + '
' + '' + '' + - '
' + '
'; deployMethodManagerDiv.append(deplpoyMethodRow) } @@ -454,8 +455,8 @@ function makeJSON() { $.ajax({ url: "/pgt_jsonbody?pgt_name=" + pgtName, type: 'get', - error: function (XMLHttpRequest, textStatus, errorThrown) { - if (404 == XMLHttpRequest.status) { + error: function (XMLHttpRequest) { + if (404 === XMLHttpRequest.status) { console.error('Server cannot locate physical graph file ' + pgtName); } else { console.error('status:' + XMLHttpRequest.status + ', status text: ' + XMLHttpRequest.statusText); @@ -470,23 +471,20 @@ function makeJSON() { function makePNG() { html2canvas(document.querySelector("#main")).then(canvas => { - var dataURL = canvas.toDataURL("image/png"); - var data = atob(dataURL.substring("data:image/png;base64,".length)), + const dataURL = canvas.toDataURL("image/png"); + const data = atob(dataURL.substring("data:image/png;base64,".length)), asArray = new Uint8Array(data.length); - for (var i = 0, len = data.length; i < len; ++i) { + let i = 0, len = data.length; + for (; i < len; ++i) { asArray[i] = data.charCodeAt(i); } - var blob = new Blob([asArray.buffer], {type: "image/png"}); + const blob = new Blob([asArray.buffer], {type: "image/png"}); saveAs(blob, pgtName + "_Template.png"); }); } -function createZipFilename(graph_name) { - return graph_name.substr(0, graph_name.lastIndexOf('.graph')) + '.zip'; -} - function downloadText(filename, text) { const element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); @@ -517,10 +515,6 @@ function downloadBlob(filename, blob) { document.body.removeChild(element); } -function zoomToFit() { - myDiagram.zoomToFit() -} - function handleFetchErrors(response) { if (!response.ok) { throw Error(response.statusText); @@ -530,7 +524,7 @@ function handleFetchErrors(response) { async function directRestDeploy() { // fetch manager host and port from local storage - murl = window.localStorage.getItem("manager_url"); + let murl = window.localStorage.getItem("manager_url"); if (!murl) { saveSettings(); $('#settingsModal').modal('show'); @@ -539,14 +533,14 @@ async function directRestDeploy() { murl = window.localStorage.getItem("manager_url"); }) } - ; - var manager_url = new URL(murl); + + let manager_url = new URL(murl); console.log("In Direct REST Deploy"); const manager_host = manager_url.hostname; const manager_port = manager_url.port; - var manager_prefix = manager_url.pathname; - var request_mode = "cors"; + let manager_prefix = manager_url.pathname; + const request_mode = "cors"; const pgt_id = $("#pg_form input[name='pgt_id']").val(); manager_url = manager_url.toString(); if (manager_url.endsWith('/')) { @@ -583,7 +577,7 @@ async function directRestDeploy() { const pgt_url = "/gen_pg?tpl_nodes_len=" + nodes.length.toString() + "&pgt_id=" + pgtName; console.log("sending request to ", pgt_url); console.log("graph name:", pgtName); - const pgt = await fetch(pgt_url, { + await fetch(pgt_url, { method: 'GET', }) .then(handleFetchErrors) @@ -694,14 +688,13 @@ async function restDeploy() { murl = window.localStorage.getItem("manager_url"); }) } - ; - var manager_url = new URL(murl); + let manager_url = new URL(murl); console.log("In REST Deploy") const manager_host = manager_url.hostname; const manager_port = manager_url.port; - var manager_prefix = manager_url.pathname; - var request_mode = "cors"; + let manager_prefix = manager_url.pathname; + const request_mode = "cors"; const pgt_id = $("#pg_form input[name='pgt_id']").val(); manager_url = manager_url.toString(); if (manager_url.endsWith('/')) { @@ -770,116 +763,4 @@ async function restDeploy() { .catch(function (error) { showMessageModal('Error', error + "\nSending PGT to backend unsuccessful!"); }); - - -// All the rest here is when the managers are actually running -// TODO: need to enable this again. - -// // fetch the nodelist from engine -// console.log("sending request to ", node_list_url); -// const node_list = await fetch(node_list_url, { -// method: 'GET', -// // mode: request_mode, -// // credentials: 'include', -// headers: { -// 'Content-Type': 'application/x-www-form-urlencoded', -// 'Origin': 'http://localhost:8084' -// }, -// }) -// .then(handleFetchErrors) -// .then(response => response.json()) -// .catch(function(error){ -// showMessageModal('Error', error + "\nGetting node_list unsuccessful: Unable to continue!"); -// }); - -// console.log("node_list", node_list); -// // build object containing manager data -// const pg_spec_request_data = { -// manager_host: manager_host, -// node_list: node_list, -// pgt_id: pgt_id -// } - -// console.log(pg_spec_request_data); -// // request pg_spec from translator -// const pg_spec_response = await fetch(pg_spec_url, { -// method: 'POST', -// mode: request_mode, -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify(pg_spec_request_data) -// }) -// .then(handleFetchErrors) -// .then(response => response.json()) -// .catch(function(error){ -// showMessageModal('Error', error + "\nGetting pg_spec unsuccessful: Unable to continue!"); -// }); - -// console.log("pg_spec response", pg_spec_response); - -// // create session on engine -// const session_data = {"sessionId": sessionId}; -// const create_session = await fetch(create_session_url, { -// credentials: 'include', -// cache: 'no-cache', -// method: 'POST', -// mode: request_mode, -// referrerPolicy: 'no-referrer', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify(session_data) -// }) -// .then(handleFetchErrors) -// .then(response => response.json()) -// .catch(function(error){ -// showMessageModal('Error', error + "\nCreating session unsuccessful: Unable to continue!"); -// }); - -// console.log("create session response", create_session); - -// // gzip the pg_spec -// const buf = fflate.strToU8(JSON.stringify(pg_spec_response.pg_spec)); -// const compressed_pg_spec = fflate.zlibSync(buf); -// console.log("compressed_pg_spec", compressed_pg_spec); - -// // append graph to session on engine -// const append_graph = await fetch(append_graph_url, { -// credentials: 'include', -// method: 'POST', -// mode: request_mode, -// headers: { -// 'Content-Type': 'application/json', -// 'Content-Encoding': 'gzip' -// }, -// body: new Blob([compressed_pg_spec]) -// // body: new Blob([buf]) -// }) -// .then(handleFetchErrors) -// .then(response => response.json()) -// .catch(function(error){ -// showMessageModal('Error', error + "\nUnable to continue!"); -// }); -// console.log("append graph response", append_graph); - -// // deploy graph -// // NOTE: URLSearchParams here turns the object into a x-www-form-urlencoded form -// const deploy_graph = await fetch(deploy_graph_url, { -// credentials: 'include', -// method: 'POST', -// mode: request_mode, -// body: new URLSearchParams({ -// 'completed': pg_spec_response.root_uids, -// }) -// }) -// .then(handleFetchErrors) -// .then(response => response.json()) -// .catch(function(error){ -// showMessageModal('Error', error + "\nUnable to continue!"); -// }); - -// console.log("deploy graph response", deploy_graph); -// // Open DIM session page in new tab -// window.open(mgr_url, '_blank').focus(); } From e0c6fd07580938ef5deec8cfdf8c17de04004cef Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 24 Aug 2022 11:24:07 +0800 Subject: [PATCH 14/61] Updates k8s check available in deployment_utils.py --- daliuge-engine/dlg/deploy/deployment_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daliuge-engine/dlg/deploy/deployment_utils.py b/daliuge-engine/dlg/deploy/deployment_utils.py index 24d193d55..e05263747 100644 --- a/daliuge-engine/dlg/deploy/deployment_utils.py +++ b/daliuge-engine/dlg/deploy/deployment_utils.py @@ -118,8 +118,9 @@ def check_k8s_env(): output = subprocess.run( ["kubectl version"], capture_output=True, shell=True ).stdout + output = output.decode(encoding="utf-8").replace("\n", "") pattern = re.compile(r"^Client Version:.*Server Version:.*") - return re.match(pattern, output.decode(encoding="utf-8")) + return re.match(pattern, output) except subprocess.SubprocessError: return False From fe6e446ff9011513b99da33995fbfffafde5e8cb Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 24 Aug 2022 11:24:28 +0800 Subject: [PATCH 15/61] Makes sure start_helm is passed a json string, not MetisPGTP object or similar. --- daliuge-translator/dlg/dropmake/web/lg_web.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daliuge-translator/dlg/dropmake/web/lg_web.py b/daliuge-translator/dlg/dropmake/web/lg_web.py index 1f71d682e..51b40d546 100644 --- a/daliuge-translator/dlg/dropmake/web/lg_web.py +++ b/daliuge-translator/dlg/dropmake/web/lg_web.py @@ -372,7 +372,8 @@ def gen_pg_helm(): num_partitions = len(list(filter(lambda n: "isGroup" in n, pgtpj["nodeDataArray"]))) # Send pgt_data to helm_start try: - start_helm(pgtp, num_partitions, pgt_dir) + pg_spec = pgtp.to_pg_spec([], ret_str=True, tpl_nodes_len=num_partitions) + start_helm(pg_spec, num_partitions, pgt_dir) except restutils.RestClientException as ex: response.status = 500 print(traceback.format_exc()) From 6bede418ef13f1d642672f2a3dd1a349e63d85a6 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 24 Aug 2022 11:25:38 +0800 Subject: [PATCH 16/61] Changes display strings for deployment options. --- daliuge-translator/dlg/dropmake/web/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index a1f0710f2..64528d96f 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -334,13 +334,13 @@ function buildDeployMethodEntry(method, selected) { let displayValue = ""; switch (method) { case "SERVER": - displayValue = "Direct"; + displayValue = "Server"; break; case "BROWSER": - displayValue = "Rest-Direct"; + displayValue = "Browser Direct"; break; case "OOD": - displayValue = "Rest-OOD"; + displayValue = "OOD"; break; case "HELM": displayValue = "Helm"; From d6758380bcbc24bc0f99831b07df8d0b76908967 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 24 Aug 2022 11:39:37 +0800 Subject: [PATCH 17/61] Updates documentation for deployment options. Changes text to reflect more accurate naming. Mentions that the translator is able to detect available options. --- docs/deployment.rst | 28 +++++++++++++++------------- docs/images/deploy_browser.jpeg | Bin 0 -> 50774 bytes docs/images/deploy_direct.jpeg | Bin 36335 -> 0 bytes docs/images/deploy_directRest.jpeg | Bin 51356 -> 0 bytes docs/images/deploy_server.jpeg | Bin 0 -> 36616 bytes 5 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 docs/images/deploy_browser.jpeg delete mode 100644 docs/images/deploy_direct.jpeg delete mode 100644 docs/images/deploy_directRest.jpeg create mode 100644 docs/images/deploy_server.jpeg diff --git a/docs/deployment.rst b/docs/deployment.rst index aa968d3df..e4c39387b 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -54,7 +54,9 @@ Deployment Scenarios The three components described in the :ref:`basics` section allow for a very flexible deployment. In a real world deployment there will always be one data island manager, zero or one master managers, and as many node managers as there are computing nodes available to the |daliuge| execution engine. In very small deployments one node manager can take over the role of the master manager as well. For extremely large deployments |daliuge| supports a hierarchy of island managers to be deployed, although even with 10s of millions of tasks we have not seen the actual need to do this. Note that the managers are only deploying the graph, the execution is completely asynchronous and does not require any of the higher level managers to run. Even the *manager functionality* of the node manager is not required at run-time. -The primary usage scenario for the |daliuge| execution engine is to run it on a large cluster of machines with very large workflows of thousands to millions of individual tasks. However, for testing and small scale applications it is also possible to deploy the whole system on a single laptop or on a small cluster. It is also possible to deploy the whole system or parts of it on AWS or a Kubernetes cluster. For instance EAGLE and/or the *translator* could run locally, or on a single AWS instance and submit the physical graph to a master manager on some HPC system. This flexible deployment is also the reason why the individual components are kept well separated. +The primary usage scenario for the |daliuge| execution engine is to run it on a large cluster of machines with very large workflows of thousands to millions of individual tasks. However, for testing and small scale applications it is also possible to deploy the whole system on a single laptop or on a small cluster. It is also possible to deploy the whole system or parts of it on AWS or a Kubernetes cluster. For instance EAGLE and/or the *translator* could run locally, or on a single AWS instance and submit the physical graph to a master manager on some HPC system. This flexible deployment is also the reason why the individual components are kept well separated. + +The translator is able to determine which of the following options is available given the selected deployment endpoint. Deployment in HPC Centers ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -82,37 +84,37 @@ It is of course possible to submit graphs to |daliuge| managers without addition The manager and translator components can be docker images or raw processes. We currently support two methods for submitting graphs in this scenario. -Direct +Server ------ -Direct deployments assumes the machine hosting the translator can communicate with the manager machines freely. -:numref:`deployment.fig.direct` presents a sequence diagram outlining the communication between the different components in this case. +The server deployment option assumes the machine hosting the translator can communicate with the manager machines freely. +:numref:`deployment.fig.server` presents a sequence diagram outlining the communication between the different components in this case. -.. _deployment.fig.direct: +.. _deployment.fig.server: -.. figure:: images/deploy_direct.jpeg +.. figure:: images/deploy_server.jpeg Sequence diagram of direct graph deployment. -Restful +Browser ------- -Restful deployment is useful in the case where only a user's machine can communicate with engine instances but the translator cannot (as is often the case with an externally hosted translator process). +Browser-based deployment is useful in the case where only a user's machine can communicate with engine instances but the translator cannot (as is often the case with an externally hosted translator process). The browser in this case drives execution and submits the graph directly to the manager nodes. -:numref:`deployment.fig.rest` presents a sequence diagram outlining the communication between the different components in this case. +:numref:`deployment.fig.browser` presents a sequence diagram outlining the communication between the different components in this case. Conceptually this is similar to how the OpenOnDemand deployment works, but targeting direct graph deployment rather than slurm job submission. N.B. Cross-Origin Resource Sharing (CORS) may return some interesting responses. If running all machines locally, make sure that your host descriptions in EAGLE and the translator are 'localhost'. -.. _deployment.fig.rest: +.. _deployment.fig.browser: -.. figure:: images/deploy_directRest.jpeg +.. figure:: images/deploy_browser.jpeg Sequence diagram of restful graph deployment. -Deployment with Kubernetes (Experimental) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Deployment with Kubernetes/Helm (Experimental) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Kubernetes is a canonical container orchestration system. We are building support to deploy workflows as helm charts which will enable easier and more reliably deployments across more computing facilities. diff --git a/docs/images/deploy_browser.jpeg b/docs/images/deploy_browser.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2d78885b74becbefd325b2f5bb46413f000da219 GIT binary patch literal 50774 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 G#{VA#4&{#k literal 0 HcmV?d00001 diff --git a/docs/images/deploy_direct.jpeg b/docs/images/deploy_direct.jpeg deleted file mode 100644 index 71a79e11ae8754569029279a9d0ecfc5e7fbdaff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36335 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 F{{`d8s!IR> diff --git a/docs/images/deploy_directRest.jpeg b/docs/images/deploy_directRest.jpeg deleted file mode 100644 index 32f2fb6d2b80576bd1c217898f3a5d39cac799cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51356 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 t(O$wvU^5thu{K)KLyYB7%UsdS@ksyx diff --git a/docs/images/deploy_server.jpeg b/docs/images/deploy_server.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..aa0d82043fc19b9d57fd15c842a865d2e5d3d3ff GIT binary patch literal 36616 zcmd?R2|Sej-amfXcVic_l(lS;rEEh&a*GywQXyN&J~E6Y`xZioAtYO}ubHfoWG7>t zkz}7yjVWXNF86)TbIyHtpXWKxd7kt7|9-FkRkm^YUf=bzyx;H79DF!f1lTU>8|niT z6aYX0{sRtX09}BJ^6)zROa)%lwA6URZ$jrgX!Nv)G&vw`d#o=F4QPYEe$i~FL1it#;ybfLi?DQ1P6gVmhc7T$df{LBu zpc@bd`$jnJfqM)RrrlF;yXJBLkZ>VPjD8Xk_Qd7~;P*a0<2ZO%{sM%>ajw$HS za$degcif9hF)XEkUi4gb2ltf`oY?8x-r)?4JiJHv_{Ak8rB0kwQdUttb5>3Fyq>8Pa&~d`@%8f$fCb)*^aCo0?lXySjUN-}Lpr9UU8=n4J1BjmBV?J}!S+`MkP@-}&vt;mKW!7#V~rVb14PY z9SowUui&_Edyg>kh$&&j@rOhEb!7kCz{3Bfk^RTO{x&W&z)VE}E*=#-00s81=2M0; z0#d;Ljb9*)`;BeEOI1;bT1X#ajENkTpSecQI6uvk)L2h~Sc*`xMmL+Mkx_v24K)>ni5xQ)E zupKQVg^%vop8hn{4Xw9`Qz{R(>rzRTYpl>aZNxQFRDA5=!-6~uT{aOpYu(r4)!Ijg zq7DELNM!aA&^N^(yp7U7UBfW2+}4%tnBbOu#1z^b}J#9rYlgyh3#uGhpZ_&rGyRbUgpW3}XMHvGK9yhG$psM40! z8R9ueA_ts>r{z)0L`k$s?Dls0asJ>)WVStJptbhEF^x$2kSh$}%^D3W1QJo1E~}yg zV-m9)!W^|YGq4oo$@u;UCEFfTaG`o!)RK3Ba4~RSxCeqZ!US|`%6DoV!(S?q$(Bsm z(uijghNtZq`EO_x5ULsMb z(8a#;yyQ+#@q_K}W2TTeqZx_X6c3^uIr0$r7O)$=tYa9M`Xc^hI? z=lz#s_6=851K*CQPVevZIa7T?a>A;xh;EY)2A$cv32nB1LB-Bc?uyNic=O_$Q~K*& z!|(8V2p`TkC- z+m+b`TfUS{a^KBYavK-uPLOD7?im9C*-6RFDBNWfy`X9F@?<(LJn(6gFYx^UK+AJp zpi?)r5)-_yz(wIkXitEv5F>FxgzV55cak{v7-pN@Xhc7`G+F%P!(GcFv^M!`Rz5_2 z#23mAH{H*Jx**W1`r(1~c5z;7{%GL5g!!-%aUMO#em^RO10J@UG&TqrFAj2Ixpy}* zONg0d0qKrOVdk+;L$?45Vt~7%we|jE1KGk97~is8wqn!#P_ODyQ(8o98|3L{1Pj6* zL2rO%AYCoeTME_+GCud^7dM{P2FFwwla2aWZMI&O3=N90Gk|dL@V{gI|I#K+u)(FV zZ$V6n(q`3-uWH(2^P3u)Vjbd~KioK&BzlXVDj<+a12g`R`2I{EJ*hE4(7mS z5^jSV={2%?YGVjD)Z+zP=&)l@Dra$>PRN*&m)WJ99>SkXyRgc6pUYJLk`z^YCZ;O< z4l#wUr{DKFv1Z1=ol;hJ^E68Xi|UixJB@(`yGRvmYpc^yQ|fsrj_!e$6cKtA2T4|P zn5YxHEgnS}TuxOuSzcZ>Bp_YNT&8n{Uc^V62QG>cW+ifVqAkMP#EajQd~T5~@>fxL z{thc;Z(7#+IKw_{Ktbk`p;#(tHn$Fd+a}YO_IeVRY^uWVGp%_-(YZdS>H>Xo2c3mC zHv2X0-$+h|YKr!tnA;#rmAYNlC2_tAfj$@(L*-!Zp6-|NM)g<9MJ%Uv7dcd#90~cX zPyd}hg(H;bf7-m3D^Yw;6HbXAiX`b`k^CeG5rJiK-Nn)+X^lN!%j2tQZ9zK4eiJj^ z=@(FU*E8eOI2n3|GE|d_%%@JUJOBz^h-L@C1CQJTpw}5V0HQAKXFxxrL%0zbff58h z8-QZcP>#qSD#%DXqWV~48RO^3e&(Wt{~s=Hl)rwXVVlX>}by)nc{ouaxBrZz(N1yCB?G)VcNoa ze>k>uaUmn^oVcna_+^|)=e!DTG9Ampvx}t}Li=uBd9U7TMS$5~_g&x$Ep+!a=R2wx zewH#+8TG$ERsZgZcly_sv2^dms+xHOE@S)LaNU5hy75?>*HVy?y7~q4BeMC)-9NJ5 zaCtpvj47jxJPbE)hiZoZw9`SeZ$UK1)ncPJO_uD4RtOf26WE1~p=dJ49xq`l(H%js zJAD}s$yyHVn^6sPY4tc=?K5mjXK?^9=kCcMM(Ri>4}eb9O9#M`5`gHE3>gsq>JDW@ z;S$GC#6dt9*S}EnW?#Uhvx+3@yG=f=O-E7;*r+}L^v7`J2LSz?`T>wIOz~TD2f+Vs zd;dcBgk1KF$?uT09$|YbB#S9u`ROm17rCV_^K?@l8{4uPlDC)%V{dMqHjwd_(Mg#I zz4CVcQ45mC7rjn&v;vx)jyhc11+L9+4BY_>^A` zfXlY`f4)znd`Xh@$Sw>@FqMaXHR1eck@Zg1bP3sT5}Z>8_6`*6RKk z1JR|!e8`uOSmFJ&1Az7bSbfv&`zN9K~L4fzGnrI7^AI4J@%XYOU zq_j#q@>{y31K``q38`Ou1YzH=tvCSc5Kh4XCglE?6Ix?s#1P?^3k@@FmFU5TJI~aG z*&F<;KaeSL_A6S$bloGmex)|naAn13IqoeGL7l+c z*^%g|gm9EoTT|v+k>02MFWc$?Keg>mI=ubsfDm=OL@72X@z(7zlARwWSWCcIab3Gz z%}xFF!&w`(1)EtNE?7&_XT{oB>bp65z}b)!iWm7KZhicxH3V0Q5Bl*bi2+=`u)qW0`HNS- zt)KS-JBeb^B&MA&N1qt6SV`CYBe>eF&s{lJMc~8jCd)Z(WikwAy$0J`U5p zU8qe;c3iY$g|pV;M4j8IN9wC02;|fFgdZl+8yPFtn``5ZK6u>ONl2n^YnGcoep{pU&;#FOb{Af<$f%NYgP~VB+9r@d$oOSj@JaRch7VhF zD*_?Jn_yt7C53P7-DfhLBnx~#064zMdhh;%PfUMg;nh=shQ;k&%F$Q#wwbPccX@SZ zR%}sANL+$K!OFO(#f>MX>d&IW|)y$fDr!g{!r=Z#8SYnW=8s8ytGv zMPGt51fr!+(x&Q%3Cl-mbA>utk$xc-Bg$5_PEL?asutGrfC;##6Hl(9%bj484Qq!0 zcAfiov8S?XdL{PGYAf^};mKz?0N6u%4Th}Q;dTBt7WDx zl~H8qaERS{obWKFJgqaV2e=Mq&>H0gmjfWx4>Xh0+V}->u{D@JRsJ!(p;*3g^Z-aJ zAx0kn;F0Y>{qVvI{^dS!3HjQn4giMjO%uT?(h1=x6o*#-B2%E&;8GCkxPZo*Dixe= zPwR_}SHw;U;CQ_)=rwaXsY;3DRVZLxFe)s zMKm_dWnChRp+Tm!kZ3IyxCqW4=~YBDz*t0lH49B~XZ`>-2-X(kS2A|Xjfm|K;90DC%uAg>+slwo zXRs(wrRT*xdxLp66{c+H*H>(-#)e;06cx*i^Z$6MZ>v)3d*2=E#cUwr644E}Vt|(@ zToM$0EhJDq^)1q2+|S1SBz=`UcZ`fg)-{dLhYZY@?nzO~GGXkxAWT+?>RnJ)4f!H# zrjO&@Jp7uk+7Nf`TD|JoAWHs-Hce-$(%+^KnSk249727#E!z%&af=)I5c5AEK9G~d z{sTZ9%$D~bF%9kO`K|pr%M>qSk1KggIo{NzRHXl^M3MQ72&TNJ#-dU%i4xr|{0M@6 zF2PL-yC;|o<`E;e&~%xQt=s!(48s2jybw6TkLZAe|IlC9sCuBqSo2}~_&!p*F4;}J zcdP5JJ$2iuYE875Wcm?c1TWlW60N0B-~JLG5UQbCIpgZn;}gVMSghlndG&mlXsTJt z1^@FDW;tRMVPV=VuZZ@znJc3!!{3CX+xc6@UR1c(^>2I{b)RRryO}#bKUa97{%P#T zloWb&Xq;l!h>>K*1vk%KyYGkzaE6&nW zMkGlAqaj=*xnbEVnL7EXd7d%%Zvr2b1^OzLf?!v$TYTo0PAO(U;1x3qi?fc7%!fN- z?fIdj|EmG0K>VxcUvSAT!j4``Y{6y?$sWQDgZ5Z7qy*>9FIf+7dKSmYWPC_gR50t8 z1eV0If#wpDBGI#Gl~+i3v|SkHI%L#3rl06#?^)C#*oKBFra0rvXq+jUo_u$3e>dv& zr9u15{#C*~9BV!PA|#&w$y6qP;;X?|AN_|s9qg@cnz3P*`u1)n^6}`hu9`7@eelGa_tSu3yAX_W zrN@6>qZtQ{UdAgwfjTu#+Wit)RS-{o*C@RG_6vq1ED4<2g1>@3AV4z--Eo8RK{t?x z*3futn6B*CoFdR-W9$GBTL%N1ny@9*{>A!>e>wEOhb#ZVuKiumwNsUV{NwI7`@`=4 zwNx2aq3jw6+IIfgc_id>rzfQG zLERUc2j^C<>}<1ZR7lS%WT@O!)JwRzKcX#Y_n-9a&!TzgPszs6f0=C1Rz;$mszPtj zPPxOcE>?vd01y(i(#msBrLdUj`gU}U<#XSjc+#o$X2C~`3FT}siBQeo?-R<Yx$2|Mdm?3<0OJ#lE~5T8Uj&J9!b;BIpCH+CN^lL>QIQ4rxX~9K(P2)S#)y> z*hs;}OP;!UgUOj)gp(Betr63HU#^dywYn2J`K(p{2>|r-4)~{Vh<^sM`7Pl2yFI=1 zwRMi)Bz|6eEFahQ(sdZ-shhWMExmJN=GEkLk$Sr3u)-#9b*B1n`K+BkXS2VvkH3;X zXTYRBphYDI7yX7mIff)JAM2ZA$Fk_58U_ zMqr8L6;x4|6 zC1IYB(FJbbYK&c+cYCByv8%O7T)if=%lwM5o07y}Vbq!JkAd%e9?0<E0<%ur_Xh%kHoHecV}lF`a~d3GJl0`u!BiJ(M|w|GAUm(8rJf=%q@EID4I1pTLa zP78M{k1?_U9;=ADm2Sg&H+i}nt3rY~*YT{yKA3IDb?ccSg3ZjPs#WTcS<^{J$+t$j zETi4R-jBCT+j4YG>BhbSrgTt8FwoB=N&Xm`X$M^dJ(8f6wV&B2gdij%g9vl`gh0ES zGuxknhUs*EI#0j%HNXA@CKcWC#9vSt3cM;wx~c}zX|!=m3lv6c8SUr18xqi% z%Nn}-PqudmAPf<6$ei+knkxP3{A6gI~JaHfR z{I?Z^$nQTRpFRM(G~x7kM6dV=s=gSv`NUi2j@|76rCI}On`G0lR8FySZQn<-#LnzyCC?HB)wNeig7 zG8P|v=iji(WPSb%gLvLGz1XK~7_*|Y`HQqnQu`ZxxXEU)@GqkwuNN3K zu71=ZoUL;F@(|&)|6OaY{#8Lzs3xK_;=3ed8j^5 zIrW6f84KIKwR^8my{D&(119sCO$62hZt&x7ps?!n*wmGaqgz$C48x>`xPDnXLdn2) z`3alXtSQ~VyukiT_?4G7b4U)71~EHxp2yZKvx6{ZC6`!kVjpzcb}paO=)V4T z>Uo|W)r_Sxz}LATtbFfwQ~=?=XZ%+bGwkhp_kI6ozET>Rj$Y6D_bc5TWoP~6B=fvZ z8Xx6&{^SUuMoQt~cT?ttXko5)Y0`!4-L7oAZb-D(x|d}a;^ZvRH#->pZ zPx~gnyQIeB7p4?mDm+O5d=CJC6tslmhVzYT$-%Agbdm4)KSj7z#e5Cs!FFn@@kR`$ zJGi{qV*WsVD>RwHgiZsvIZyt8fPA%KD&OXU8T8}{?|j`qeYHCIT2e1$Q%ad(UO<;K zr|_sFT2e+5mf*^e<_Ktw5!!J=^(CtoAso2mxHcoNQX4l5#pzY1Tcw)R~=oMN^}>`S&n3*Gmxt5rok9ha3o-?C%mO~?1T zA2@0d19csuB6@b+myh2DdzictYz@)EZa%FGQ<}Xl5D@37hGDKLio-l@_%ciLew2?1 z5P!3&TT~U}i2Ia-%fYgio_4+eBH3QExH2@_v~4Uma@whMDdxQT5Q%y1vCXP zs`1}U_=!5-WCJ$r&>3nC<%Ku)lT_j)GTxGdY}Wuz;ROLT5%-%~x+AtIO#8tXSC z`VN0H<$Hv9al46d z_2)#Ro@_m^`)iZiEF6uYbO- zuuUG~A6&cM^^E7e_1t+^T@Z~q>rYki-WUkh{i`!P8r_rpfJ{xCTeJZgxCM^7fC&xF z(tc0zxu>%O9*#SA3?F*NroW!!U!nHO2EL%;$&@&gF1rXJVO(a{0l?@IA~aGNA_C*; zaV)VeVyD$QFQS zDrz2OJx%0hC-o5?J#;r`&AIrJxk=d-~CndhlYl7g^E-gT9hyJ*eG-X?U&fX2|;w}L~DP^#C#LuG+^5wUmxM7DT#8lM>qE_AUz?yP`tP7xjSc=%oS z{RcJ$>D`9N_m>p3g%ofgtY(8(n_X~)a%wmQ6Fw+aLJWsJzRp?6ZTSvuOs{00C`{QX z(0PYB@7<%W7|k(o1};TcY6<5Z*?duvQib#UE_Yvi=*2|OdTNosZOUs>9dCw6UC7ULvn%1m} zqNEQOPW#wX8HlZGb#U3=N{0VJ@c}NkkE}O=)T3hC+_3}S+b-qFyux2gMcejxr)u_O zTeWfY5vm#3pC(#9#zeRs6*0Tc#!|%wKjz5^*TD;J749Xo!s5ERGUX<(R`?`dH5|Xw z%=)>{?7{QNl+5J@o4eodJpjCJ&=Xnk0v$;9n|OEt+VN95Cdb`%tjk&{dp6#a-&OVD zFRy88ARf;jHoo1lO?_9E$RS5h_uL4BWFw;NaPzfG?!uQwhaM=ORnApfwH}-MJ|%DO z=g_Y>;KI8_DxbD=zq@^HgI?q)ZwwT~7yviI6i-A?p7hw{my1Y)7B?%IY{SZrt<9}~ zM#K9N?(Chbdr!m0^=o3ywDVbK^oa79O4b9wXdE-)7Zjf;X+Ej^80~Uwf3vq}RJj*d z{T$6)`BF}y*T|GsJ__mtDhW>Su`YCy&*GDEN?cT|HFM{Yo^bSiVqavr-pQE2&fpjNrf2WVvM z)~hoNzr$X2R6&x9VqKWW55a1Hv+uo6D9=Z9+T>S7DGRjb$_>Xt>o;kN5(xP8_uI;f z5rP46z^3y*G;x2kT8FoQnb6T2pC{WvgGXD63G}nc8R9UZ&GnB@DVk*wz3kuOsXbgo z`2MZ=^8DW$yx+GDLP;*3Ae3YPLP?Z}HM8PFjEHXF0042H{ymj-@Egz0!~olUq!$00 zT1MewFyq$F&UU9ir-_e723uWfzvy|ZxyKnYphUwMxc}b;S$>!1{`VN&{}KxL|5bDF z>1-Ex4LKmMHl~ux`+vOi%?B<+zWO(kDJ_w`bd`ziEwr$N> zrcw;M2s4m*;PU&WByP|<4Piml5#?J2`P^W~Cfhz(;^Kc9ZQX(D;umP| zD`bpso_oCL{m$?ssH<%Gb4e-4<=BA4cgi90{Tlfr-Q|$c2KmpL08;jw4%D6`S?B;5 z^X2-9BmE;eekkni1sn_^YY>?2eN`Q8x<9u5Cs^=bQS8x$olAAeds0Fm#WG%QbQmN2dF1f(s{CQa zK%D^?Yk)$7O>mAVhYp1Xw4o4o@TD=5#$o4{L*5NL`Zx0~_t)llH%#83 zpr`?;^R$RV7|&?lmh|YDyME@`U0t=rRCZ5h&;0_f(_-?T9!%l8l!ld=_B2;KZVNQ4 zYin$GLmT9~wt*Qgd{%Sz_j;B<<5x{JHEuz&YGfmel*qwFPPS`OmR8G;$`#KQO6Bvb zA{vA{1*CK63a2&O5Y9dvM?{5UHUQ{5M(AEWJXF8s=cJ~~}&Rg!f^?jn>9 zws#Jv_2L%Z{MD@z-DmSUyfY>BEiB7z&b1E`7d}dVYI(p6AAanff0R(QXXWz4_*+Av zYS$1)GHVwG#j&o|`rW@W$lkATp~FeDeLy<*4qS$x;d5BN6%C)rdMti$$!$ezd7P*_J!NF&^`|Mii~O(YsY7H zAsLCNt8w&I%7Ywlt~6cXsxXon7E54L5R;O*#~>*<2>3qw%i#piF^B_!LI(ds(#Gjf z$Pl4T7X!YuN)|#)L-Cp9C-Ad>k*56Ff&M+F^=G?G_~!@wrgZtaIq*aqA^O!up`fk@ z%rLH0${n5=t$X{(c14(OC$cuWV~7DQdjQ13%|ir<-q&#RVf3xzlq$KjZytMCZi?Pz zlW0yo5_@DJPm1bu{0R>^naY*6I+faf4vm+*thc=>PBh*7aF*{VsLcHO+vDK}Kt6fE zd{>3PllZ0kepD6rLQI=guLvpVSysOiZ(w^nE3K&hi_Cy|0tWSYuwpLn7HYfn)G~*TAWd^t(zmr=I}+ zd16E}PyD65&L6woi7J|^mvJF4TZ$8`waTX0=1y{@+-`D_U6K_>a&aCRhvcC-TY*ww zsC_>d4iy`{@mc6BK00dqh110R)~;b|V^UB~us;69O2Zx4(c>}|@Mcj4rJK1m;g%1< zT!)X`k7UyDs~{q;Pd=OCLGTzC4#8i}OPQ&`rek(R+2ll+XnGA5&*xJXdE~RSB16%# zy(JYUyoqf#ZiQ~%3v(uzJ-5EACN`~oH>Bf3HIQfH15*u`H7e=T+d706>4AwHj8>L+ zi{PuNSmpkTwzFDUJ|$MQGcg4j{bG7|V{{uIiwWJaOrCo-h*Vi*b+Xydf^%1PDA(m3 z!wc30T!v-Lf7DTK+V61&=5@3hX!LSa^)kqm&bk(gB`h`WFDFm-#HNfAw%XRS^h~UQ`E7WbGlhu9HvIjo1OvA;=-C35I-^ zw2mG+g#Xg?|Ha^f!n`y~qUx{OkgkOv9^s*oKR)0ewFdf}V${c67hzBj0V1}g=9!15 zO;{ApyZ^Y2a|to+iE$^V2u9@ul*6REvZD%1)~v6q^@f$r$>n%9xDCoJ?HWr0S^3U# z0svpkk$gxY0%J*pTzQ4tjv;|WaA!csG2BGmz12)G9X{2T?ZQZ5V<>3!I_o`UO0N`U zkbIOKOWmB44PGc0x7%gj(im%hYTW|oqO$5$TCiC8E==_oNMWiJ6_m`C+<_qKA~4rM zT)6;eK{}3b-m@b>2s9;x0OXVJs5?8P%I_=wv%|EJ+g}xKU-H@i21q=N&@O9+Kl+3tx*w%jYf&a5E>5$ zhL)SbM^m)<4gg$H2ov~V(AhJtkhvk@C=BPG0jT_XiFhdst!*Q%4DP9BE_)8CH+%|J zk`Lj7tBlv@a(3TO?pk26b)Dx=(0#fvR5bId_i;pM!zkAry-2W?B}kmXn9Vzp`=3s=NgqL3W9J0n1ftJ<^5)JJ`2+VxTf{8PUrDL zh2WZ6ST{x$vVCs+vZ}J}^-N?@wk}fj9NkkkDWHbvh?_B<^h2?)EDf!~L6 z2%hq97xDt!2Lb0Bpv1ww)J;t?<~OCsM4R%FXgw6turVl6fsVqx=tK*fCR>;`gCp(@ zrXi>XUJ2+8O=6IPqg05sii8(ptGedt{f39MpEY2H2mmHl2m%!c4f(@rZnsS+e6n_`4>XzNxU#A7uq!h#>R?ZY@8hZn}qY9uqJ;J zfl@6lfcn&=1%jyzCZ3ZCueP8Y1r*(ExxtvEz~n-3n9VrZ%vy@#@5Cszg$O!Te7=f z**bAM<-FJ1ogF_O+w|5xe4 zk5(OCM>*H-A8j=D`N$NhcjwNXljo6Y88=yXv52mq+|!*<){hg)o_wFx>gG?5`l_!7 zi(-*6FEDO~+p~R9%b%hQr!#c-`b9aUX80TW#sHxlJd@odS#Wip2B#XjCA;(ruhNhw zSU=vTD?V8&m@S?-F`eQ4rD9iZ;LdH^v2xyYL;D0a2z}#z1^KKi8amUax`+#y6j54zJkEB6YV@mof-sjH`M9;UYXh0j zj%iIuV;s&+Q>5aPs6)-~H;A^a?|9OEY2BXl{PB;1Se;!dstV%j(gF`rzyz0$3SSv^ zuZM6E%LckoE|d8gZb80HN%o)Umog**Sok-M*PC*iuXKdcCAkUL?e<&)GvPR1BhYdP zjhW+EUD$Gqk1y^;F z443t*qMarpO(&Gy2Bg!Tt);9&Tvcq&%jwQ!@ZP+*DKl?$exSj4+LU#j;Dygj*6?}` z+qJ+aHQNWc?H3ld=Q_DNi5>588ur~QwKoq$dh<5*9idiz+?|UtrGjFSJ)Yb|D0ViF z3-@+)WX{bzgm0{3TiKvF`|Vq|nN4FM7xzgAgYWL?*CaD`F5n9d>qJah?J)s~8lFDb zSBxOx+b)%JtvLq^jf0;E63Pisw=(4N-)XzsFeDnYWKc&(-FnW!fmz2}TdUx=0ObFL z{1T4ZQ-s5D?TMhEUuslNgBO1?@Ggi_*l0B5QB2B-_#4q*tMh*2apk<2gIa3EDIJkv zx?+%QHh>wBADDn-u!r&xSr4qP;aBg3emxEcnc*(47l<8Mu()Al9?Yc+II&e^fkdAJ zAjO%Aw+F!!!Up^D3%`64NEm=%YH2>e?9$^vt#)W^sD-cFW7o~nvvoWY|^iBq9Mp?po@L*EnOq`-V84E);$beN9NKjyQ+SJ$(_y zA39oZ7nv#V;w2Jyq2X=HsqwL-e3~=C&Fa5%CuM?)&f41AK~% za06x;qZZX1C#9vQ1xH%W80bDc$80Yt!Uoq)UW6vsu?Tc7;3`8-IP+_TH?mJ#x(GAM z^Pt6Z1m1ZWKkIvV;gtU%ebTl0z3U!I6l17vlW3JI!6hAJ2(cccUMkX9-_km&#k~-? z`|x#+>{7Pj*Hx=YaT(4$D%DB=O6F1RoYOTKm(g&-tz(@MSj(CH8*AN=3{}NJInE#2 z{CDWTel>^{{OD(XH7X_fK2YwyWcMKZ#w*OAMHh;hEbi>D#kY4oqs2TES8LW#@3Inm zOR=W-@v=R0J~Jfe$>tGY6N)Kp6FmUVN%q*0RQXp9fa8ayH7tS{;UDZ;V4)7CX$tzC z2P__Q-ljhQhGX#-;BR}uxUH{_0&mg{AxhK5gs_KYC5eaBSd{t>lCcB>z9eT^u+!rQP;;*$UMs+Ur9ssP$sq=1zrq zc|y9L>3Nn8`UQFh-W#qp@vf%5aQ9ij!tzP_oRzeDuW6W&=!MsK9p$;kaFS!7=d)GC zb?e!e)9A<%_r+JuwJp5Iwzx9GgU(Kj9usAYEaU(#0NbR1B^Hw|vM5e6S_$#;`@Hnj zf=RO~dtD;;6BXV7@0Q~&x!p4cY~B}3a}8NKU((Uo@7chpOmW5w$r#*J>{Wv#Y%lt4 zAWfV&Dbwf3ZL>$S-_7421@Loi90tt+Wf?%IRqK9X-5}JAZI0;H%_2_7Dl&TJEd8uI zJH2Ofb8dr)KK;dcmYy6f!wczb{VQrsA1DkkC2x?N#Hn6$YyYZ(85jN=g%z;qb4Etx zcjA7`Z<{z@)ZdawmGnls%yOQbH~)G&|0C7hUqMzdtaaG_uACfkDBjgP0G@%GrNdNP zoOh`al)Xdu^?dpNRA+n`ZvQWW*Fu3MAwx*ox}muHsNOy;;nh^)f(`7(idD0&#^>9S zw@f78iRtV}QTKA?c^dfX zyWtv;A{f*8mSLr=7cXS1loM^NAI>SZ2BH&5yJOGMWhx9F4rWLCwDU7WssXH0^7K%b zwULXO1xZ3nx)qlc7QjMC!wBT$_>XQBhjnLkTnM!_JSz8mq(^3(pDUT^fi@tH+uhhJ zaOOuvZ`t+E%S5Uj zhLK~*@inGkmR7Wp&@*|5FCHx2sysX0*ZZt8EJwtWgJR~WPKoe{iR8)wL?LXZ z*E%FkV=zE2!mUik+{AzK>4#Z9%HVP@Z2?c@a+da^lB_R>`nx?D*!|9_tEWhY_|gf# z*t_$4W_QNm>(4h+F`{u;3MYb1v2=jkr>1a2!3THWTT4v?k}vl&G-#e$u7Zk7JG@+S z;8VGr{j}%kS(~~a@C?&A?Noj7NUS=hy*Y7Yq!3NK_6sv$UB92zM&p91;vnAEyn4gA z%?2}*%(|8-@55=LID375cGh-FVez`5R-yNXSFQNDr(xj~qr|;V>lcmKWC|GaeE9S; z-1}CU-JWHIi$8*UA_wcL3juHHnweir9|~-&`81n{o+%fecIs*nMrYn5UBr*M{&>QZ z;ba+syWG~xjqMT^!E9rGbz0EQ`bWasEgVC8@Q-GJU_&>M4NJDE0>!ea#V||Q%nI) zalrQ}g8x1WaR+=C1N{rWp3KCTpS2{@+o~~}2*=8g$*Bqqv$vbxGcXC%E;^<=Q#Jpj z!}$Lwmt6u2Uww0>?eNi?GzD%gj?W68t!bTUtZS=njl2{7i9tiMKqkddfbvB`Q7KrA z|InFg=o^TZr4iht38^@_NXW)_+=S8$s5z?H!VbbEZ;E8YJipS!StP$jzmL~=TcLl= zNaV<@H^nOAr%GH_XidpR+lUOyo+?~cLG*N)FrCYcSq$({BaeZx`3K}8;Q422e5!)| zbg1Kc;1wA3&UR$=fB>C=37tzvdRU)G3`}I03UOF~{a=aI{|B||C(t2aY*@pxzfZ`t z--E)1nqG|wDYZAmcV^oRxV{e?z8Ef3;BYMTrXsy)98jye0oF;+=!*Be*_gxSNj<%? zwY|8nY<7(~)i4^Mm=BdgZD96}8WdZ*)xc?qN-getHsX~ST>B%DCb!HBo88`lHrLF~Jy8;=)cn-D!mOdCTc=B!Dx!nh zKc5Nt)13cj)zAM{uAD;f%|`m3?4YNN0(o$Oamh*F%xiH@{p5u;s1G_+>;8TYV(}4_ zQj7Tpnple!e}`g+Uv`sfirl`nr$w8huaxHF+O}!D4+@taqcT}tFK)w^C;JveILH$WN=81v`aNqV1^`QS2 zp6fq>v;J-&{(~j{+aiGlRey|!mY~jpSFst1_^OWV=nfR~s`YN1C^2|RxMq4yRp{DX zXdV1)bEqtTUsr!G>x0<7FUT;Sm}7lt%hMH z37eFyo!7yU01uc1aX3u+H7N@-5M5a$==(=J^6@O)jl5OU9ye7kHgtjS54Fb+43Q^^ zBOto&IfKFgAj=?*be`GI1V#733xt3aB#N-Gv7j0PjoA2b0JyyZvE~JmVY1bT$XJ^s zru1vp5}kDmvRjq?x)s!~A=FB7HaQ@J3KUM(ponP_wUr&ui6&j9E~oS_#rx64c!)Q> z3K{UBs4;60T9sM(FieXeH1;MFbwlRB9?*dtgUlXS28kjXZGqySwuJoypy2?x^Su>M z629|h&-OFbIl=*e{pJ4qSlV0+l*OcI=!n|HIQdvp9JYV2vW~Jp<6}_UOW|AEH0dDj zlck*N!D7%E!XIH6uIOv?TtnAOwr8DS^Oz;I^5w3flU8r&DH&O^*NeL+ZP&(`CQY_)H}IxbXDE|2xKN62YP0;w;6l-+Q9FEYG=)WBZQ`dk8czv` z+uYhpeIJ|%+;j%HU{2C$+?u)xgd~gOjzV$ICbWnN$+Xy>9!S+sHB8(lIAuFJ1^Cm> zWZX6a^(HL;+nb4$$)eWjNn2~f*nbG7co7rA94W9 zAZp=rL^7n-e*eW+km&(i)pR-^aS3d-WYj4^&~**LHmm35us?~Wa2W8`oVSF4l{aq> zcS-j9M%qGfm*^CA@Ttj;3mt39z<%44eMj4G{voKS7HNM-bD0R>7e$J=uH@M&&;mv@2R>Ys+-~U76@lQlG|A?*sdr=Nu`cHo8 zPxGCWD4r}2elf+THPYz<>6YQzn%d$Pq95*?QholL{f@SoE$(F(=xS;JHa zUFY$1ac%6_ZCavdcZNj(`qHb)7kQHwmuIX80;CQAz1BR-oQ=sZZ>g)FnM+W8`tD3s zFs7yTdDN9Z7#R;|!GdokVzYkPSS~rZ%G#!gKRTW1{pY6)#i@Mo#~&Z+-AlP(uG*Pe z0nm6t$iuSOV3nE6Bke43oyr`eXW!u?_O>iQ(Ct@_k&(?{8_jz7wS$d5*~W7N|;JL<)uQnwwyIdpBNNe zw)TT1Gl`_`RZ0v|$+4)3zZSMKwq)0~%N{kyk+l4pGE}Ou7y7^uL)5Sa9k_;2i{M0m zZNycz<>z%7i+-_p7OIi^4}CVZX$$af7(%2_Z2)$N`}4eD^@!hvx>!#?TkBDJ@ut*K zfk%wJS6@Ef;$;vqWF76esHh%l-9*gAp~viMaFtWt@+`WQYkD+!ITDo~jTYkLiZNLE zS34C)ZXOMX=xTG>U{TR@v72pj4VfFes{9GiyT`@rlhltqbt|1lYs2d@96}d)zUabR zxt{=r;Jc$FW9V_8@nOVLP@uiR=|^BMLwH#4Kb&3CGRZ|bWhMDLX^jInNY=#QLW6Q! zVouL}m57p6-pL<93v2i5i&RDjmFq0nBR!Qi#l^47gbBX1x>jIulko;6aS)Rn6X2iG zq8C+iH#_PfdZK98h(0OEugO^qC`!j;iJn&}YCE)C5X9`f{kO-wyc0X-8S%-?(Vs0Dz8WpL` z)Eaz$^1@xT69awv#M@rmJ~L*0tTvw#@I6EUemtT}rClipi+mu8`}XzI<^ zN`WhCxtn>dkZ4bb+6)H!x|Zzj`Ew54IiyDIk@Ay`+w9{@E+SN;heYfD6^PV7XNKq) zFo+Lb9U~;cdYoPSi+ZMHuBxalU!Aht0Mqfxy8ai(oQtwM(ObIAM^zM#US9To%y=Q6 zwa^qN+&dI=KRMb0%RLqGRlt*B)~L+phO-4P@0crp=CnjZltnm}eiSXH3ONz~?n&** z6Ttp9 zPm$u&4M|WEc1$%!Lp5k|Zvk@t?$=6*F7rt_j}@yehx%8kYj^T`3bN7PZI@Fo?6@yS zS&8{Q+7bZH?yC|mPF3bl)&=Vt;U-I5vz=l#m0Aalx?7e<+TE}GpZ2~xtf_3>J9H2+ zfb@C_*VeUil1uJ(kCm=c8H>ehy1UK6 z(qRyNeR65RV=Z1GbE#d!o8w$lUClik-kF^;o7(_XI)NEA#c0Vmvd;;=&svXf!`RFe z%ZS?oMc!egXBP`mAwwqucf92I)84}q&i&fio_gn&#msV)S~?LrlkXj>daSyhesIZ7 zzj@CHR7_Bvgmr|0$rrjY*w6aAwrG~~Fa^r?OOx)$Gt<_G@AAca(~D0W=63VDZLScm ziL)c)ZLJ~gQo>EORbxft+g!vPz}ymwxUO=4t9pIXSK;0nKJyE8HyV^T6$LKJn$cTC zcz_H&&vc7K?+iU1?E>~v-_>NC4N_H;=Gfv`F7~1v9Q=^iwXy1f$jfsFUgd+lmgfM4 zR41AnnFOJ{ubzuQnP6A!PB%Kt;2Jzqhih)R71^kB`n(sa;KIs#4ao&;c(}jv{c5DK zElzgpoZ1~GOgmXC^x`5dh_FMAE50Ij)xX`<4L>Tw=62bp`L2m>NA?9u{q2W$0dWs+ z80)s}8-snl#O%Z9JiOR-7l-m5EhUe&GFW%|r+?P8SU^-rD zD{(;h&rPXXy2iHU8=O-J#ddAH-OszBFz=a-PPC7nLkT=Hw8}2A@=Kjk4_i3578N3;}zp^($Z<^#i;U~o-Iv2^ULFvYZZbUtf!b6Odj%eH7SA+Od0S8^GH zzMA4zlx}9~M4hHVC^l_E&Kje@YvfY)5iL+9y?13?>~fB0XlT)j229UPvo=%Fc6RN& zCg#1eYcj)M$%Ru!D*Gu)o8lS0ea5rkqUmm+V5ba3SC>Hhk1mFkXuTMgja|0VD^n5a z8A;YjzqAs#66sSAZV+ebnw%|{?C`{=l&QzI7 zHkD(gpBm%JB%tkrpPzJ$Of@2HjJY*k-H~y{vf;^+>g?}b?ivAVZ5YjFp<^c>$6ZuQ z(xDuN+}Q>bd+vRGuBWrqatEEu9t9dcVJfdKr{?a-E<#mjk+}x8PM`nMaa2S-Yz!@s z$8;gEG@F3p2eEGEJ+c(luQ&w~5dh86&Y}6x%d24kJvd+6hTksCke>OEpaVAK&2Q5Gc>JdY!mD2rW-P5? zvZ#~eIPry~m|2+@@B-K#FDc;>&1psY9k=qt%`>Aad1+q>woLP0NakQghWsok&gPv!6-Z(n__C2uyVXKQ;95T33(qXZ_3K!s<%33c-)=p{0H22q;3^Eu6)7JUkjhj8OpLyh)7iVNOd*x`iOE8DcOD+@E( zV9vU>vCOh8jgeAKA%fd&xR6`k`zupA8U!&fs2`>y0iFN;gIpijw><$^Hh{cNf>0Es zIlX4pC<$}n`85RASrf&xRFiH@-f(WBcxTWmBJ@UaP&S*f9%RwMC&BkQPpo zvc4dFNAH{unMxdQx1Fk>y>epSU@VzFno-VNzEjM_7CZZDs5fn+2DiiM_bki`w6toV zzwJVXMGe!P$8lQ4_LEndm73mE!>j5Sy>-R)1Bp{wX|sxz*Ac$_N3Y)~pON<#lT)zk zy9N%RnHSIXmY|HuBob+d=;f@GspwGZ84|^7ZYOVb7JzTW#;J+h3x)vc?m>4Rpl)Nc zH)BDAO|+=o3GZVH5&F@09G7765HB*)?QG`kk&ufs^6ZKxjJ|bIsr!H^*?17~IU8~O zc6&#Of3BHFWAL_Gu?a#(5?d<8BnX`JGRNHphi>D; zqP^B%YwYAuVb!ZYf)KAJ=YIGm%8ha4!Bjr+(wIP|mIlx|YLYlGT8FR;9M%18My^$c ztFI}^D@JVT9m?!Kf*js_5>e8#sy)Vf8CsjuE_PpggK27tFwf_dL%kHt^(hyuP0_=H zQRniINyG#KEsi~vm@PKlb~7s8fMd-1Ddb zRYK9NFm|N+_(d{%6nX7~catJS=omE*Jr8}5Sa~&XO>5dw-Z9afd}4U%MJ{)kbR&(2 zV90Gsk!zddT)*LMuVM>i@-RfM*ly+f3R$!($2xGg+`624;@d)T#Z8KG({$Gk2w zrJZ`LyOr+UZ)4w>?l7{3Cpgc{bRvR|+NKdh3aY(Um;9bN6(MI*#p?x_f?C9)F?#yX zXuZaj2FeYia-(b2{bv`yv~5K-;gS?U5X*#y_8WqHyh>n2$@-(fZXQiQAkTeMn4ba| z87B#6dSehK^4V%*4jQmFg4}8 zRziZ6H?o!Ff9g77Yp)1P5TK7aw3g7i{>>x%lM?^8t#H4&kpG?$ z^w)R!JBskPH-#Jc_&Hu&(EN^#)ew%Qa2obO43qq9t{twB`Mj z&Tg?_e=!&T_zk83C)8!y9ic3j=aZAjo2$s=b=Tpl(fH1Q%Vg(nUm?4+_i}Z2OHwyB zIkt|+O3oROnXvOYG0uh*n^>HN*VaI^xYd~EjvsMr|DXzKZ8-GU*6h%ezFNZu*O8jf zYIU~pQE-81nY~w}tE3WgFJ7Gd=qxd=a(Q}bLOaJRu_4mWIWEKtn_2+YEUhq+ooW?z~cUjlXQHX4Zg$eVHIw*;5 zfpi_7T_b$b_dk1kYGb3v_c@jnd3L6Ae-Gq5kAHo>7uKoc0pF2UrTAILaA8Bq0`Jb| z7v%SJcwka60u84gSIJdbedBW7TdL=zwdw<(K*9s{uO0kM?x46cJB8cgaMpC?!VhFq zA5W9zuO_EMxj+R|ez8oDo z*5EJ~<9o7DG(X`1G%eLw$KYfnKhrKIE5uaU$)Wue7Gx1&J&urMQNRC5<%nzjy{?f} zTYts3xN|roT>43YJi%S;I}hB9gx3t=x;(n}W2Db8j*r(&@~5Cpo#7DM=IXnh!Pj+P zw#~N;VXL3}F^N}h(^?U!M#tnRw|gDQi`ZhCZ5P4!2G+Uvcpd!)sG#Z7xSEOlsi$8R zq>n=G8gI5s@PlNkXkT)jAbDlm`yTC!mGrZ)#G#yY*G61%q1tMJ90acvsuHotkn~N$JCiQ^O|kA3!^muE~Nsm6ClNeP)nDAQm;e zH;;;>#sz#e*(N5Sk}ZC!DQm($wFYWa*{fCY_Eg^q)dQ37MO#@|WZd1E*JIIel0myA z#gP`$;;fi{fxPqRv7T~BkvY%9Uhk#+bVa$o+xGlWztZv)6;Z3l9000|gHc#a$>)pr zVA*Cc5N_-V5kbz4`Fpe4u#D(F#}9gYBH7Bx5_mliafQxP=2SD~KA&$I#qZ#y&8D@NC$tLu`bPsq@IUk!;Gl7Rn- z)(G`_zP@LD&(BxXs11SwG=X%v-!SVikr6-?c{hT7Pzb#hyR7+pHhW-ssHz2W&~KsVw|ja*3`48|&SJKMAI$z%71^VQ02Cc1E=O$|Mw+*z0LV^B|f&#MN%G9I~*E z&Rk|ytsk?JRf|6C9)IK~0{{ed3=sGG1JH#SS`fX_5H5qBk|VgMzxZ$#X+rdOhIY#H z&gitM3W&-j8b=L5>AJG^-J&wrtW<990(GSji&noXZDo(*x%94VdRw@S<|A%Dl2g7} zP%HQV*r;DNvS+~z$Z0Z#Sko5BFf-_hA%+ynq#VlWQClT6$DwDw7Y=b|~}XL+UM z5zsB;_`Ox2V@yWedD|CgEQGIeDaUC}3#1>6Fh96e({zYlPh!;kI^~=+4-2XH)Zb^G zu0Ie9>T9G#uXYDKfe>hn-N@IkUt9RP9QP#KbX?P9Cdvw6_bNQtef8y2Zk0WJp==;1 zEGDP&%7l4ASu0wY`=FD1;sgpz5x$>@l~ZJ8^tH$q-j7J!orsQk#~i9doNEo$F>M;O zYcmvFsvRvW)l#fUCz!{cNV(XrmXWX2aIKv~4lXh!Xw)@i#0BmIPZDQ|+>Pi+DUH|= zXuTHaew*82a(1yFtQ(ikYW6)(C|6%t)D=DeJhjhdQepiJnVPDKv1UFbF~s}OS^IfI zwr$Jz+2!40C92fRqS_|RBt2J7d|e%=-mnv(o8E8t=0qB3rf%QysWogh>KJ@c;Naw$aN)p5;{ zi@jvK&NAhGT%f-N5MOBQXd$Qefatc9aWD2##Fa-JsZt!T>Oj-O0BRsVsO;QpMn-gU zTS(bu#f z-ZSwa3*aZ#W$cLl{C|DZq zJE#zD(Emq~asFRt6@+@fiC=!Z=q-oFUp26$ITdp?N*>j)q4~c z=FJZX!QWi|5D-7}wzrse&6M@hLtMOn)WwvDZuW5fCqJtK|GLHe+Pk2T@zfNHavhNy z(b8Q(xy@sD4M6TY_8!ux?%yeyZh2Y9ER@OBLVxw;)H{-#7srq`IV%L23(tSh6^pUt zRH2E?On}@!>z!u3|F`AOA7)wnJ~jZH?M73h=n#rXhELj`qD&}=__A$SF72K-Ld2o) zArKzu6#xbon_;L2sAk#QO51>}=c5t_5(PnwRLVDjy4KQ>kd{DU(9pTz47uT3M0ppI&aAxECi zaJFV1fOs^j1gh^UukNG(z}Ckc-x-A#?m%bJH&~Phns|pHi@d#QjjQEtC-mfwbY)+jH`_gQ45~co zUw()G;<2#Li(St-&#iRYPWZm&3bGs_lb+^g6wI z>0}xpx@$rmX%hfdhtALO0g}8nwRb3cic>ljoWAq;9TaZP;OA*}4x3(dsOg`DdamXG z0VWsefdp+3w1WJ)+Is=uLIi9m+#SN1KJV)=(tE7gA8CHnVLo#C&hVsfZ+5)JJ;KPB zvM3Rwa8X-l5XGr10MW#lJw|N#K3cR)*xeJ{z0VIraV;-;HTQce>%$(sV7?3+A`! zn1B+PD2(<59pfy68$l>{9HIGnX;O+D>i8lwPo?z+UR-rOk-jIV33afI74)it%n?gM ziK7X@3?15=&9k_=y(Yw?Sj{s}Cv(+Oc3WiLoD$F#*}K0HzYg1dBnqSqL+W${p~>;V z(n5N69y<~50n<^i(9}0@F9MOCQ}Z>=r!pvQl70ZdFvktv)>(%Ot>QGWjx<1@)(L1o zDQt^b!M?K5?J38L5{~t^#EU+;Q3I`5a^sh;y8--aGhP|?3+dr{cHZ-V>XUl_$jYkC zOnu*yDb3dp2U0Tonc^R~o)~#E_;{ivLm7BrH)$@B2br$WTbtz?fs1f^Nh>Eds;)|_ z(oJ603@2^Ni=%KmwSAR7S-6^kNrM4KJ(bjU71ZcS=y#p&9B-D->Yn;Mn;ig76^>rS zwAedp?~`)(P3ye@RjcnvX;_3CT;yy6x&QAS=fP#GQH1O(9I z!bU70D?xkk`-jMJ{9jR2d_Q~s!9rL5o3)RB!hF9DW&IbKftJEph7`z%M8>|i$lF_O z6SU1v%9Tt3h4-lGr>=vVy>O68&o!zJ>(d_2#|>hj%I8QEhbGz*L0Z}S_~`CckXa|QMC zaumy)W=(N+ghCK(oug`&0kW~ILA!?QZ2g{sXJyZ1u~eiCih(l253e+oW?&t8Ow32I z2&}`q7#kn;UQy&sUg>*z4<`qU)$b zxZ`Wlm^k)?&Rkh+pJpN(Su6??XY6}HNh|^!lUan&v9g5H>aMYXm`{yaaf*$N$%U=N zy^?w2#eBFPw(_V2bq&4b$0Ex<#A%K}bR{*lcp1N2xoXGFx}m(m2?o92>d$Q| zW2prxGMj3VP*;DU-_8sKlP{0{Cl6fUzXR8rM`NV4XmcLq+WF>a3Nt58l6(QjMj%`# zI!Qa!Ri|0tD=!U9mATZULA2H^wRc4`A@nscwq_=ZKy}vesO5 zX;ludwPt4iJ=7vJpDy$bVSp*c_!@RMkAI#Ba?28+^v z1o<%fH-i>v2K`CY#zFQd(m&Uu1=P9TD6enI7t$O#9I43QAE@$S^a5WruhVs34$RRJ zR74hDx|MnrT|Eor;UO6E-Orcr!e{rAN4HK=BCt+2MDdtXrJROoBc%*q^Tu8#DiFs9 z+qO(`cr!nWIzV`;`9SV~X+uPZ4*!fZ-lINz^6)hHMwRQvYgpGuYQQ4O!cL$sI5;t&u;I6H9e2_0J{xcLmmiAa@bABe+{N@k8jKPwKxvUVFwGLncEbKE&4NR# zRgj~;ZykeWYNBKl@;xq`eSezY`049w99F+<_4T_$AJ8Yo0GvkHKBy^0ybI6TtVL^Y ze{_&?!p~c%LGPMZ#fslYHTYeJXT~y`x53y|tH9Ee+h!Ei zF0dGF(SPEzyEpxl^@T8a;hTxEUoH-t|IM9C9(jyPLGS1~I6F3afyG_&;*x#EXZe`Pt z0ZBauB1YrUJZ=<;5#&gR)ZtOwh37nov!m736%OyRuVSx`UARgj5zgCReGKB<%>nAJbltA{0t z*6opZ1;&ZGUW82QL4l z)J-Gynl(@i+b2KU2tW!hPSSvUo{uN4@;J_~h{bA^+>DA^s_Vv9JQ`&J_B+3K*6UkN zYY7KB*Z0B)kocUYlkMVWM@nmv)01~_9TtmGr_!uJ%|rUlp}yw?L`U z<^Y;B(uOX5lF>VzWJouE-9-0g(Dlk*_c7|Jdoiy{k*x*5A4gQpY0!E4T>#02M*)$RQ_x uotWFn-pL87d^JGXsa$pFCL_M&?z#QDzw~eK-|tQXKi``F$^qaXC;tOyFGLjp literal 0 HcmV?d00001 From da73e5736dce264c2294a8b39dc674ad29659bc6 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 24 Aug 2022 11:39:55 +0800 Subject: [PATCH 18/61] Adds draw.io file for deployment sequence diagrams - in case they need later adjustment. --- docs/images/DeploymentOptionsSeqDiagrams.drawio | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/images/DeploymentOptionsSeqDiagrams.drawio diff --git a/docs/images/DeploymentOptionsSeqDiagrams.drawio b/docs/images/DeploymentOptionsSeqDiagrams.drawio new file mode 100644 index 000000000..bc1a48e1d --- /dev/null +++ b/docs/images/DeploymentOptionsSeqDiagrams.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file From 395fa05a8cddb0c2e64d35369930e3b0e4d43189 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 20 Oct 2022 15:39:36 +0800 Subject: [PATCH 19/61] Initial attempt at porting dynamic checking to FastAPI backend. --- daliuge-translator/dlg/dropmake/web/main.js | 2 +- .../dlg/dropmake/web/translator_rest.py | 36 ++++++++++++++++++- .../dlg/dropmake/web/translator_utils.py | 30 ++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index b0d9f1864..d06f3f5a3 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -731,7 +731,7 @@ async function restDeploy() { // const create_session_url = manager_url + "/api/sessions"; // const append_graph_url = manager_url + "/api/sessions/" + sessionId + "/graph/append"; // const deploy_graph_url = manager_url + "/api/sessions/" + sessionId + "/deploy"; - // const mgr_url = manager_url + "/session?sessionId=" + sessionId; + // const dlg_mgr_url = manager_url + "/session?sessionId=" + sessionId; // fetch the PGT from this server console.log("sending request to ", pgt_url); diff --git a/daliuge-translator/dlg/dropmake/web/translator_rest.py b/daliuge-translator/dlg/dropmake/web/translator_rest.py index 674130d8f..97e8e3f72 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_rest.py +++ b/daliuge-translator/dlg/dropmake/web/translator_rest.py @@ -51,12 +51,13 @@ from dlg.common.reproducibility.constants import REPRO_DEFAULT, ALL_RMODES, ReproducibilityFlags from dlg.common.reproducibility.reproducibility import init_lgt_repro_data, init_lg_repro_data, \ init_pgt_partition_repro_data, init_pgt_unroll_repro_data, init_pg_repro_data +from dlg.deploy.deployment_utils import check_k8s_env from dlg.dropmake.lg import GraphException from dlg.dropmake.pg_manager import PGManager from dlg.dropmake.scheduler import SchedulerException from dlg.dropmake.web.translator_utils import file_as_string, lg_repo_contents, lg_path, lg_exists, \ pgt_exists, pgt_path, pgt_repo_contents, prepare_lgt, unroll_and_partition_with_params, \ - make_algo_param_dict + make_algo_param_dict, check_mgr_avail, parse_mgr_url APP_DESCRIPTION = """ DALiuGE LG Web interface translates and deploys logical graphs. @@ -793,6 +794,34 @@ def pgt_map( return JSONResponse(pg) +@app.get("/api/submission_method") +def get_submission_method( + dlg_mgr_url: str = Query(default=None, + description="If present, translator will query this URL for its deployment options"), + dlg_mgr_host: str = Query(default=None, + description="If present with mport and mprefix, will be the base host for deployment"), + dlg_mgr_port: int = Query(default=None, + description="") +): + logger.debug("Received submission_method request") + if dlg_mgr_url: + mhost, mport, mprefix = parse_mgr_url(dlg_mgr_url) + else: + mhost = dlg_mgr_host + mport = dlg_mgr_port + mprefix = "" + available_methods = [] + if check_k8s_env(): + available_methods.append("HELM") + if mhost is not None: + host_available_methods = check_mgr_avail(mhost, mport, mprefix) + if host_available_methods: + if "BROWSER" in host_available_methods["methods"]: + available_methods.extend(["SERVER"]) + print(available_methods) + return {"methods": available_methods} + + @app.get("/", response_class=HTMLResponse, description="The page used to view physical graphs") def index(request: Request): tpl = templates.TemplateResponse("pg_viewer.html", { @@ -805,6 +834,11 @@ def index(request: Request): return tpl +@app.head("/") +def liveliness(): + return {} + + def run(_, args): """ FastAPI implementation of daliuge translator interface diff --git a/daliuge-translator/dlg/dropmake/web/translator_utils.py b/daliuge-translator/dlg/dropmake/web/translator_utils.py index a545b089d..591644688 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_utils.py +++ b/daliuge-translator/dlg/dropmake/web/translator_utils.py @@ -1,8 +1,10 @@ import os import logging import pkg_resources +from rfc3986 import urlparse from dlg import common +from dlg.clients import CompositeManagerClient from dlg.common.reproducibility.reproducibility import init_lg_repro_data, init_lgt_repro_data, \ init_pgt_unroll_repro_data, init_pgt_partition_repro_data from dlg.dropmake.lg import load_lg @@ -84,6 +86,34 @@ def filter_dict_to_algo_params(input_dict: dict): return algo_params +def check_mgr_avail(mhost, mport, mprefix): + mgr_client = CompositeManagerClient( + host=mhost, port=mport, url_prefix=mprefix, timeout=15 + ) + response = mgr_client.get_submission_method() + return response + + +def parse_mgr_url(mgr_url): + print(mgr_url) + mport = -1 + mparse = urlparse(mgr_url) + print(mparse) + if mparse.scheme == "http": + mport = 80 + elif mparse.scheme == "https": + mport = 443 + if mparse.port is not None: + mport = mparse.port + mprefix = mparse.path + if mprefix is not None: + if mprefix.endswith("/"): + mprefix = mprefix[:-1] + else: + mprefix = "" + return mparse.host, mport, mprefix + + def make_algo_param_dict(min_goal, ptype, max_load_imb, max_cpu, time_greedy, deadline, topk, swam_size, max_mem): return { "min_goal": min_goal, From 97a4890608566cebcb5cceb584ecb3de92f205ee Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 24 Oct 2022 13:48:24 +0800 Subject: [PATCH 20/61] Adds rfc3986 to translator setup.py requirements. --- daliuge-translator/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daliuge-translator/setup.py b/daliuge-translator/setup.py index 91d797fd0..4918d5c30 100644 --- a/daliuge-translator/setup.py +++ b/daliuge-translator/setup.py @@ -104,6 +104,7 @@ def package_files(directory): "pyswarm", "python-multipart", # "ruamel.yaml.clib<=0.2.2", + "rfc3986" "uvicorn", "wheel", ] From 4ffca2f58189eca6a04e958496a00b5775a6f0c7 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 24 Oct 2022 13:50:22 +0800 Subject: [PATCH 21/61] Adds missing comma to setup.py --- daliuge-translator/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-translator/setup.py b/daliuge-translator/setup.py index 4918d5c30..a1648643d 100644 --- a/daliuge-translator/setup.py +++ b/daliuge-translator/setup.py @@ -104,7 +104,7 @@ def package_files(directory): "pyswarm", "python-multipart", # "ruamel.yaml.clib<=0.2.2", - "rfc3986" + "rfc3986", "uvicorn", "wheel", ] From 50bc9d6264e3cf1eda81ca375ca1e91b69ec9d9f Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 24 Oct 2022 14:54:30 +0800 Subject: [PATCH 22/61] Changes 127.0.0.1 to localhost in test_lgweb.py --- daliuge-translator/test/dropmake/test_lgweb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 43843433a..65596c942 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -49,7 +49,7 @@ def setUp(self): "-p", str(lgweb_port), "-H", - "127.0.0.1", + "localhost", ] self.devnull = open(os.devnull, "wb") self.web_proc = tool.start_process( From 241a8f86e0a82b2d74e0ec2a47bef6ecbf0db0e1 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 24 Oct 2022 14:54:54 +0800 Subject: [PATCH 23/61] Moves k8s environment check to new common file. --- daliuge-common/dlg/common/k8s_utils.py | 40 +++++++++++++++++++ daliuge-engine/dlg/deploy/deployment_utils.py | 15 ------- daliuge-engine/dlg/deploy/helm_client.py | 2 +- .../test/deploy/test_helm_deploy.py | 2 +- .../dlg/dropmake/web/translator_rest.py | 3 +- 5 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 daliuge-common/dlg/common/k8s_utils.py diff --git a/daliuge-common/dlg/common/k8s_utils.py b/daliuge-common/dlg/common/k8s_utils.py new file mode 100644 index 000000000..a764c263b --- /dev/null +++ b/daliuge-common/dlg/common/k8s_utils.py @@ -0,0 +1,40 @@ +# +# ICRAR - International Centre for Radio Astronomy Research +# (c) UWA - The University of Western Australia, 2016 +# Copyright by UWA (in the framework of the ICRAR) +# All rights reserved +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + + +import re +import subprocess + + +def check_k8s_env(): + """ + Makes sure kubectl can be called and is accessible. + """ + try: + output = subprocess.run( + ["kubectl version"], capture_output=True, shell=True + ).stdout + output = output.decode(encoding="utf-8").replace("\n", "") + pattern = re.compile(r"^Client Version:.*Server Version:.*") + return re.match(pattern, output) + except subprocess.SubprocessError: + return False diff --git a/daliuge-engine/dlg/deploy/deployment_utils.py b/daliuge-engine/dlg/deploy/deployment_utils.py index 2e5af77a6..ea6a520c0 100644 --- a/daliuge-engine/dlg/deploy/deployment_utils.py +++ b/daliuge-engine/dlg/deploy/deployment_utils.py @@ -113,21 +113,6 @@ def list_as_string(s): return _parse_list_tokens(iter(_list_tokenizer(s))) -def check_k8s_env(): - """ - Makes sure kubectl can be called and is accessible. - """ - try: - output = subprocess.run( - ["kubectl version"], capture_output=True, shell=True - ).stdout - output = output.decode(encoding="utf-8").replace("\n", "") - pattern = re.compile(r"^Client Version:.*Server Version:.*") - return re.match(pattern, output) - except subprocess.SubprocessError: - return False - - def find_numislands(physical_graph_template_file): """ Given the physical graph data extract the graph name and the total number of diff --git a/daliuge-engine/dlg/deploy/helm_client.py b/daliuge-engine/dlg/deploy/helm_client.py index 077da9fd0..ddf3ed641 100644 --- a/daliuge-engine/dlg/deploy/helm_client.py +++ b/daliuge-engine/dlg/deploy/helm_client.py @@ -42,10 +42,10 @@ find_service_ips, find_pod_ips, wait_for_pods, - check_k8s_env, ) from dlg.dropmake import pg_generator from dlg.restutils import RestClient +from dlg.common.k8s_utils import check_k8s_env logger = logging.getLogger(__name__) diff --git a/daliuge-engine/test/deploy/test_helm_deploy.py b/daliuge-engine/test/deploy/test_helm_deploy.py index e0a449782..ef26d7d6b 100644 --- a/daliuge-engine/test/deploy/test_helm_deploy.py +++ b/daliuge-engine/test/deploy/test_helm_deploy.py @@ -29,7 +29,7 @@ import unittest import yaml -from dlg.deploy.deployment_utils import check_k8s_env +from dlg.common.k8s_utils import check_k8s_env if check_k8s_env(): from dlg.common.version import version as dlg_version diff --git a/daliuge-translator/dlg/dropmake/web/translator_rest.py b/daliuge-translator/dlg/dropmake/web/translator_rest.py index 15fc76e77..34a1982e8 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_rest.py +++ b/daliuge-translator/dlg/dropmake/web/translator_rest.py @@ -51,7 +51,7 @@ from dlg.common.reproducibility.constants import REPRO_DEFAULT, ALL_RMODES, ReproducibilityFlags from dlg.common.reproducibility.reproducibility import init_lgt_repro_data, init_lg_repro_data, \ init_pgt_partition_repro_data, init_pgt_unroll_repro_data, init_pg_repro_data -from dlg.deploy.deployment_utils import check_k8s_env +from dlg.common.k8s_utils import check_k8s_env from dlg.dropmake.lg import GraphException from dlg.dropmake.pg_manager import PGManager from dlg.dropmake.scheduler import SchedulerException @@ -818,7 +818,6 @@ def get_submission_method( if host_available_methods: if "BROWSER" in host_available_methods["methods"]: available_methods.extend(["SERVER"]) - print(available_methods) return {"methods": available_methods} From fc4f9057491e60bcda4a022d943729aa8063627d Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 25 Oct 2022 15:00:08 +0800 Subject: [PATCH 24/61] Ensures compatibility with endpoints not supporting the /api/submission_methods call (allows for manual override) --- daliuge-translator/dlg/dropmake/web/main.js | 27 +++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index d06f3f5a3..2350b3626 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -208,6 +208,9 @@ async function checkUrlSubmissionMethods(url) { success: function (response) { resolve(response) }, + error: function (jqXHR, textStatus, errorThrown) { + resolve({"methods": []}) + }, timeout: 2000 }); }) @@ -396,7 +399,6 @@ function fillOutSettings() { deployMethodManagerDiv.empty() console.log("filling out settings, GET Errors and Cors warning from Url check") deployMethodsArray.forEach(async element => { - let i; const urlReachable = await checkUrlStatus(element.url); const directlyAvailableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method'); @@ -411,17 +413,22 @@ function fillOutSettings() { const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); const availableOptions = []; - console.log(allAvailableMethods); - for (i = 0; i < allAvailableMethods.length; i++) { - const deploy_option = allAvailableMethods[i]; - if (element.deployMethod !== "undefined") { - // If a choice has already been made, go with that. - availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); - } else { - // By default, select the first listed. - availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); + const default_options = ["SERVER", "BROWSER", "HELM", "OOD"]; + if (allAvailableMethods.length === 0){ // Support backend without submission/method api + default_options.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) + } else { + for (i = 0; i < allAvailableMethods.length; i++) { + const deploy_option = allAvailableMethods[i]; + if (element.deployMethod !== "undefined") { + // If a choice has already been made, go with that. + availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); + } else { + // By default, select the first listed. + availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); + } } } + console.log(availableOptions); let deplpoyMethodRow = '

' + '
' + `
` + From 4073705db3baac17aac7f275e3bd3efd8af222e8 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 25 Oct 2022 15:00:29 +0800 Subject: [PATCH 25/61] Makes translator to manager calls error resiliant --- .../dlg/dropmake/web/translator_utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/translator_utils.py b/daliuge-translator/dlg/dropmake/web/translator_utils.py index 591644688..dffcde8f5 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_utils.py +++ b/daliuge-translator/dlg/dropmake/web/translator_utils.py @@ -9,7 +9,7 @@ init_pgt_unroll_repro_data, init_pgt_partition_repro_data from dlg.dropmake.lg import load_lg from dlg.dropmake.pg_generator import unroll, partition - +from dlg.restutils import RestClientException logger = logging.getLogger(__name__) @@ -87,10 +87,14 @@ def filter_dict_to_algo_params(input_dict: dict): def check_mgr_avail(mhost, mport, mprefix): - mgr_client = CompositeManagerClient( - host=mhost, port=mport, url_prefix=mprefix, timeout=15 - ) - response = mgr_client.get_submission_method() + try: + mgr_client = CompositeManagerClient( + host=mhost, port=mport, url_prefix=mprefix, timeout=15 + ) + response = mgr_client.get_submission_method() + except RestClientException: + logger.debug("Cannot connect to manager object at endpoint %s:%d", mhost, mport) + response = None return response From 76cc3d9f6f8185cb786c60142f0560190102901d Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Tue, 25 Oct 2022 15:01:03 +0800 Subject: [PATCH 26/61] Gaierrors raised from bogus IPs are logged and handled. --- daliuge-common/dlg/common/network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daliuge-common/dlg/common/network.py b/daliuge-common/dlg/common/network.py index 1d2efbd32..9daf0cfbe 100644 --- a/daliuge-common/dlg/common/network.py +++ b/daliuge-common/dlg/common/network.py @@ -95,6 +95,10 @@ def check_port(host, port, timeout=0, checking_open=True, return_socket=False): ) return not checking_open + except socket.gaierror: + logger.debug("Endpoint service %s:%d not known", host, port) + return not checking_open + except socket.error as e: # If the connection becomes suddenly closed from the server-side. From a8f1afcf8ef9678a8d4f9630e1caeb17f23d02e1 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 11:59:30 +0800 Subject: [PATCH 27/61] Adds default case for method selection. --- daliuge-translator/dlg/dropmake/web/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 2350b3626..2991a149a 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -356,6 +356,9 @@ function buildDeployMethodEntry(method, selected) { case "HELM": displayValue = "Helm"; break; + default: + displayValue = "Server"; + break; } if (selected) { return `` From c0e02cda98fe132e4953014cbb394e22a10d18a6 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 12:00:23 +0800 Subject: [PATCH 28/61] Typo fix for deplpoy --- daliuge-translator/dlg/dropmake/web/main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 2991a149a..c6ee55bd0 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -432,26 +432,26 @@ function fillOutSettings() { } } console.log(availableOptions); - let deplpoyMethodRow = '
' + + let deployMethodRow = '
' + '
' + `
` + ReachableIcon + '
' + '' + '' + '
' - deployMethodManagerDiv.append(deplpoyMethodRow) + deployMethodManagerDiv.append(deployMethodRow) }); } function addDeployMethod() { const deployMethodManagerDiv = $("#DeployMethodManager"); - const deplpoyMethodRow = '
' + + const deployMethodRow = '
' + '
' + `
` + `` + @@ -460,7 +460,7 @@ function addDeployMethod() { '' + '' + '
'; - deployMethodManagerDiv.append(deplpoyMethodRow) + deployMethodManagerDiv.append(deployMethodRow) } function removeDeployMethod(e) { From c44121a846a5174e3b37358b95ad3235f06deed0 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 12:15:31 +0800 Subject: [PATCH 29/61] Removes some of the more verbose console logs. Changes console logs to reflect their urgency. --- daliuge-translator/dlg/dropmake/web/main.js | 72 ++++++++++----------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index c6ee55bd0..e88059f48 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -168,11 +168,11 @@ function updateDeployOptionsDropdown() { const newHost = newUrl.hostname; const newPrefix = newUrl.pathname; const newProtocol = newUrl.protocol; - console.log("URL set to:'" + newUrl + "'"); - console.log("Protocol set to:'" + newProtocol + "'"); - console.log("Host set to:'" + newHost + "'"); - console.log("Port set to:'" + newPort + "'"); - console.log("Prefix set to:'" + newPrefix + "'"); + console.debug("URL set to:'" + newUrl + "'"); + console.debug("Protocol set to:'" + newProtocol + "'"); + console.debug("Host set to:'" + newHost + "'"); + console.debug("Port set to:'" + newPort + "'"); + console.debug("Prefix set to:'" + newPrefix + "'"); window.localStorage.setItem("manager_url", newUrl); window.localStorage.setItem("manager_protocol", newProtocol); @@ -287,7 +287,7 @@ function saveSettings() { try { new URL($(this).find(".deployMethodUrl").val()); } catch (error) { - console.log("faulty Url: ", $(this).find(".deployMethodUrl").val()) + console.error("faulty Url: ", $(this).find(".deployMethodUrl").val()) badUrl = true } @@ -321,7 +321,7 @@ function saveSettings() { deployMethod: $(this).find(".deployMethodMethod option:selected").val(), active: $(this).find(".deployMethodActive").val() } - console.log($(this).find(".deployMethodMethod option:selected").val()) + console.debug($(this).find(".deployMethodMethod option:selected").val()) deployMethodsArray.push(deployMethod) } }) @@ -400,7 +400,7 @@ function fillOutSettings() { //fill out settings list rom deploy methods array const deployMethodManagerDiv = $("#DeployMethodManager"); deployMethodManagerDiv.empty() - console.log("filling out settings, GET Errors and Cors warning from Url check") + console.debug("filling out settings, GET Errors and Cors warning from Url check") deployMethodsArray.forEach(async element => { let i; const urlReachable = await checkUrlStatus(element.url); @@ -431,14 +431,13 @@ function fillOutSettings() { } } } - console.log(availableOptions); let deployMethodRow = '
' + '
' + `
` + ReachableIcon + '
' + @@ -468,7 +467,7 @@ function removeDeployMethod(e) { } function makeJSON() { - console.log("makeJSON()"); + console.info("makeJSON()"); $.ajax({ url: "/pgt_jsonbody?pgt_name=" + pgtName, @@ -553,7 +552,7 @@ async function directRestDeploy() { } let manager_url = new URL(murl); - console.log("In Direct REST Deploy"); + console.info("In Direct REST Deploy"); const manager_host = manager_url.hostname; const manager_port = manager_url.port; @@ -567,17 +566,17 @@ async function directRestDeploy() { if (manager_prefix.endsWith('/')) { manager_prefix = manager_prefix.substring(0, manager_prefix.length - 1); } - console.log("Manager URL:'" + manager_url + "'"); - console.log("Manager host:'" + manager_host + "'"); - console.log("Manager port:'" + manager_port + "'"); - console.log("Manager prefix:'" + manager_prefix + "'"); - console.log("Request mode:'" + request_mode + "'"); + console.debug("Manager URL:'" + manager_url + "'"); + console.debug("Manager host:'" + manager_host + "'"); + console.debug("Manager port:'" + manager_port + "'"); + console.debug("Manager prefix:'" + manager_prefix + "'"); + console.debug("Request mode:'" + request_mode + "'"); // sessionId must be unique or the request will fail const lgName = pgtName.substring(0, pgtName.lastIndexOf("_pgt.graph")); const sessionId = lgName + "-" + Date.now(); - console.log("sessionId:'" + sessionId + "'"); + console.debug("sessionId:'" + sessionId + "'"); const nodes_url = manager_url + "/api/nodes"; @@ -590,11 +589,10 @@ async function directRestDeploy() { .catch(function (error) { showMessageModal('Error', error + "\nGetting Nodes unsuccessful"); }) - console.log(nodes) const pgt_url = "/gen_pg?tpl_nodes_len=" + nodes.length.toString() + "&pgt_id=" + pgtName; - console.log("sending request to ", pgt_url); - console.log("graph name:", pgtName); + console.debug("sending request to ", pgt_url); + console.debug("graph name:", pgtName); await fetch(pgt_url, { method: 'GET', }) @@ -604,14 +602,13 @@ async function directRestDeploy() { showMessageModal('Error', error + "\nGetting PGT unsuccessful: Unable to continue!"); }); - console.log("node_list", nodes); + console.debug("node_list", nodes); const pg_spec_request_data = { manager_host: manager_host, node_list: nodes, pgt_id: pgt_id } - console.log(pg_spec_request_data); // request pg_spec from translator const pg_spec_url = "/gen_pg_spec"; const pg_spec_response = await fetch(pg_spec_url, { @@ -645,12 +642,11 @@ async function directRestDeploy() { .catch(function (error) { showMessageModal('Error', error + "\nCreating session unsuccessful: Unable to continue!"); }); - console.log("create session response", create_session); + console.debug("create session response", create_session); // gzip the pg_spec - console.log(pg_spec_response.pg_spec); const buf = fflate.strToU8(JSON.stringify(pg_spec_response.pg_spec)); const compressed_pg_spec = fflate.zlibSync(buf); - console.log("compressed_pg_spec", compressed_pg_spec); + console.debug("compressed_pg_spec", compressed_pg_spec); // append graph to session on engine const append_graph_url = manager_url + "/api/sessions/" + sessionId + "/graph/append"; @@ -672,7 +668,7 @@ async function directRestDeploy() { .catch(function (error) { showMessageModal('Error', error + "\nUnable to continue!"); }); - console.log("append graph response", append_graph); + console.debug("append graph response", append_graph); // deploy graph // NOTE: URLSearchParams here turns the object into a x-www-form-urlencoded form const deploy_graph_url = manager_url + "/api/sessions/" + sessionId + "/deploy"; @@ -691,7 +687,6 @@ async function directRestDeploy() { }); //showMessageModal("Chart deployed" , "Check the dashboard of your k8s cluster for status updates."); const mgr_url = manager_url + "/session?sessionId=" + sessionId; - console.log("deploy graph response", deploy_graph); window.open(mgr_url, '_blank').focus(); } @@ -707,7 +702,7 @@ async function restDeploy() { }) } let manager_url = new URL(murl); - console.log("In REST Deploy") + console.info("In REST Deploy") const manager_host = manager_url.hostname; const manager_port = manager_url.port; @@ -721,16 +716,16 @@ async function restDeploy() { if (manager_prefix.endsWith('/')) { manager_prefix = manager_prefix.substring(0, manager_prefix.length - 1); } - console.log("Manager URL:'" + manager_url + "'"); - console.log("Manager host:'" + manager_host + "'"); - console.log("Manager port:'" + manager_port + "'"); - console.log("Manager prefix:'" + manager_prefix + "'"); - console.log("Request mode:'" + request_mode + "'"); + console.debug("Manager URL:'" + manager_url + "'"); + console.debug("Manager host:'" + manager_host + "'"); + console.debug("Manager port:'" + manager_port + "'"); + console.debug("Manager prefix:'" + manager_prefix + "'"); + console.debug("Request mode:'" + request_mode + "'"); // sessionId must be unique or the request will fail const lgName = pgtName.substring(0, pgtName.lastIndexOf("_pgt.graph")); const sessionId = lgName + "-" + Date.now(); - console.log("sessionId:'" + sessionId + "'"); + console.debug("sessionId:'" + sessionId + "'"); // build urls // the manager_url in this case has to point to daliuge_ood @@ -744,8 +739,8 @@ async function restDeploy() { // const dlg_mgr_url = manager_url + "/session?sessionId=" + sessionId; // fetch the PGT from this server - console.log("sending request to ", pgt_url); - console.log("graph name:", pgtName); + console.debug("sending request to ", pgt_url); + console.debug("graph name:", pgtName); let pgt = await fetch(pgt_url, { method: 'GET', }) @@ -756,9 +751,8 @@ async function restDeploy() { }); pgt = JSON.parse(pgt); // This is for a deferred start of daliuge, e.g. on SLURM - console.log("sending request to ", create_slurm_url); + console.debug("sending request to ", create_slurm_url); var body = [pgtName, pgt]; // we send the name in the body with the pgt - // console.log("Sending PGT with name:", body); const slurm_script = await fetch(create_slurm_url, { method: 'POST', credentials: 'include', From c880f7596e1d1eefc31b144abc176082f366447b Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 13:43:13 +0800 Subject: [PATCH 30/61] Changes length string concatenations to interpolations. Makes variable interpolations into console log consistent. Removes unnecessary commented code --- daliuge-translator/dlg/dropmake/web/main.js | 152 +++++++++----------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index e88059f48..ce2356059 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -45,12 +45,7 @@ $(document).ready(function () { //fill out keyboard shortcuts modal keyboardShortcuts.forEach(element => { - const shortCutItem = '
' + - '
' + - '' + element.name + '' + - '' + element.shortcut + '' + - '
' + - '
'; + const shortCutItem = `
${element.name}${element.shortcut}
`; $("#shortcutsModal .modal-body .row").append(shortCutItem) }) @@ -69,11 +64,19 @@ $(document).ready(function () { }) }); +function consoleDebugUrl(url){ + console.debug("URL set to: ", url); + console.debug("Protocol set to: ", url.protocol); + console.debug("Host set to: ", url.hostname); + console.debug("Port set to: ", url.port); + console.debug("Prefix set to: ", url.pathname); +} + function getCurrentPageUrl() { const pathElements = window.location.href.split('/'); const protocol = pathElements[0]; const host = pathElements[2]; - return protocol + '//' + host; + return `${protocol}//${host}`; } function openSettingsModal() { @@ -142,13 +145,22 @@ function updateDeployOptionsDropdown() { if (element.active === "false") { //dropdown options $("#deployDropdowns .dropdown-menu").prepend( - `` + element.name + `` + `${element.name}` ) } else { selectedUrl = element.url //active option $("#deployDropdowns").prepend( - `Deploy: ` + element.name + `` + `Deploy: ${element.name}` ) checkActiveDeployMethod(selectedUrl) } @@ -156,30 +168,26 @@ function updateDeployOptionsDropdown() { }) - if(selectedUrl === undefined){ + if (selectedUrl === undefined) { $("#deployDropdowns").prepend( - `Add Deploy Method` + `Add Deploy Method` ) return } const newUrl = new URL(selectedUrl); - const newPort = newUrl.port; - const newHost = newUrl.hostname; - const newPrefix = newUrl.pathname; - const newProtocol = newUrl.protocol; - console.debug("URL set to:'" + newUrl + "'"); - console.debug("Protocol set to:'" + newProtocol + "'"); - console.debug("Host set to:'" + newHost + "'"); - console.debug("Port set to:'" + newPort + "'"); - console.debug("Prefix set to:'" + newPrefix + "'"); - + consoleDebugUrl(newUrl); + // TODO: Why are we storing the object and then a copy of its fields? window.localStorage.setItem("manager_url", newUrl); - window.localStorage.setItem("manager_protocol", newProtocol); - window.localStorage.setItem("manager_host", newHost); - window.localStorage.setItem("manager_port", newPort); - window.localStorage.setItem("manager_prefix", newPrefix); - + window.localStorage.setItem("manager_protocol", newUrl.protocol); + window.localStorage.setItem("manager_host", newUrl.host); + window.localStorage.setItem("manager_port", newUrl.port); + window.localStorage.setItem("manager_prefix", newUrl.pathname); } async function checkUrlStatus(url) { @@ -209,7 +217,7 @@ async function checkUrlSubmissionMethods(url) { resolve(response) }, error: function (jqXHR, textStatus, errorThrown) { - resolve({"methods": []}) + resolve({"methods": []}) }, timeout: 2000 }); @@ -416,8 +424,9 @@ function fillOutSettings() { const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); const availableOptions = []; + // TODO: move magic strings to object/enum const default_options = ["SERVER", "BROWSER", "HELM", "OOD"]; - if (allAvailableMethods.length === 0){ // Support backend without submission/method api + if (allAvailableMethods.length === 0) { // Support backend without submission/method api default_options.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) } else { for (i = 0; i < allAvailableMethods.length; i++) { @@ -431,34 +440,33 @@ function fillOutSettings() { } } } - let deployMethodRow = '
' + - '
' + - `
` + - ReachableIcon + - '
+
+ ${ReachableIcon}
' + - '' + - '' + - '
' + `
+ + +
` deployMethodManagerDiv.append(deployMethodRow) }); } function addDeployMethod() { const deployMethodManagerDiv = $("#DeployMethodManager"); - const deployMethodRow = '
' + - '
' + - `
` + - `` + - '
' + - '' + - '' + - '
'; + const deployMethodRow = `
+
+
+ +
+ + +
`; deployMethodManagerDiv.append(deployMethodRow) } @@ -474,9 +482,9 @@ function makeJSON() { type: 'get', error: function (XMLHttpRequest) { if (404 === XMLHttpRequest.status) { - console.error('Server cannot locate physical graph file ' + pgtName); + console.error('Server cannot locate physical graph file ', pgtName); } else { - console.error('status:' + XMLHttpRequest.status + ', status text: ' + XMLHttpRequest.statusText); + console.error(`status: ${XMLHttpRequest.status}, status text: ${XMLHttpRequest.statusText}`); } }, success: function (data) { @@ -555,28 +563,18 @@ async function directRestDeploy() { console.info("In Direct REST Deploy"); const manager_host = manager_url.hostname; - const manager_port = manager_url.port; - let manager_prefix = manager_url.pathname; const request_mode = "cors"; const pgt_id = $("#pg_form input[name='pgt_id']").val(); manager_url = manager_url.toString(); if (manager_url.endsWith('/')) { manager_url = manager_url.substring(0, manager_url.length - 1); } - if (manager_prefix.endsWith('/')) { - manager_prefix = manager_prefix.substring(0, manager_prefix.length - 1); - } - console.debug("Manager URL:'" + manager_url + "'"); - console.debug("Manager host:'" + manager_host + "'"); - console.debug("Manager port:'" + manager_port + "'"); - console.debug("Manager prefix:'" + manager_prefix + "'"); - console.debug("Request mode:'" + request_mode + "'"); - + consoleDebugUrl(manager_url); // sessionId must be unique or the request will fail const lgName = pgtName.substring(0, pgtName.lastIndexOf("_pgt.graph")); const sessionId = lgName + "-" + Date.now(); - console.debug("sessionId:'" + sessionId + "'"); + console.debug("sessionId:", sessionId); const nodes_url = manager_url + "/api/nodes"; @@ -587,19 +585,19 @@ async function directRestDeploy() { .then(handleFetchErrors) .then(response => response.json()) .catch(function (error) { - showMessageModal('Error', error + "\nGetting Nodes unsuccessful"); + showMessageModal(`Error ${error}\nGetting nodes unsuccessful`); }) const pgt_url = "/gen_pg?tpl_nodes_len=" + nodes.length.toString() + "&pgt_id=" + pgtName; console.debug("sending request to ", pgt_url); - console.debug("graph name:", pgtName); + console.debug("graph name: ", pgtName); await fetch(pgt_url, { method: 'GET', }) .then(handleFetchErrors) .then(response => response.json()) .catch(function (error) { - showMessageModal('Error', error + "\nGetting PGT unsuccessful: Unable to continue!"); + showMessageModal(`Error ${error}\nGetting PGT unsuccessful: Unable to continue!`); }); console.debug("node_list", nodes); @@ -704,39 +702,22 @@ async function restDeploy() { let manager_url = new URL(murl); console.info("In REST Deploy") - const manager_host = manager_url.hostname; - const manager_port = manager_url.port; - let manager_prefix = manager_url.pathname; const request_mode = "cors"; - const pgt_id = $("#pg_form input[name='pgt_id']").val(); manager_url = manager_url.toString(); if (manager_url.endsWith('/')) { manager_url = manager_url.substring(0, manager_url.length - 1); } - if (manager_prefix.endsWith('/')) { - manager_prefix = manager_prefix.substring(0, manager_prefix.length - 1); - } - console.debug("Manager URL:'" + manager_url + "'"); - console.debug("Manager host:'" + manager_host + "'"); - console.debug("Manager port:'" + manager_port + "'"); - console.debug("Manager prefix:'" + manager_prefix + "'"); - console.debug("Request mode:'" + request_mode + "'"); + consoleDebugUrl(manager_url); // sessionId must be unique or the request will fail const lgName = pgtName.substring(0, pgtName.lastIndexOf("_pgt.graph")); - const sessionId = lgName + "-" + Date.now(); - console.debug("sessionId:'" + sessionId + "'"); + const sessionId = `${lgName}-${Date.now()}`; + console.debug("sessionId: ", sessionId); // build urls // the manager_url in this case has to point to daliuge_ood const create_slurm_url = manager_url + "/api/slurm/script"; const pgt_url = "/gen_pg?tpl_nodes_len=1&pgt_id=" + pgtName; // TODO: tpl_nodes_len >= nodes in LG - // const pg_spec_url = "/gen_pg_spec"; - // const node_list_url = manager_url + "/api/nodes"; - // const create_session_url = manager_url + "/api/sessions"; - // const append_graph_url = manager_url + "/api/sessions/" + sessionId + "/graph/append"; - // const deploy_graph_url = manager_url + "/api/sessions/" + sessionId + "/deploy"; - // const dlg_mgr_url = manager_url + "/session?sessionId=" + sessionId; // fetch the PGT from this server console.debug("sending request to ", pgt_url); @@ -753,7 +734,7 @@ async function restDeploy() { // This is for a deferred start of daliuge, e.g. on SLURM console.debug("sending request to ", create_slurm_url); var body = [pgtName, pgt]; // we send the name in the body with the pgt - const slurm_script = await fetch(create_slurm_url, { + await fetch(create_slurm_url, { method: 'POST', credentials: 'include', cache: 'no-cache', @@ -768,11 +749,10 @@ async function restDeploy() { .then(handleFetchErrors) .then(response => { if (response.redirected) { - // window.location.href = response.url; window.open(response.url, 'deploy_target').focus(); } }) .catch(function (error) { - showMessageModal('Error', error + "\nSending PGT to backend unsuccessful!"); + showMessageModal(`Error ${error}\nSending PGT to backend unsuccessful!`); }); } From 6ddcdf8e64221534389f3f9ae8db661576a21e19 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 14:17:47 +0800 Subject: [PATCH 31/61] Removes rfc3986, this should be inluded in urllib --- daliuge-translator/setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daliuge-translator/setup.py b/daliuge-translator/setup.py index a1648643d..91d797fd0 100644 --- a/daliuge-translator/setup.py +++ b/daliuge-translator/setup.py @@ -104,7 +104,6 @@ def package_files(directory): "pyswarm", "python-multipart", # "ruamel.yaml.clib<=0.2.2", - "rfc3986", "uvicorn", "wheel", ] From 685ddcf3fcfc785708e4b5a484638a4ea5bfaf91 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 14:19:45 +0800 Subject: [PATCH 32/61] Replaces magic strings and pseudo enums with constants and frozen dict respectively. --- daliuge-translator/dlg/dropmake/web/main.js | 39 +++++++++++++-------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index ce2356059..bfff7f8ed 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -1,3 +1,13 @@ +const DEFAULT_OPTIONS = Object.freeze({ + SERVER: "SERVER", + BROWSER: "BROWSER", + HELM: "HELM", + OOD: "OOD" +}); + +const DEFAULT_URL = "http://localhost:8001/"; +const DEFAULT_NAME = "default deployment"; + $(document).ready(function () { // jquery starts here //hides the dropdown navbar elements when stopping hovering over the element @@ -64,7 +74,7 @@ $(document).ready(function () { }) }); -function consoleDebugUrl(url){ +function consoleDebugUrl(url) { console.debug("URL set to: ", url); console.debug("Protocol set to: ", url.protocol); console.debug("Host set to: ", url.hostname); @@ -104,17 +114,17 @@ async function initiateDeploy(method, selected, clickedName) { }); return } - if (method === "SERVER") { + if (method === DEFAULT_OPTIONS.SERVER) { $("#gen_pg_button").val("Generate & Deploy Physical Graph") $("#dlg_mgr_deploy").prop("checked", true) $("#pg_form").submit(); - } else if (method === "HELM") { + } if (method === DEFAULT_OPTIONS.HELM) { $("#gen_helm_button").val("Generate & Deploy Physical Graph") $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() - } else if (method === "OOD") { + } else if (method === DEFAULT_OPTIONS.OOD) { await restDeploy() - } else if (method === "BROWSER") { + } else if (method === DEFAULT_OPTIONS.BROWSER) { await directRestDeploy() } } @@ -352,16 +362,16 @@ function saveSettings() { function buildDeployMethodEntry(method, selected) { let displayValue = ""; switch (method) { - case "SERVER": + case DEFAULT_OPTIONS.SERVER: displayValue = "Server"; break; - case "BROWSER": + case DEFAULT_OPTIONS.BROWSER: displayValue = "Browser Direct"; break; - case "OOD": + case DEFAULT_OPTIONS.OOD: displayValue = "OOD"; break; - case "HELM": + case DEFAULT_OPTIONS.HELM: displayValue = "Helm"; break; default: @@ -384,7 +394,7 @@ function fillOutSettings() { //fill settings with saved or default values if (!manager_url) { - $("#managerUrlInput").val("http://localhost:8001"); + $("#managerUrlInput").val(DEFAULT_URL); } else { $("#managerUrlInput").val(manager_url); } @@ -393,9 +403,9 @@ function fillOutSettings() { if (!localStorage.getItem("deployMethods")) { deployMethodsArray = [ { - name: "default deployment", - url: "http://localhost:8001/", - deployMethod: "SERVER", + name: DEFAULT_NAME, + url: DEFAULT_URL, + deployMethod: DEFAULT_OPTIONS.SERVER, active: true } ]; @@ -425,9 +435,8 @@ function fillOutSettings() { const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); const availableOptions = []; // TODO: move magic strings to object/enum - const default_options = ["SERVER", "BROWSER", "HELM", "OOD"]; if (allAvailableMethods.length === 0) { // Support backend without submission/method api - default_options.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) + DEFAULT_OPTIONS.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) } else { for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; From e41c2b2b3d7bc5dc0c0a79bb33c251f2c214b0f0 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 14:57:33 +0800 Subject: [PATCH 33/61] In-case for some arcane reason, this is what's missing. --- daliuge-translator/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daliuge-translator/setup.py b/daliuge-translator/setup.py index 91d797fd0..a1648643d 100644 --- a/daliuge-translator/setup.py +++ b/daliuge-translator/setup.py @@ -104,6 +104,7 @@ def package_files(directory): "pyswarm", "python-multipart", # "ruamel.yaml.clib<=0.2.2", + "rfc3986", "uvicorn", "wheel", ] From c68501a05f724d0fd730b695c3bb1d88f8847ad1 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 15:18:45 +0800 Subject: [PATCH 34/61] Removes rfc3986 again and reverts lg_web tests to 127.0.0.1 --- daliuge-translator/setup.py | 1 - daliuge-translator/test/dropmake/test_lgweb.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/daliuge-translator/setup.py b/daliuge-translator/setup.py index a1648643d..91d797fd0 100644 --- a/daliuge-translator/setup.py +++ b/daliuge-translator/setup.py @@ -104,7 +104,6 @@ def package_files(directory): "pyswarm", "python-multipart", # "ruamel.yaml.clib<=0.2.2", - "rfc3986", "uvicorn", "wheel", ] diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 65596c942..43843433a 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -49,7 +49,7 @@ def setUp(self): "-p", str(lgweb_port), "-H", - "localhost", + "127.0.0.1", ] self.devnull = open(os.devnull, "wb") self.web_proc = tool.start_process( From c45e984acef7a62a6294797853918e720f8ee11b Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 15:29:53 +0800 Subject: [PATCH 35/61] Reverts all referenced lgweb test urls to 127.0.0.1 --- .../test/dropmake/test_lgweb.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 43843433a..7eaf70566 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -69,7 +69,7 @@ def _generate_pgt(self, c): def test_get_lgjson(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # a specific one lg = c._get_json("/jsonbody?lg_name=logical_graphs/chiles_simple.graph") @@ -86,7 +86,7 @@ def test_get_lgjson(self): def test_post_lgjson(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # new graphs cannot currently be added form_data = { @@ -117,7 +117,7 @@ def test_post_lgjson(self): def test_gen_pgt(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # doesn't exist! self.assertRaises( @@ -136,7 +136,7 @@ def test_gen_pgt(self): def test_get_pgtjson(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) c._GET( "/gen_pgt?lg_name=logical_graphs/chiles_simple.graph&num_par=5&algo=metis&min_goal=0&ptype=0&max_load_imb=100" ) @@ -150,7 +150,7 @@ def test_get_pgtjson(self): def test_get_pgt_post(self, algo="metis", algo_options=None): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -185,7 +185,7 @@ def test_get_pgt_post(self, algo="metis", algo_options=None): def test_mkn_pgt_post(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -218,7 +218,7 @@ def test_mkn_pgt_post(self): def test_loop_pgt_post(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -267,7 +267,7 @@ def test_pso_translation(self): def test_pg_viewer(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) self._generate_pgt(c) # doesn't exist @@ -281,7 +281,7 @@ def test_pg_viewer(self): def _test_pgt_action(self, path, unknown_fails): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) self._generate_pgt(c) # doesn't exist @@ -306,7 +306,7 @@ def test_get_gantt_chart(self): def test_get_submission_methods(self): import json - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) response = c._GET("/api/submission_method") response_content = json.load(response) self.assertEqual(response_content, {'methods': []}) @@ -341,7 +341,7 @@ def _test_post_request(self, client: RestClient, url: str, form_data: dict = Non return json.load(ret) def test_get_fill(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) test_url = "/lg_fill" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -362,7 +362,7 @@ def test_get_fill(self): self._test_post_request(c, test_url, request[0], request[1]) def test_lg_unroll(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) test_url = "/unroll" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -392,7 +392,7 @@ def test_lg_unroll(self): self.assertEqual(dropspec["app"], "test.app") def test_pgt_partition(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) test_url = "/partition" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -416,7 +416,7 @@ def test_pgt_partition(self): self._test_post_request(c, test_url, request[0], request[1]) def test_lg_unroll_and_partition(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) test_url = "/unroll_and_partition" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -437,7 +437,7 @@ def test_lg_unroll_and_partition(self): self._test_post_request(c, test_url, request[0], request[1]) def test_pgt_map(self): - c = RestClient("localhost", lgweb_port, timeout=10) + c = RestClient("127.0.0.1", lgweb_port, timeout=10) test_url = "/map" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -450,7 +450,7 @@ def test_pgt_map(self): request_tests = [ (None, True), # Call with an empty form should cause an error - ({"pgt_content": pgt, "nodes": "localhost", "num_islands": 1, "co_host_dim": True}, False), # Simple partition + ({"pgt_content": pgt, "nodes": "127.0.0.1", "num_islands": 1, "co_host_dim": True}, False), # Simple partition ] for request in request_tests: From 2a1fa8372c291566695b111ca8f85849ebb4abcd Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 15:40:11 +0800 Subject: [PATCH 36/61] Changes to localhost consistently and removes erroneous reference to rfc3986 --- .../dlg/dropmake/web/translator_utils.py | 2 +- .../test/dropmake/test_lgweb.py | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/translator_utils.py b/daliuge-translator/dlg/dropmake/web/translator_utils.py index dffcde8f5..afe583f0a 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_utils.py +++ b/daliuge-translator/dlg/dropmake/web/translator_utils.py @@ -1,7 +1,7 @@ import os import logging import pkg_resources -from rfc3986 import urlparse +from urllib.parse import urlparse from dlg import common from dlg.clients import CompositeManagerClient diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 7eaf70566..65596c942 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -49,7 +49,7 @@ def setUp(self): "-p", str(lgweb_port), "-H", - "127.0.0.1", + "localhost", ] self.devnull = open(os.devnull, "wb") self.web_proc = tool.start_process( @@ -69,7 +69,7 @@ def _generate_pgt(self, c): def test_get_lgjson(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # a specific one lg = c._get_json("/jsonbody?lg_name=logical_graphs/chiles_simple.graph") @@ -86,7 +86,7 @@ def test_get_lgjson(self): def test_post_lgjson(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # new graphs cannot currently be added form_data = { @@ -117,7 +117,7 @@ def test_post_lgjson(self): def test_gen_pgt(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # doesn't exist! self.assertRaises( @@ -136,7 +136,7 @@ def test_gen_pgt(self): def test_get_pgtjson(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) c._GET( "/gen_pgt?lg_name=logical_graphs/chiles_simple.graph&num_par=5&algo=metis&min_goal=0&ptype=0&max_load_imb=100" ) @@ -150,7 +150,7 @@ def test_get_pgtjson(self): def test_get_pgt_post(self, algo="metis", algo_options=None): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -185,7 +185,7 @@ def test_get_pgt_post(self, algo="metis", algo_options=None): def test_mkn_pgt_post(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -218,7 +218,7 @@ def test_mkn_pgt_post(self): def test_loop_pgt_post(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) # an API call with an empty form should cause an error self.assertRaises(RestClientException, c._POST, "/gen_pgt") @@ -267,7 +267,7 @@ def test_pso_translation(self): def test_pg_viewer(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) self._generate_pgt(c) # doesn't exist @@ -281,7 +281,7 @@ def test_pg_viewer(self): def _test_pgt_action(self, path, unknown_fails): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) self._generate_pgt(c) # doesn't exist @@ -306,7 +306,7 @@ def test_get_gantt_chart(self): def test_get_submission_methods(self): import json - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) response = c._GET("/api/submission_method") response_content = json.load(response) self.assertEqual(response_content, {'methods': []}) @@ -341,7 +341,7 @@ def _test_post_request(self, client: RestClient, url: str, form_data: dict = Non return json.load(ret) def test_get_fill(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) test_url = "/lg_fill" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -362,7 +362,7 @@ def test_get_fill(self): self._test_post_request(c, test_url, request[0], request[1]) def test_lg_unroll(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) test_url = "/unroll" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -392,7 +392,7 @@ def test_lg_unroll(self): self.assertEqual(dropspec["app"], "test.app") def test_pgt_partition(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) test_url = "/partition" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -416,7 +416,7 @@ def test_pgt_partition(self): self._test_post_request(c, test_url, request[0], request[1]) def test_lg_unroll_and_partition(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) test_url = "/unroll_and_partition" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -437,7 +437,7 @@ def test_lg_unroll_and_partition(self): self._test_post_request(c, test_url, request[0], request[1]) def test_pgt_map(self): - c = RestClient("127.0.0.1", lgweb_port, timeout=10) + c = RestClient("localhost", lgweb_port, timeout=10) test_url = "/map" with open( os.path.join(lg_dir, "logical_graphs", "testLoop.graph"), "rb" @@ -450,7 +450,7 @@ def test_pgt_map(self): request_tests = [ (None, True), # Call with an empty form should cause an error - ({"pgt_content": pgt, "nodes": "127.0.0.1", "num_islands": 1, "co_host_dim": True}, False), # Simple partition + ({"pgt_content": pgt, "nodes": "localhost", "num_islands": 1, "co_host_dim": True}, False), # Simple partition ] for request in request_tests: From b6061b72d0de1a536da93e681e718d1471a81995 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 15:54:17 +0800 Subject: [PATCH 37/61] Fetches protocol and host directly from window.location - rather than relying on implicit ordering. --- daliuge-translator/dlg/dropmake/web/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index bfff7f8ed..d40705e53 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -83,9 +83,8 @@ function consoleDebugUrl(url) { } function getCurrentPageUrl() { - const pathElements = window.location.href.split('/'); - const protocol = pathElements[0]; - const host = pathElements[2]; + const protocol = window.location.protocol; + const host = window.location.host; return `${protocol}//${host}`; } From 74389ad23999d0aacfe96919c7933e84b886d064 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 16:01:34 +0800 Subject: [PATCH 38/61] Addresses subtle differences in parsed url objects --- daliuge-translator/dlg/dropmake/web/translator_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/translator_utils.py b/daliuge-translator/dlg/dropmake/web/translator_utils.py index afe583f0a..d69296983 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_utils.py +++ b/daliuge-translator/dlg/dropmake/web/translator_utils.py @@ -99,10 +99,8 @@ def check_mgr_avail(mhost, mport, mprefix): def parse_mgr_url(mgr_url): - print(mgr_url) mport = -1 mparse = urlparse(mgr_url) - print(mparse) if mparse.scheme == "http": mport = 80 elif mparse.scheme == "https": @@ -115,7 +113,7 @@ def parse_mgr_url(mgr_url): mprefix = mprefix[:-1] else: mprefix = "" - return mparse.host, mport, mprefix + return mparse.hostname, mport, mprefix def make_algo_param_dict(min_goal, ptype, max_load_imb, max_cpu, time_greedy, deadline, topk, swam_size, max_mem): From e40f7efe5a1f744bda08d0d03006c00828dd56ce Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Wed, 26 Oct 2022 16:31:24 +0800 Subject: [PATCH 39/61] Sets "undefined" to undefined - to make clear and avoid JS magic --- daliuge-translator/dlg/dropmake/web/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index d40705e53..8e3f8c339 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -439,7 +439,7 @@ function fillOutSettings() { } else { for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; - if (element.deployMethod !== "undefined") { + if (element.deployMethod !== undefined) { // If a choice has already been made, go with that. availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); } else { From 14565b0b2593d51d6b128f1e14a3f66641e62047 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 10:11:50 +0800 Subject: [PATCH 40/61] Reverses deploymethod selection conditional for clarity --- daliuge-translator/dlg/dropmake/web/main.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 8e3f8c339..b0ce0af41 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -439,12 +439,12 @@ function fillOutSettings() { } else { for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; - if (element.deployMethod !== undefined) { - // If a choice has already been made, go with that. - availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); - } else { + if(element.deployMethod === undefined){ // By default, select the first listed. availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); + } else { + // If a choice has already been made, go with that. + availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); } } } From c2cfcd1312c4c94c3c5842f89c5b5d8469de3aeb Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 10:14:30 +0800 Subject: [PATCH 41/61] Removes resolved TODO --- daliuge-translator/dlg/dropmake/web/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index b0ce0af41..8843c69c6 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -433,7 +433,6 @@ function fillOutSettings() { const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); const availableOptions = []; - // TODO: move magic strings to object/enum if (allAvailableMethods.length === 0) { // Support backend without submission/method api DEFAULT_OPTIONS.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) } else { From c14d28230177de67550f5ca355974c93167ecd94 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 14:30:36 +0800 Subject: [PATCH 42/61] Defaults to allowing all deployment options available. Changes iteration through default values. --- daliuge-translator/dlg/dropmake/web/main.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 8843c69c6..7268ebbc6 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -434,7 +434,11 @@ function fillOutSettings() { const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); const availableOptions = []; if (allAvailableMethods.length === 0) { // Support backend without submission/method api - DEFAULT_OPTIONS.forEach((option, i) => availableOptions.push(buildDeployMethodEntry(option, i === 0))) + i = 0 + Object.keys(DEFAULT_OPTIONS).forEach(function(key){ + availableOptions.push(buildDeployMethodEntry(key, i === 0)); + i += 1; + }) } else { for (i = 0; i < allAvailableMethods.length; i++) { const deploy_option = allAvailableMethods[i]; @@ -470,6 +474,10 @@ function addDeployMethod() {
From b62b72c5a657a669c4d8502437304782d949aadf Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 14:52:45 +0800 Subject: [PATCH 43/61] Adds Python enum for deployment methods. Changes check_mgr_methods function to unwrap "methods" key. --- .../dlg/common/deployment_methods.py | 30 +++++++++++++++++++ daliuge-engine/dlg/manager/rest.py | 3 +- .../dlg/dropmake/web/translator_rest.py | 12 ++++---- .../dlg/dropmake/web/translator_utils.py | 5 ++-- 4 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 daliuge-common/dlg/common/deployment_methods.py diff --git a/daliuge-common/dlg/common/deployment_methods.py b/daliuge-common/dlg/common/deployment_methods.py new file mode 100644 index 000000000..0e65cf4dd --- /dev/null +++ b/daliuge-common/dlg/common/deployment_methods.py @@ -0,0 +1,30 @@ +# +# ICRAR - International Centre for Radio Astronomy Research +# (c) UWA - The University of Western Australia, 2015 +# Copyright by UWA (in the framework of the ICRAR) +# All rights reserved +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + +from enum import Enum + + +class DeploymentMethods(Enum): + SERVER = "SERVER", + BROWSER = "BROWSER", + HELM = "HELM", + OOD = "OOD" diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index 9fdfa2f42..8e3a72801 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -55,6 +55,7 @@ from ..restserver import RestServer from ..restutils import RestClient, RestClientException from .session import generateLogFileName +from dlg.common.deployment_methods import DeploymentMethods logger = logging.getLogger(__name__) @@ -196,7 +197,7 @@ def initializeSpecifics(self, app): @daliuge_aware def submit_methods(self): - return {"methods": ["BROWSER"]} + return {"methods": [DeploymentMethods.BROWSER]} def _stop_manager(self): self.dm.shutdown() diff --git a/daliuge-translator/dlg/dropmake/web/translator_rest.py b/daliuge-translator/dlg/dropmake/web/translator_rest.py index 34a1982e8..6e84baa60 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_rest.py +++ b/daliuge-translator/dlg/dropmake/web/translator_rest.py @@ -51,13 +51,14 @@ from dlg.common.reproducibility.constants import REPRO_DEFAULT, ALL_RMODES, ReproducibilityFlags from dlg.common.reproducibility.reproducibility import init_lgt_repro_data, init_lg_repro_data, \ init_pgt_partition_repro_data, init_pgt_unroll_repro_data, init_pg_repro_data +from dlg.common.deployment_methods import DeploymentMethods from dlg.common.k8s_utils import check_k8s_env from dlg.dropmake.lg import GraphException from dlg.dropmake.pg_manager import PGManager from dlg.dropmake.scheduler import SchedulerException from dlg.dropmake.web.translator_utils import file_as_string, lg_repo_contents, lg_path, lg_exists, \ pgt_exists, pgt_path, pgt_repo_contents, prepare_lgt, unroll_and_partition_with_params, \ - make_algo_param_dict, check_mgr_avail, parse_mgr_url + make_algo_param_dict, get_mgr_deployment_methods, parse_mgr_url APP_DESCRIPTION = """ DALiuGE LG Web interface translates and deploys logical graphs. @@ -812,12 +813,11 @@ def get_submission_method( mprefix = "" available_methods = [] if check_k8s_env(): - available_methods.append("HELM") + available_methods.append(DeploymentMethods.HELM) if mhost is not None: - host_available_methods = check_mgr_avail(mhost, mport, mprefix) - if host_available_methods: - if "BROWSER" in host_available_methods["methods"]: - available_methods.extend(["SERVER"]) + host_available_methods = get_mgr_deployment_methods(mhost, mport, mprefix) + if DeploymentMethods.BROWSER in host_available_methods: + available_methods.append(DeploymentMethods.SERVER) return {"methods": available_methods} diff --git a/daliuge-translator/dlg/dropmake/web/translator_utils.py b/daliuge-translator/dlg/dropmake/web/translator_utils.py index d69296983..39a7f043d 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_utils.py +++ b/daliuge-translator/dlg/dropmake/web/translator_utils.py @@ -86,15 +86,16 @@ def filter_dict_to_algo_params(input_dict: dict): return algo_params -def check_mgr_avail(mhost, mport, mprefix): +def get_mgr_deployment_methods(mhost, mport, mprefix): try: mgr_client = CompositeManagerClient( host=mhost, port=mport, url_prefix=mprefix, timeout=15 ) response = mgr_client.get_submission_method() + response = response.get("methods", []) except RestClientException: logger.debug("Cannot connect to manager object at endpoint %s:%d", mhost, mport) - response = None + response = [] return response From bb25357522f06d32f86d5717542791b9cd59d16a Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 14:56:29 +0800 Subject: [PATCH 44/61] Adds small test for get_mgr_deployment_methods --- daliuge-translator/test/dropmake/test_lgweb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/daliuge-translator/test/dropmake/test_lgweb.py b/daliuge-translator/test/dropmake/test_lgweb.py index 65596c942..8fd77d5e3 100644 --- a/daliuge-translator/test/dropmake/test_lgweb.py +++ b/daliuge-translator/test/dropmake/test_lgweb.py @@ -31,6 +31,7 @@ from dlg import common from dlg.common import tool +from dlg.dropmake.web.translator_utils import get_mgr_deployment_methods from dlg.restutils import RestClient, RestClientException lg_dir = pkg_resources.resource_filename(__name__, ".") # @UndefinedVariable @@ -455,3 +456,7 @@ def test_pgt_map(self): for request in request_tests: self._test_post_request(c, test_url, request[0], request[1]) + + def test_get_mgr_deployment_methods(self): + response = get_mgr_deployment_methods("localhost", lgweb_port, "") + self.assertEqual([], response) From 61de396c7111c34256b68776eefe9aad68343f52 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 15:03:48 +0800 Subject: [PATCH 45/61] Updates test_rest for new enum based submission_methods --- daliuge-engine/test/manager/test_rest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daliuge-engine/test/manager/test_rest.py b/daliuge-engine/test/manager/test_rest.py index 6edb425c7..e5c7c103c 100644 --- a/daliuge-engine/test/manager/test_rest.py +++ b/daliuge-engine/test/manager/test_rest.py @@ -25,6 +25,7 @@ from dlg import exceptions from dlg.common import Categories +from dlg.common.deployment_methods import DeploymentMethods from dlg.exceptions import InvalidGraphException from dlg.manager import constants @@ -272,4 +273,4 @@ def test_reprostatus_get(self): def test_submit_method(self): c = NodeManagerClient(hostname) response = c.get_submission_method() - self.assertEqual({"methods": ["BROWSER"]}, response) + self.assertEqual([DeploymentMethods.BROWSER], response) From fcdaa3b0b10077366caf4cc9531bc00d163791a1 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 15:28:00 +0800 Subject: [PATCH 46/61] Fixes submit_methods function in rest.py and associated test. --- daliuge-engine/dlg/manager/rest.py | 2 +- daliuge-engine/test/manager/test_rest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index 8e3a72801..baeca87f8 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -197,7 +197,7 @@ def initializeSpecifics(self, app): @daliuge_aware def submit_methods(self): - return {"methods": [DeploymentMethods.BROWSER]} + return {"methods": [DeploymentMethods.BROWSER.name]} def _stop_manager(self): self.dm.shutdown() diff --git a/daliuge-engine/test/manager/test_rest.py b/daliuge-engine/test/manager/test_rest.py index e5c7c103c..f002fbc73 100644 --- a/daliuge-engine/test/manager/test_rest.py +++ b/daliuge-engine/test/manager/test_rest.py @@ -273,4 +273,4 @@ def test_reprostatus_get(self): def test_submit_method(self): c = NodeManagerClient(hostname) response = c.get_submission_method() - self.assertEqual([DeploymentMethods.BROWSER], response) + self.assertEqual({"methods": [DeploymentMethods.BROWSER.name]}, response) From 376911d83dea18ae32c714682117d3199c2fa108 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Thu, 27 Oct 2022 15:35:02 +0800 Subject: [PATCH 47/61] Fixes deploymentMethod check in translator_rest to --- daliuge-translator/dlg/dropmake/web/translator_rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-translator/dlg/dropmake/web/translator_rest.py b/daliuge-translator/dlg/dropmake/web/translator_rest.py index 6e84baa60..c7c3406d3 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_rest.py +++ b/daliuge-translator/dlg/dropmake/web/translator_rest.py @@ -816,7 +816,7 @@ def get_submission_method( available_methods.append(DeploymentMethods.HELM) if mhost is not None: host_available_methods = get_mgr_deployment_methods(mhost, mport, mprefix) - if DeploymentMethods.BROWSER in host_available_methods: + if DeploymentMethods.BROWSER.name in host_available_methods: available_methods.append(DeploymentMethods.SERVER) return {"methods": available_methods} From a125c87533fb3c65469efbc7ebf510d1fb29210b Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 11:55:51 +0800 Subject: [PATCH 48/61] Add 'persist' and 'streaming' component parameters to Data drops. Remove parameters from the schema. Remove parameters from xml2palette. --- .../dlg/data/drops/dataDummy_drop.py | 2 + .../dlg/data/drops/environmentvar_drop.py | 2 + daliuge-engine/dlg/data/drops/file.py | 5 +- daliuge-engine/dlg/data/drops/memory.py | 4 + daliuge-engine/dlg/data/drops/ngas.py | 2 + daliuge-engine/dlg/data/drops/parset_drop.py | 2 + daliuge-engine/dlg/data/drops/s3_drop.py | 2 + daliuge-engine/dlg/drop.py | 2 + .../dlg/dropmake/lg.graph.schema | 115 ++++++++---------- tools/xml2palette/xml2palette.py | 3 - 10 files changed, 69 insertions(+), 70 deletions(-) diff --git a/daliuge-engine/dlg/data/drops/dataDummy_drop.py b/daliuge-engine/dlg/data/drops/dataDummy_drop.py index 9457fd5db..91e285a74 100644 --- a/daliuge-engine/dlg/data/drops/dataDummy_drop.py +++ b/daliuge-engine/dlg/data/drops/dataDummy_drop.py @@ -42,6 +42,8 @@ # @param dataclass Application Class//String/ComponentParameter/readonly//False/False/Data class # @param data_volume Data volume/5/Float/ComponentParameter/readwrite//False/False/Estimated size of the data contained in this node # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-engine/dlg/data/drops/environmentvar_drop.py b/daliuge-engine/dlg/data/drops/environmentvar_drop.py index 4df20fdb2..45f068f17 100644 --- a/daliuge-engine/dlg/data/drops/environmentvar_drop.py +++ b/daliuge-engine/dlg/data/drops/environmentvar_drop.py @@ -66,6 +66,8 @@ def _filter_parameters(parameters: dict): # @par EAGLE_START # @param category EnvironmentVariables # @param tag daliuge +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END class EnvironmentVarDROP(AbstractDROP, KeyValueDROP): diff --git a/daliuge-engine/dlg/data/drops/file.py b/daliuge-engine/dlg/data/drops/file.py index 596fcb34b..a558a0d87 100644 --- a/daliuge-engine/dlg/data/drops/file.py +++ b/daliuge-engine/dlg/data/drops/file.py @@ -40,9 +40,12 @@ # @param tag daliuge # @param data_volume Data volume/5/Float/ComponentParameter/readwrite//False/False/Estimated size of the data contained in this node # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? -# @param check_filepath_exists Check file path exists/True/Boolean/ComponentParameter/readwrite//False/False/Perform a check to make sure the file path exists before proceeding with the application +# @param delete_parent_directory Delete parent directory/False/Boolean/ComponentParameter/readwrite//False/False/Also delete the parent directory of this file when deleting the file itself +# @param check_filepath_exists Check file path exists/False/Boolean/ComponentParameter/readwrite//False/False/Perform a check to make sure the file path exists before proceeding with the application # @param filepath File Path//String/ComponentParameter/readwrite//False/False/Path to the file for this node # @param dirname Directory name//String/ComponentParameter/readwrite//False/False/Path to the file for this node +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/True/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-engine/dlg/data/drops/memory.py b/daliuge-engine/dlg/data/drops/memory.py index d0a819d28..a35bdcbde 100644 --- a/daliuge-engine/dlg/data/drops/memory.py +++ b/daliuge-engine/dlg/data/drops/memory.py @@ -39,6 +39,8 @@ # @param tag daliuge # @param data_volume Data volume/5/Float/ComponentParameter/readwrite//False/False/Estimated size of the data contained in this node # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END @@ -101,6 +103,8 @@ def generate_reproduce_data(self): # @param tag daliuge # @param data_volume Data volume/5/Float/ComponentParameter/readwrite//False/False/Estimated size of the data contained in this node # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-engine/dlg/data/drops/ngas.py b/daliuge-engine/dlg/data/drops/ngas.py index cf99056b2..4b823f942 100644 --- a/daliuge-engine/dlg/data/drops/ngas.py +++ b/daliuge-engine/dlg/data/drops/ngas.py @@ -39,6 +39,8 @@ # @param ngasConnectTimeout Connection timeout/2/Integer/ComponentParameter/readwrite//False/False/Timeout for connecting to the NGAS server # @param ngasMime NGAS mime-type/"text/ascii"/String/ComponentParameter/readwrite//False/False/Mime-type to be used for archiving # @param ngasTimeout NGAS timeout/2/Integer/ComponentParameter/readwrite//False/False/Timeout for receiving responses for NGAS +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-engine/dlg/data/drops/parset_drop.py b/daliuge-engine/dlg/data/drops/parset_drop.py index bc26ad22a..e6bf4da28 100644 --- a/daliuge-engine/dlg/data/drops/parset_drop.py +++ b/daliuge-engine/dlg/data/drops/parset_drop.py @@ -38,6 +38,8 @@ # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? # @param mode Parset mode/"YANDA"/String/ComponentParameter/readonly//False/False/To what standard DALiuGE should filter and serialize the parameters. # @param config_data ConfigData/""/String/ComponentParameter/readwrite//False/False/Additional configuration information to be mixed in with the initial data +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param Config ConfigFile//Object.File/OutputPort/readwrite//False/False/The output configuration file # @par EAGLE_END class ParameterSetDROP(DataDROP): diff --git a/daliuge-engine/dlg/data/drops/s3_drop.py b/daliuge-engine/dlg/data/drops/s3_drop.py index d55875c7f..cac7e0ad0 100644 --- a/daliuge-engine/dlg/data/drops/s3_drop.py +++ b/daliuge-engine/dlg/data/drops/s3_drop.py @@ -53,6 +53,8 @@ # @param object_name Object Name//String/ComponentParameter/readwrite//False/False/The S3 object key # @param profile_name Profile Name//String/ComponentParameter/readwrite//False/False/The S3 profile name # @param endpoint_url Endpoint URL//String/ComponentParameter/readwrite//False/False/The URL exposing the S3 REST API +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-engine/dlg/drop.py b/daliuge-engine/dlg/drop.py index 6c68fda8b..78cfd40f7 100644 --- a/daliuge-engine/dlg/drop.py +++ b/daliuge-engine/dlg/drop.py @@ -1199,6 +1199,8 @@ def path(self) -> str: # @param dataclass Data Class/my.awesome.data.Component/String/ComponentParameter/readonly//False/False/The python class that implements this data component # @param data_volume Data volume/5/Float/ComponentParameter/readwrite//False/False/Estimated size of the data contained in this node # @param group_end Group end/False/Boolean/ComponentParameter/readwrite//False/False/Is this node the end of a group? +# @param streaming Streaming/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component streams input and output data +# @param persist Persist/False/Boolean/ComponentParameter/readwrite//False/False/Specifies whether this data component contains data that should not be deleted after execution # @param dummy dummy//Object/InputPort/readwrite//False/False/Dummy input port # @param dummy dummy//Object/OutputPort/readwrite//False/False/Dummy output port # @par EAGLE_END diff --git a/daliuge-translator/dlg/dropmake/lg.graph.schema b/daliuge-translator/dlg/dropmake/lg.graph.schema index 691ccd739..d11634288 100644 --- a/daliuge-translator/dlg/dropmake/lg.graph.schema +++ b/daliuge-translator/dlg/dropmake/lg.graph.schema @@ -5,9 +5,6 @@ "modelData": { "type": "object", "properties": { - "fileType": { - "type": "string" - }, "repoService": { "type": "string" }, @@ -23,10 +20,7 @@ "filePath": { "type": "string" }, - "sha": { - "type": "string" - }, - "gitUrl": { + "fileType": { "type": "string" }, "eagleVersion": { @@ -49,23 +43,38 @@ }, "numLGNodes": { "type": "integer" + }, + "commitHash": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "repositoryUrl": { + "type": "string" + }, + "signature": { + "type": "string" } }, "required": [ "filePath", "fileType", - "gitUrl", "readonly", "repo", "repoBranch", "repoService", - "sha", "eagleVersion", "eagleCommitHash", "schemaVersion", "lastModifiedName", "lastModifiedEmail", - "lastModifiedDatetime" + "lastModifiedDatetime", + "numLGNodes", + "commitHash", + "downloadUrl", + "repositoryUrl", + "signature" ] }, "nodeDataArray": { @@ -82,6 +91,12 @@ "color": { "type": "string" }, + "commitHash": { + "type": "string" + }, + "dataHash": { + "type": "string" + }, "drawOrderHint": { "type": "integer" }, @@ -109,22 +124,12 @@ "collapsed": { "type": "boolean" }, - "streaming": { - "type": "boolean" - }, "subject": { "type": "null" }, "expanded": { "type": "boolean" }, - "precious": { - "type": "boolean", - "deprecated": true - }, - "persist": { - "type": "boolean" - }, "inputApplicationName": { "type": "string" }, @@ -261,6 +266,12 @@ ] } }, + "paletteDownloadUrl": { + "type": "string" + }, + "repositoryUrl": { + "type": "string" + }, "fields": { "type": "array", "items": { @@ -296,58 +307,20 @@ "positional": { "type": "boolean", "default": false - } - }, - "required": [ - "description", - "name", - "text", - "value" - ] - } - }, - "applicationArgs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "text": { - "type": "string" }, - "name": { - "type": "string" - }, - "value": { - "type": ["string","boolean","number", "null"] - }, - "defaultValue": { - "type": ["string","boolean","number", "null"] - }, - "description": { - "type": "string" - }, - "readonly": { + "keyAttribute": { "type": "boolean" - }, - "type": { - "type": "string" - }, - "precious": { - "type": "boolean" - }, - "options": { - "type": "array" - }, - "positional": { - "type": "boolean", - "default": false } }, "required": [ "description", "name", "text", - "value" + "value", + "type", + "precious", + "options", + "positional" ] } }, @@ -359,11 +332,12 @@ "category", "collapsed", "color", + "commitHash", + "dataHash", "description", "drawOrderHint", "expanded", "fields", - "applicationArgs", "height", "inputAppFields", "inputApplicationName", @@ -377,7 +351,8 @@ "outputApplicationType", "outputLocalPorts", "outputPorts", - "streaming", + "paletteDownloadUrl", + "repositoryUrl", "subject", "text", "width", @@ -391,6 +366,12 @@ "items": { "type": "object", "properties": { + "closesLoop": { + "type": "boolean" + }, + "dataType": { + "type": "string" + }, "from": { "type": "integer" }, @@ -408,6 +389,8 @@ } }, "required": [ + "closesLoop", + "dataType", "from", "fromPort", "to", diff --git a/tools/xml2palette/xml2palette.py b/tools/xml2palette/xml2palette.py index bf88c5af6..27819dc99 100755 --- a/tools/xml2palette/xml2palette.py +++ b/tools/xml2palette/xml2palette.py @@ -661,7 +661,6 @@ def create_palette_node_from_params(params) -> dict: "description": comp_description, "collapsed": False, "showPorts": False, - "streaming": False, "subject": None, "selected": False, "expanded": False, @@ -1213,8 +1212,6 @@ def create_construct_node(node_type:str, node:dict)-> dict: "paletteDownloadUrl": "", "dataHash": "", "key": get_next_key(), - "precious": False, - "streaming": False, "text": node_type + "/" + node["text"], } From 625c79f536b6bdbc586fb5cda4cdda15c4c84e42 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 12:24:55 +0800 Subject: [PATCH 49/61] Switch back to old schema --- .../dlg/dropmake/lg.graph.schema | 115 ++++++++++-------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/lg.graph.schema b/daliuge-translator/dlg/dropmake/lg.graph.schema index d11634288..691ccd739 100644 --- a/daliuge-translator/dlg/dropmake/lg.graph.schema +++ b/daliuge-translator/dlg/dropmake/lg.graph.schema @@ -5,6 +5,9 @@ "modelData": { "type": "object", "properties": { + "fileType": { + "type": "string" + }, "repoService": { "type": "string" }, @@ -20,7 +23,10 @@ "filePath": { "type": "string" }, - "fileType": { + "sha": { + "type": "string" + }, + "gitUrl": { "type": "string" }, "eagleVersion": { @@ -43,38 +49,23 @@ }, "numLGNodes": { "type": "integer" - }, - "commitHash": { - "type": "string" - }, - "downloadUrl": { - "type": "string" - }, - "repositoryUrl": { - "type": "string" - }, - "signature": { - "type": "string" } }, "required": [ "filePath", "fileType", + "gitUrl", "readonly", "repo", "repoBranch", "repoService", + "sha", "eagleVersion", "eagleCommitHash", "schemaVersion", "lastModifiedName", "lastModifiedEmail", - "lastModifiedDatetime", - "numLGNodes", - "commitHash", - "downloadUrl", - "repositoryUrl", - "signature" + "lastModifiedDatetime" ] }, "nodeDataArray": { @@ -91,12 +82,6 @@ "color": { "type": "string" }, - "commitHash": { - "type": "string" - }, - "dataHash": { - "type": "string" - }, "drawOrderHint": { "type": "integer" }, @@ -124,12 +109,22 @@ "collapsed": { "type": "boolean" }, + "streaming": { + "type": "boolean" + }, "subject": { "type": "null" }, "expanded": { "type": "boolean" }, + "precious": { + "type": "boolean", + "deprecated": true + }, + "persist": { + "type": "boolean" + }, "inputApplicationName": { "type": "string" }, @@ -266,12 +261,6 @@ ] } }, - "paletteDownloadUrl": { - "type": "string" - }, - "repositoryUrl": { - "type": "string" - }, "fields": { "type": "array", "items": { @@ -307,20 +296,58 @@ "positional": { "type": "boolean", "default": false + } + }, + "required": [ + "description", + "name", + "text", + "value" + ] + } + }, + "applicationArgs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string" }, - "keyAttribute": { + "name": { + "type": "string" + }, + "value": { + "type": ["string","boolean","number", "null"] + }, + "defaultValue": { + "type": ["string","boolean","number", "null"] + }, + "description": { + "type": "string" + }, + "readonly": { "type": "boolean" + }, + "type": { + "type": "string" + }, + "precious": { + "type": "boolean" + }, + "options": { + "type": "array" + }, + "positional": { + "type": "boolean", + "default": false } }, "required": [ "description", "name", "text", - "value", - "type", - "precious", - "options", - "positional" + "value" ] } }, @@ -332,12 +359,11 @@ "category", "collapsed", "color", - "commitHash", - "dataHash", "description", "drawOrderHint", "expanded", "fields", + "applicationArgs", "height", "inputAppFields", "inputApplicationName", @@ -351,8 +377,7 @@ "outputApplicationType", "outputLocalPorts", "outputPorts", - "paletteDownloadUrl", - "repositoryUrl", + "streaming", "subject", "text", "width", @@ -366,12 +391,6 @@ "items": { "type": "object", "properties": { - "closesLoop": { - "type": "boolean" - }, - "dataType": { - "type": "string" - }, "from": { "type": "integer" }, @@ -389,8 +408,6 @@ } }, "required": [ - "closesLoop", - "dataType", "from", "fromPort", "to", From 7019fdc4bd03e8dafb4c9665e3d8ec07ef2b59f6 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 14:09:32 +0800 Subject: [PATCH 50/61] Attempt to pin pyarrow to version 9.0 to avoid issue with new version --- daliuge-engine/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-engine/setup.py b/daliuge-engine/setup.py index 5fd9fb0b7..365b42321 100644 --- a/daliuge-engine/setup.py +++ b/daliuge-engine/setup.py @@ -131,7 +131,7 @@ def run(self): "overrides", "paramiko", "psutil", - "pyarrow", + "pyarrow==9.0", "python-daemon", "pyzmq ~= 22.3.0", "scp", From 068fa91ead1556d357d66373dfbd4a33caa74bc7 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 14:19:14 +0800 Subject: [PATCH 51/61] Switch back to the new schema --- .../dlg/dropmake/lg.graph.schema | 115 ++++++++---------- 1 file changed, 49 insertions(+), 66 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/lg.graph.schema b/daliuge-translator/dlg/dropmake/lg.graph.schema index 691ccd739..d11634288 100644 --- a/daliuge-translator/dlg/dropmake/lg.graph.schema +++ b/daliuge-translator/dlg/dropmake/lg.graph.schema @@ -5,9 +5,6 @@ "modelData": { "type": "object", "properties": { - "fileType": { - "type": "string" - }, "repoService": { "type": "string" }, @@ -23,10 +20,7 @@ "filePath": { "type": "string" }, - "sha": { - "type": "string" - }, - "gitUrl": { + "fileType": { "type": "string" }, "eagleVersion": { @@ -49,23 +43,38 @@ }, "numLGNodes": { "type": "integer" + }, + "commitHash": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "repositoryUrl": { + "type": "string" + }, + "signature": { + "type": "string" } }, "required": [ "filePath", "fileType", - "gitUrl", "readonly", "repo", "repoBranch", "repoService", - "sha", "eagleVersion", "eagleCommitHash", "schemaVersion", "lastModifiedName", "lastModifiedEmail", - "lastModifiedDatetime" + "lastModifiedDatetime", + "numLGNodes", + "commitHash", + "downloadUrl", + "repositoryUrl", + "signature" ] }, "nodeDataArray": { @@ -82,6 +91,12 @@ "color": { "type": "string" }, + "commitHash": { + "type": "string" + }, + "dataHash": { + "type": "string" + }, "drawOrderHint": { "type": "integer" }, @@ -109,22 +124,12 @@ "collapsed": { "type": "boolean" }, - "streaming": { - "type": "boolean" - }, "subject": { "type": "null" }, "expanded": { "type": "boolean" }, - "precious": { - "type": "boolean", - "deprecated": true - }, - "persist": { - "type": "boolean" - }, "inputApplicationName": { "type": "string" }, @@ -261,6 +266,12 @@ ] } }, + "paletteDownloadUrl": { + "type": "string" + }, + "repositoryUrl": { + "type": "string" + }, "fields": { "type": "array", "items": { @@ -296,58 +307,20 @@ "positional": { "type": "boolean", "default": false - } - }, - "required": [ - "description", - "name", - "text", - "value" - ] - } - }, - "applicationArgs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "text": { - "type": "string" }, - "name": { - "type": "string" - }, - "value": { - "type": ["string","boolean","number", "null"] - }, - "defaultValue": { - "type": ["string","boolean","number", "null"] - }, - "description": { - "type": "string" - }, - "readonly": { + "keyAttribute": { "type": "boolean" - }, - "type": { - "type": "string" - }, - "precious": { - "type": "boolean" - }, - "options": { - "type": "array" - }, - "positional": { - "type": "boolean", - "default": false } }, "required": [ "description", "name", "text", - "value" + "value", + "type", + "precious", + "options", + "positional" ] } }, @@ -359,11 +332,12 @@ "category", "collapsed", "color", + "commitHash", + "dataHash", "description", "drawOrderHint", "expanded", "fields", - "applicationArgs", "height", "inputAppFields", "inputApplicationName", @@ -377,7 +351,8 @@ "outputApplicationType", "outputLocalPorts", "outputPorts", - "streaming", + "paletteDownloadUrl", + "repositoryUrl", "subject", "text", "width", @@ -391,6 +366,12 @@ "items": { "type": "object", "properties": { + "closesLoop": { + "type": "boolean" + }, + "dataType": { + "type": "string" + }, "from": { "type": "integer" }, @@ -408,6 +389,8 @@ } }, "required": [ + "closesLoop", + "dataType", "from", "fromPort", "to", From 3adb9f8d036d36c8804981f35bdc6bb505b22c9b Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 28 Oct 2022 14:56:32 +0800 Subject: [PATCH 52/61] Separates deployMethod factory out for new deployment options. --- daliuge-translator/dlg/dropmake/web/main.js | 42 +++++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 7268ebbc6..12353ebc9 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -469,20 +469,38 @@ function fillOutSettings() { function addDeployMethod() { const deployMethodManagerDiv = $("#DeployMethodManager"); - const deployMethodRow = `
+ let methods = []; + Object.keys(DEFAULT_OPTIONS).forEach(function(key){ + methods.push(key); + }) + deployMethodManagerDiv.append(buildDeployMethod(methods, undefined)); +} + +function buildDeployMethod(methods, reachable, deployActive, deployName, deployUrl){ + let i; + let reachableIcon; + + if (reachable === undefined) { + reachableIcon = ``; + } else if(reachable){ + reachableIcon = `
done
`; + } else { + reachableIcon = `
close
`; + } + let deployMethodRow = `
- -
- - -
`; - deployMethodManagerDiv.append(deployMethodRow) + ${reachableIcon} +
+ + +
` + return deployMethodRow; } function removeDeployMethod(e) { From 82a8079fa29c6d4af1700323628113bb9f8cbd13 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 28 Oct 2022 15:20:06 +0800 Subject: [PATCH 53/61] buildDeployMethod is now complete and used for existing deployment options. --- daliuge-translator/dlg/dropmake/web/main.js | 72 +++++++-------------- 1 file changed, 22 insertions(+), 50 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index 12353ebc9..f7b2bc991 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -117,7 +117,8 @@ async function initiateDeploy(method, selected, clickedName) { $("#gen_pg_button").val("Generate & Deploy Physical Graph") $("#dlg_mgr_deploy").prop("checked", true) $("#pg_form").submit(); - } if (method === DEFAULT_OPTIONS.HELM) { + } + if (method === DEFAULT_OPTIONS.HELM) { $("#gen_helm_button").val("Generate & Deploy Physical Graph") $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() @@ -419,85 +420,56 @@ function fillOutSettings() { deployMethodManagerDiv.empty() console.debug("filling out settings, GET Errors and Cors warning from Url check") deployMethodsArray.forEach(async element => { - let i; const urlReachable = await checkUrlStatus(element.url); const directlyAvailableMethods = await checkUrlSubmissionMethods(element.url + '/api/submission_method'); const translatorAvailableMethods = await checkUrlSubmissionMethods(getCurrentPageUrl() + '/api/submission_method?dlg_mgr_url=' + element.url); - let ReachableIcon; - - if (urlReachable) { - ReachableIcon = `
done
` - } else { - ReachableIcon = `
close
` - } - const allAvailableMethods = directlyAvailableMethods["methods"].concat(translatorAvailableMethods["methods"]); - const availableOptions = []; if (allAvailableMethods.length === 0) { // Support backend without submission/method api - i = 0 - Object.keys(DEFAULT_OPTIONS).forEach(function(key){ - availableOptions.push(buildDeployMethodEntry(key, i === 0)); - i += 1; + Object.keys(DEFAULT_OPTIONS).forEach(function (key) { + allAvailableMethods.push(key); }) - } else { - for (i = 0; i < allAvailableMethods.length; i++) { - const deploy_option = allAvailableMethods[i]; - if(element.deployMethod === undefined){ - // By default, select the first listed. - availableOptions.push(buildDeployMethodEntry(deploy_option, i === 0)); - } else { - // If a choice has already been made, go with that. - availableOptions.push(buildDeployMethodEntry(deploy_option, element.deployMethod === deploy_option)); - } - } - } - let deployMethodRow = `
-
-
- ${ReachableIcon}
- - -
` - deployMethodManagerDiv.append(deployMethodRow) + deployMethodManagerDiv.append(buildDeployMethod(allAvailableMethods, urlReachable, element.active, element.name, element.url, element.deployMethod)) }); } function addDeployMethod() { const deployMethodManagerDiv = $("#DeployMethodManager"); let methods = []; - Object.keys(DEFAULT_OPTIONS).forEach(function(key){ + Object.keys(DEFAULT_OPTIONS).forEach(function (key) { methods.push(key); }) - deployMethodManagerDiv.append(buildDeployMethod(methods, undefined)); + deployMethodManagerDiv.append(buildDeployMethod(methods, undefined, "false", "", "", undefined)); } -function buildDeployMethod(methods, reachable, deployActive, deployName, deployUrl){ +function buildDeployMethod(methods, reachable, deployActive, deployName, deployUrl, selectedMethod) { let i; let reachableIcon; - if (reachable === undefined) { reachableIcon = ``; - } else if(reachable){ + } else if (reachable) { reachableIcon = `
done
`; } else { reachableIcon = `
close
`; } let deployMethodRow = `
-
-
+
+
${reachableIcon}
- + `
+
` return deployMethodRow; From be13189ffc0ae0c105285475c2db3000e12b3d23 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 15:29:50 +0800 Subject: [PATCH 54/61] Modified all graphs to move 'streaming' and 'persist' attribute to fields. --- OpenAPI/tests/test.graph | 32 +++++-- .../test/reproducibility/testSingle.graph | 1 - .../topoGraphs/computationSandwich.graph | 16 +++- .../reproducibility/topoGraphs/dataFan.graph | 43 ++++++++- .../topoGraphs/dataFunnel.graph | 43 ++++++++- .../topoGraphs/dataSandwich.graph | 26 ++++++ .../topoGraphs/testCycle.graph | 4 - .../topoGraphs/testSingle.graph | 1 - .../topoGraphs/testTwoEnd.graph | 3 - .../topoGraphs/testTwoLines.graph | 4 - .../topoGraphs/testTwoStart.graph | 3 - .../logical_graphs/HelloWorld_simple.graph | 13 +++ .../dropmake/logical_graphs/Plasma-test.graph | 32 +++++-- .../dropmake/logical_graphs/Plasma_test.graph | 26 ++++++ .../logical_graphs/SharedMemoryTest.graph | 16 ---- .../logical_graphs/chiles_simple.graph | 50 ---------- .../dropmake/logical_graphs/cont_img.graph | 93 +------------------ .../logical_graphs/eagle_gather.graph | 26 ++++++ .../logical_graphs/eagle_gather_empty.graph | 33 +++---- .../logical_graphs/eagle_gather_simple.graph | 67 +++++++------ .../logical_graphs/leap_cli_dir_appRef.graph | 76 ++++++++++++--- .../dropmake/logical_graphs/lofar_std.graph | 4 - .../test/dropmake/logical_graphs/nagsIo.graph | 3 - .../dropmake/logical_graphs/simpleMKN.graph | 4 - .../logical_graphs/test-20190830-110556.graph | 13 +++ .../dropmake/logical_graphs/testLoop.graph | 53 ++++++----- .../logical_graphs/test_grpby_gather.graph | 43 +-------- .../reproGraphs/HelloSBash.graph | 43 ++++++++- .../reproGraphs/HelloSPython.graph | 30 +++++- .../reproGraphs/HelloSPython2.graph | 30 +++++- .../reproGraphs/HelloWorldBash.graph | 43 ++++++++- .../reproGraphs/HelloWorldBashSplit.graph | 43 ++++++++- .../reproGraphs/HelloWorldFile.graph | 14 ++- .../reproducibility/reproGraphs/apps.graph | 10 -- .../reproducibility/reproGraphs/files.graph | 19 ++-- .../reproGraphs/groupUse.graph | 2 - .../reproducibility/reproGraphs/groups.graph | 14 --- .../reproducibility/reproGraphs/misc.graph | 6 -- .../reproGraphs/simpleNoScatter.graph | 52 +++++++++++ .../reproGraphs/simpleScatter.graph | 54 ++++++++++- 40 files changed, 694 insertions(+), 394 deletions(-) diff --git a/OpenAPI/tests/test.graph b/OpenAPI/tests/test.graph index a492b6bf2..d388c4430 100644 --- a/OpenAPI/tests/test.graph +++ b/OpenAPI/tests/test.graph @@ -33,8 +33,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -235,8 +233,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -304,6 +300,19 @@ "readonly": false, "type": "Unknown", "precious": false + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "applicationArgs": [], @@ -337,8 +346,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -386,6 +393,19 @@ "readonly": false, "type": "Unknown", "precious": false + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "applicationArgs": [], diff --git a/daliuge-engine/test/reproducibility/testSingle.graph b/daliuge-engine/test/reproducibility/testSingle.graph index 8382b4eb2..54f5fbae6 100644 --- a/daliuge-engine/test/reproducibility/testSingle.graph +++ b/daliuge-engine/test/reproducibility/testSingle.graph @@ -74,7 +74,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/computationSandwich.graph b/daliuge-engine/test/reproducibility/topoGraphs/computationSandwich.graph index 88141cc67..40a402c71 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/computationSandwich.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/computationSandwich.graph @@ -104,7 +104,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -165,6 +164,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -201,7 +213,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -283,7 +294,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/dataFan.graph b/daliuge-engine/test/reproducibility/topoGraphs/dataFan.graph index 113d1a292..197752e61 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/dataFan.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/dataFan.graph @@ -118,7 +118,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -179,6 +178,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -208,7 +220,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -269,6 +280,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -298,7 +322,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -359,6 +382,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -388,7 +424,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/dataFunnel.graph b/daliuge-engine/test/reproducibility/topoGraphs/dataFunnel.graph index 1273671ff..d18a7fe26 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/dataFunnel.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/dataFunnel.graph @@ -90,6 +90,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -119,7 +132,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -214,7 +226,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -275,6 +286,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -304,7 +328,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -365,6 +388,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -394,7 +430,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph b/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph index b67a95c5c..bd905c98f 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph @@ -172,6 +172,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -262,6 +275,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testCycle.graph b/daliuge-engine/test/reproducibility/topoGraphs/testCycle.graph index ce29bd624..f46b9b136 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testCycle.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testCycle.graph @@ -99,7 +99,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -170,7 +169,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -241,7 +239,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -312,7 +309,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testSingle.graph b/daliuge-engine/test/reproducibility/topoGraphs/testSingle.graph index 8382b4eb2..54f5fbae6 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testSingle.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testSingle.graph @@ -74,7 +74,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testTwoEnd.graph b/daliuge-engine/test/reproducibility/topoGraphs/testTwoEnd.graph index a6f6f6b79..f09b625a4 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testTwoEnd.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testTwoEnd.graph @@ -91,7 +91,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -162,7 +161,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -233,7 +231,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testTwoLines.graph b/daliuge-engine/test/reproducibility/topoGraphs/testTwoLines.graph index 4f447002b..e472e4f5e 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testTwoLines.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testTwoLines.graph @@ -87,7 +87,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -158,7 +157,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -229,7 +227,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -300,7 +297,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testTwoStart.graph b/daliuge-engine/test/reproducibility/topoGraphs/testTwoStart.graph index c30ddb125..68bc34820 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testTwoStart.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testTwoStart.graph @@ -87,7 +87,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -162,7 +161,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -233,7 +231,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph b/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph index 7e0a431e1..be07d4cdd 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph @@ -187,6 +187,19 @@ "description": "", "readonly": false, "type": "Unknown" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], diff --git a/daliuge-translator/test/dropmake/logical_graphs/Plasma-test.graph b/daliuge-translator/test/dropmake/logical_graphs/Plasma-test.graph index 8faa1bc6d..4901bce21 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/Plasma-test.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/Plasma-test.graph @@ -32,7 +32,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -105,7 +104,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -194,7 +192,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -250,6 +247,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -284,7 +294,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -373,7 +382,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -436,6 +444,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -470,7 +491,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph b/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph index cd897e2a4..6ea85d12c 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph @@ -250,6 +250,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -436,6 +449,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], diff --git a/daliuge-translator/test/dropmake/logical_graphs/SharedMemoryTest.graph b/daliuge-translator/test/dropmake/logical_graphs/SharedMemoryTest.graph index b0cacc65e..b91be0b1c 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/SharedMemoryTest.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/SharedMemoryTest.graph @@ -143,10 +143,8 @@ "type": "String" } ], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Python App", "width": 200, @@ -218,10 +216,8 @@ "type": "String" } ], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Shared Memory", "width": 200, @@ -313,10 +309,8 @@ "type": "String" } ], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Python App", "width": 200, @@ -408,10 +402,8 @@ "type": "String" } ], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Python App", "width": 200, @@ -503,10 +495,8 @@ "type": "String" } ], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Python App", "width": 200, @@ -569,10 +559,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Shared Memory", "width": 200, @@ -635,10 +623,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Shared Memory", "width": 200, @@ -701,10 +687,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": false, "sha": "", - "streaming": false, "subject": null, "text": "Shared Memory", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/chiles_simple.graph b/daliuge-translator/test/dropmake/logical_graphs/chiles_simple.graph index c59ce2e34..ba142da35 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/chiles_simple.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/chiles_simple.graph @@ -270,10 +270,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Start", "width": 200, @@ -321,10 +319,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "", "width": 200, @@ -372,10 +368,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "", "width": 200, @@ -441,10 +435,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day1", "width": 200, @@ -642,10 +634,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "MST", "width": 200, @@ -711,10 +701,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day1-Split1", "width": 200, @@ -771,10 +759,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1004,10 +990,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "CLEAN", "width": 200, @@ -1073,10 +1057,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day2", "width": 200, @@ -1142,10 +1124,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day3", "width": 200, @@ -1211,10 +1191,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day4", "width": 200, @@ -1280,10 +1258,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day5", "width": 200, @@ -1481,10 +1457,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "MST", "width": 200, @@ -1682,10 +1656,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "MST", "width": 200, @@ -1883,10 +1855,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "MST", "width": 200, @@ -2084,10 +2054,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "MST", "width": 200, @@ -2153,10 +2121,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day2-Split1", "width": 200, @@ -2222,10 +2188,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day3-Split1", "width": 200, @@ -2291,10 +2255,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day4-Split1", "width": 200, @@ -2360,10 +2322,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Day5-Split1", "width": 200, @@ -2561,10 +2521,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Copy", "width": 200, @@ -2762,10 +2720,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Copy", "width": 200, @@ -2963,10 +2919,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Copy", "width": 200, @@ -3164,10 +3118,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Copy", "width": 200, @@ -3365,10 +3317,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Copy", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph b/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph index abc82b4ed..725b8940c 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph @@ -371,10 +371,8 @@ "type": "Boolean" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Self-Cal\nConverge?", "width": 200, @@ -429,10 +427,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Update GSM", "width": 200, @@ -487,10 +483,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Update LSM", "width": 200, @@ -545,10 +539,9 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, + , "subject": null, "text": "Cal. Source\nFinding", "width": 200, @@ -603,10 +596,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Image Plane\nSpectral Averaging", "width": 200, @@ -670,10 +661,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Predict 01", "width": 200, @@ -735,10 +724,8 @@ "type": "string" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Solve", "width": 200, @@ -793,10 +780,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Correct", "width": 200, @@ -867,10 +852,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Subtract", "width": 200, @@ -925,10 +908,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Flag", "width": 200, @@ -983,10 +964,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "iFFT", "width": 200, @@ -1048,10 +1027,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "deGrid", "width": 200, @@ -1122,10 +1099,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Subtract", "width": 200, @@ -1180,10 +1155,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Flag", "width": 200, @@ -1238,10 +1211,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "FFT", "width": 200, @@ -1296,10 +1267,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Subtract \ncompnt frm\nimg plane", "width": 200, @@ -1345,10 +1314,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "End", "width": 200, @@ -1412,10 +1379,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Calibration\nParameters", "width": 200, @@ -1487,10 +1452,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1562,10 +1525,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1637,10 +1598,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1712,10 +1671,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1787,10 +1744,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1862,10 +1817,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1937,10 +1890,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -1988,10 +1939,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Minor Cycle", "width": 709, @@ -2143,10 +2092,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Identify\nComponent", "width": 200, @@ -2218,10 +2165,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Model", "width": 200, @@ -2300,10 +2245,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Image", "width": 200, @@ -2351,10 +2294,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Major Cycle", "width": 1217, @@ -2426,10 +2367,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -2501,10 +2440,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Model Vis", "width": 200, @@ -2576,10 +2513,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Residul Vis", "width": 200, @@ -2643,10 +2578,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "FRV", "width": 200, @@ -2726,10 +2659,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Data", "width": 200, @@ -2881,10 +2812,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Grid", "width": 200, @@ -2956,10 +2885,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "CC", "width": 200, @@ -3030,10 +2957,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "GSM", "width": 200, @@ -3097,10 +3022,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Raw Vis", "width": 200, @@ -3147,10 +3070,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Self-Cal Cycle", "width": 1885, @@ -3222,10 +3143,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "LSM", "width": 200, @@ -3377,10 +3296,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Check Conv", "width": 200, @@ -3452,10 +3369,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "result", "width": 200, @@ -3526,10 +3441,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Start Data ", "width": 200, @@ -3575,10 +3488,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Start", "width": 200, @@ -3722,10 +3633,8 @@ "type": "" } ], - "persist": false, "readonly": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Predict 02", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph index fc9839582..c4bd1e679 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph @@ -967,6 +967,19 @@ "readonly": false, "type": "Unknown", "precious": false + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "applicationArgs": [], @@ -1352,6 +1365,19 @@ "readonly": false, "type": "Unknown", "precious": false + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "applicationArgs": [], diff --git a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_empty.graph b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_empty.graph index c3e4f67ea..ca2de75d2 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_empty.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_empty.graph @@ -33,8 +33,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -116,8 +114,6 @@ "height": 432, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -337,8 +333,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -530,8 +524,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -613,8 +605,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -696,8 +686,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -888,8 +876,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -967,6 +953,19 @@ "readonly": false, "type": "Unknown", "precious": false + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "applicationArgs": [], @@ -1000,8 +999,6 @@ "height": 200, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1221,8 +1218,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1273,8 +1268,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, diff --git a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_simple.graph b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_simple.graph index d2671f4d8..5a95e841b 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_simple.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather_simple.graph @@ -32,8 +32,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -107,8 +105,6 @@ "collapsed": false, "showPorts": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": true, "expanded": true, @@ -222,8 +218,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -297,8 +291,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -372,8 +364,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -429,6 +419,19 @@ "description": "", "readonly": false, "type": "Unknown" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -463,8 +466,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": true, @@ -570,8 +571,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -645,8 +644,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -720,8 +717,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": true, @@ -827,8 +822,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -885,6 +878,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -919,8 +925,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -1018,8 +1022,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -1123,8 +1125,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": true, @@ -1230,8 +1230,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, @@ -1288,6 +1286,19 @@ "description": "Name of the directory containing the file for this node", "readonly": false, "type": "String" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], diff --git a/daliuge-translator/test/dropmake/logical_graphs/leap_cli_dir_appRef.graph b/daliuge-translator/test/dropmake/logical_graphs/leap_cli_dir_appRef.graph index bb7f1254b..c74b09c95 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/leap_cli_dir_appRef.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/leap_cli_dir_appRef.graph @@ -104,7 +104,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Direction Scatter", "width": 585, @@ -144,7 +143,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Gather", "width": 200, @@ -202,6 +200,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -229,7 +240,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "IntermediateResult", "width": 200, @@ -287,6 +297,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -314,7 +337,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Phase Vector", "width": 200, @@ -353,7 +375,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "", "width": 199.58141986216643, @@ -411,6 +432,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -438,7 +472,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "ConfigFile", "width": 200, @@ -561,7 +594,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "LeapAccelerateCLI", "width": 200, @@ -619,6 +651,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -639,7 +684,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "directions.csv", "width": 200, @@ -697,6 +741,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -717,7 +774,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "MS", "width": 200, @@ -826,7 +882,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Produce Config.", "width": 200, @@ -903,7 +958,6 @@ "readonly": true, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "LEAPGather", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph b/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph index 53feebc13..e066f1033 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph @@ -33,8 +33,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -87,8 +85,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, diff --git a/daliuge-translator/test/dropmake/logical_graphs/nagsIo.graph b/daliuge-translator/test/dropmake/logical_graphs/nagsIo.graph index 2d414d576..c57718b20 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/nagsIo.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/nagsIo.graph @@ -32,7 +32,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -122,7 +121,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -204,7 +202,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/simpleMKN.graph b/daliuge-translator/test/dropmake/logical_graphs/simpleMKN.graph index d01a2c541..b22ed62ce 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/simpleMKN.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/simpleMKN.graph @@ -27,7 +27,6 @@ "height": 252.30527217275548, "collapsed": false, "showPorts": true, - "streaming": false, "subject": null, "selected": true, "expanded": true, @@ -152,7 +151,6 @@ "height": 200, "collapsed": false, "showPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -230,7 +228,6 @@ "height": 200, "collapsed": false, "showPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -284,7 +281,6 @@ "height": 200, "collapsed": false, "showPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph b/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph index fc394dacb..184d8c788 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph @@ -319,6 +319,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/testLoop.graph b/daliuge-translator/test/dropmake/logical_graphs/testLoop.graph index 4afbfd95b..8b55d3258 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/testLoop.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/testLoop.graph @@ -45,7 +45,7 @@ "defaultValue": "", "description": "", "name": "num_of_iter", - "persist": false, + "precious": false, "readonly": false, "text": "Number loops", "type": "Unknown", @@ -95,10 +95,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Loop", "width": 385.98717625064404, @@ -118,7 +116,7 @@ "defaultValue": "", "description": "", "name": "execution_time", - "persist": false, + "precious": false, "readonly": false, "text": "Execution time", "type": "Unknown", @@ -128,7 +126,7 @@ "defaultValue": "", "description": "", "name": "num_cpus", - "persist": false, + "precious": false, "readonly": false, "text": "Num CPUs", "type": "Unknown", @@ -138,7 +136,7 @@ "defaultValue": "", "description": "", "name": "group_start", - "persist": false, + "precious": false, "readonly": false, "text": "Group start", "type": "Unknown", @@ -148,7 +146,7 @@ "defaultValue": "", "description": "The command line to be executed", "name": "Arg01", - "persist": false, + "precious": false, "readonly": false, "text": "Arg01", "type": "Unknown", @@ -158,7 +156,7 @@ "defaultValue": "sleep", "description": "", "name": "command", - "persist": false, + "precious": false, "readonly": false, "text": "command", "type": "String", @@ -200,10 +198,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "SleepExternal", "width": 200, @@ -223,7 +219,7 @@ "defaultValue": "", "description": "", "name": "execution_time", - "persist": false, + "precious": false, "readonly": false, "text": "Execution time", "type": "Unknown", @@ -233,7 +229,7 @@ "defaultValue": "", "description": "", "name": "num_cpus", - "persist": false, + "precious": false, "readonly": false, "text": "Num CPUs", "type": "Unknown", @@ -243,7 +239,7 @@ "defaultValue": "false", "description": "", "name": "group_start", - "persist": false, + "precious": false, "readonly": false, "text": "Group start", "type": "Boolean", @@ -253,7 +249,7 @@ "defaultValue": "", "description": "The command line to be executed", "name": "Arg01", - "persist": false, + "precious": false, "readonly": false, "text": "Arg01", "type": "Unknown", @@ -263,7 +259,7 @@ "defaultValue": "sleep", "description": "", "name": "command", - "persist": false, + "precious": false, "readonly": false, "text": "command", "type": "String", @@ -306,10 +302,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "SleepInternal", "width": 200, @@ -329,7 +323,7 @@ "defaultValue": "5", "description": "Estimated size of the data contained in this node", "name": "data_volume", - "persist": false, + "precious": false, "readonly": false, "text": "Data volume", "type": "Float", @@ -339,7 +333,7 @@ "defaultValue": "false", "description": "Is this node the end of a group?", "name": "group_end", - "persist": false, + "precious": false, "readonly": false, "text": "Group end", "type": "Boolean", @@ -349,7 +343,7 @@ "defaultValue": "true", "description": "Perform a check to make sure the file path exists before proceeding with the application", "name": "check_filepath_exists", - "persist": false, + "precious": false, "readonly": false, "text": "Check file path exists", "type": "Boolean", @@ -359,7 +353,7 @@ "defaultValue": "", "description": "Path to the file for this node", "name": "filepath", - "persist": false, + "precious": false, "readonly": false, "text": "File path", "type": "String", @@ -369,11 +363,24 @@ "defaultValue": "", "description": "Name of the directory containing the file for this node", "name": "dirname", - "persist": false, + "precious": false, "readonly": false, "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -412,10 +419,8 @@ "type": "event" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "event", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph b/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph index b08b20849..b8cfe862b 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph @@ -33,8 +33,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -85,8 +83,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -148,8 +144,6 @@ "height": 485.9616075598674, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": true, "readonly": true, @@ -222,8 +216,6 @@ "height": 283.5907928388748, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": true, "readonly": true, @@ -297,8 +289,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -478,8 +468,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -551,8 +539,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -648,8 +634,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -812,8 +796,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -976,8 +958,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1040,8 +1020,6 @@ "height": 197.06393861892587, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": true, "readonly": true, @@ -1113,8 +1091,6 @@ "height": 160, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1165,8 +1141,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1320,8 +1294,7 @@ "height": 193.6368286445012, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, + false, "subject": null, "expanded": true, "readonly": true, @@ -1393,8 +1366,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1457,8 +1428,6 @@ "height": 200, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1518,8 +1487,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1572,8 +1539,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1626,8 +1591,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1800,8 +1763,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1854,8 +1815,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloSBash.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloSBash.graph index bf7ada046..015014d4a 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloSBash.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloSBash.graph @@ -63,6 +63,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -89,7 +102,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" }, @@ -128,6 +140,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -149,7 +174,6 @@ "outputApplication": "Unknown", "outputLocalPorts": [], "outputPorts": [], - "streaming": false, "subject": null, "text": "File" }, @@ -183,6 +207,19 @@ "name": "Arg01", "text": "Arg01", "value": "echo -en 'world' > %o0" + }, + { + "defaultValue": false, + "description": "Specifies whether this data component streams input and output data", + "keyAttribute": false, + "name": "streaming", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Streaming", + "type": "Boolean", + "value": true } ], "inputAppFields": [], @@ -204,7 +241,6 @@ "IdText": "event" } ], - "streaming": true, "subject": null, "text": "Bash Shell App" }, @@ -264,7 +300,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Bash Shell App" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython.graph index 95ceb6301..e60cb2cb9 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython.graph @@ -79,7 +79,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Python App" }, @@ -118,6 +117,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -144,7 +156,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" }, @@ -204,7 +215,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Python App" }, @@ -243,6 +253,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -264,7 +287,6 @@ "outputApplication": "Unknown", "outputLocalPorts": [], "outputPorts": [], - "streaming": false, "subject": null, "text": "File" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython2.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython2.graph index f0c01fa1f..1c17d0868 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython2.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloSPython2.graph @@ -79,7 +79,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Python App" }, @@ -118,6 +117,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -144,7 +156,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" }, @@ -204,7 +215,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Python App" }, @@ -243,6 +253,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -264,7 +287,6 @@ "outputApplication": "Unknown", "outputLocalPorts": [], "outputPorts": [], - "streaming": false, "subject": null, "text": "File" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBash.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBash.graph index 2f090edfb..08bfa7339 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBash.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBash.graph @@ -63,6 +63,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -89,7 +102,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" }, @@ -128,6 +140,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -149,7 +174,6 @@ "outputApplication": "Unknown", "outputLocalPorts": [], "outputPorts": [], - "streaming": false, "subject": null, "text": "File" }, @@ -183,6 +207,19 @@ "name": "Arg01", "text": "Arg01", "value": "echo -en 'Hello world' > %o0" + }, + { + "defaultValue": false, + "description": "Specifies whether this data component streams input and output data", + "keyAttribute": false, + "name": "streaming", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Streaming", + "type": "Boolean", + "value": true } ], "inputAppFields": [], @@ -204,7 +241,6 @@ "IdText": "event" } ], - "streaming": true, "subject": null, "text": "Bash Shell App" }, @@ -264,7 +300,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Bash Shell App" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBashSplit.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBashSplit.graph index 6ca5b30aa..1bd6e04e2 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBashSplit.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldBashSplit.graph @@ -63,6 +63,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -89,7 +102,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" }, @@ -128,6 +140,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -149,7 +174,6 @@ "outputApplication": "Unknown", "outputLocalPorts": [], "outputPorts": [], - "streaming": false, "subject": null, "text": "File" }, @@ -183,6 +207,19 @@ "name": "Arg01", "text": "Arg01", "value": "echo -en 'Hello world' > %o0" + }, + { + "defaultValue": false, + "description": "Specifies whether this data component streams input and output data", + "keyAttribute": false, + "name": "streaming", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Streaming", + "type": "Boolean", + "value": true } ], "inputAppFields": [], @@ -205,7 +242,6 @@ } ], "reprodata": {"rmode": 7}, - "streaming": true, "subject": null, "text": "Bash Shell App" }, @@ -265,7 +301,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Bash Shell App" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldFile.graph b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldFile.graph index 92dc0838e..c6acedf0c 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldFile.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/HelloWorldFile.graph @@ -44,6 +44,19 @@ "name": "dirname", "text": "Directory name", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "inputAppFields": [], @@ -65,7 +78,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "File" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/apps.graph b/daliuge-translator/test/reproducibility/reproGraphs/apps.graph index 632b1dd3c..09ca2d27f 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/apps.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/apps.graph @@ -111,9 +111,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -213,9 +211,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Dynlib App", "width": 200, @@ -315,9 +311,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "MPI", "width": 200, @@ -477,9 +471,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Docker", "width": 200, @@ -579,9 +571,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Python App", "width": 200, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/files.graph b/daliuge-translator/test/reproducibility/reproGraphs/files.graph index 63ae85384..fff30eb18 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/files.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/files.graph @@ -79,9 +79,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Memory", "width": 200, @@ -156,6 +154,19 @@ "text": "Directory name", "type": "Unknown", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -185,9 +196,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -255,9 +264,7 @@ "text": "event" } ], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "NGAS", "width": 200, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/groupUse.graph b/daliuge-translator/test/reproducibility/reproGraphs/groupUse.graph index a128f826c..fda942e84 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/groupUse.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/groupUse.graph @@ -68,7 +68,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Group By" }, @@ -127,7 +126,6 @@ "IdText": "event" } ], - "streaming": false, "subject": null, "text": "Bash Shell App" } diff --git a/daliuge-translator/test/reproducibility/reproGraphs/groups.graph b/daliuge-translator/test/reproducibility/reproGraphs/groups.graph index 266b59b67..e2354ad00 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/groups.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/groups.graph @@ -186,9 +186,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Gather", "width": 200, @@ -330,9 +328,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Scatter", "width": 200, @@ -380,9 +376,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Loop", "width": 493.31143980063956, @@ -451,9 +445,7 @@ "type": "String" } ], - "persist": false, "sha": "63d7df9", - "streaming": false, "subject": null, "text": "Memory", "width": 200, @@ -548,9 +540,7 @@ "type": "Complex" } ], - "persist": false, "sha": "a7abe4c", - "streaming": false, "subject": null, "text": "SleepApp", "width": 200, @@ -629,9 +619,7 @@ "type": "Complex" } ], - "persist": false, "sha": "a7abe4c", - "streaming": false, "subject": null, "text": "Memory", "width": 200, @@ -725,9 +713,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "a7abe4c", - "streaming": false, "subject": null, "text": "SleepApp", "width": 200, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/misc.graph b/daliuge-translator/test/reproducibility/reproGraphs/misc.graph index 2e1913c20..0b8f3954f 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/misc.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/misc.graph @@ -46,9 +46,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Comment", "width": 200, @@ -83,9 +81,7 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "sha": "", - "streaming": false, "subject": null, "text": "Description", "width": 200, @@ -163,9 +159,7 @@ "type": "Complex" } ], - "persist": false, "sha": "63d7df9", - "streaming": false, "subject": null, "text": "Memory", "width": 200, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph b/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph index 639f8a40f..17fa3d2b0 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph @@ -111,6 +111,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -397,6 +410,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -495,6 +521,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -699,6 +738,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph b/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph index aa4d5c8f5..6a54d0d52 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph @@ -111,6 +111,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -140,7 +153,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -246,7 +258,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Scatter", "width": 461, @@ -397,6 +408,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -495,6 +519,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, @@ -699,6 +736,19 @@ "text": "Directory name", "type": "String", "value": "" + }, + { + "defaultValue": true, + "description": "Specifies whether this data component contains data that should not be deleted after execution", + "keyAttribute": false, + "name": "persist", + "options": [], + "positional": false, + "precious": false, + "readonly": false, + "text": "Persist", + "type": "Boolean", + "value": false } ], "flipPorts": false, From cd8ca9101331a3e35fd778de9f39e67aee45777e Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Fri, 28 Oct 2022 15:31:49 +0800 Subject: [PATCH 55/61] Pins pyarrow to 9.0.0 to sidestep deprecation. --- daliuge-engine/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daliuge-engine/setup.py b/daliuge-engine/setup.py index 5fd9fb0b7..3cb3c76ba 100644 --- a/daliuge-engine/setup.py +++ b/daliuge-engine/setup.py @@ -131,7 +131,7 @@ def run(self): "overrides", "paramiko", "psutil", - "pyarrow", + "pyarrow == 9.0.0", "python-daemon", "pyzmq ~= 22.3.0", "scp", From e1782ecc017da5c1caf14115b724fe4dcf9162c6 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 15:38:20 +0800 Subject: [PATCH 56/61] Missed some graph changes in previous commit --- .../topoGraphs/dataSandwich.graph | 3 - .../topoGraphs/testNotDAG.graph | 4 -- .../logical_graphs/HelloWorld_simple.graph | 4 -- .../dropmake/logical_graphs/Plasma_test.graph | 6 -- .../dropmake/logical_graphs/cont_img.graph | 1 - .../logical_graphs/eagle_gather.graph | 26 ------- .../dropmake/logical_graphs/lofar_std.graph | 72 ------------------- .../logical_graphs/test-20190830-110556.graph | 4 -- .../dropmake/logical_graphs/testScatter.graph | 2 - .../logical_graphs/test_grpby_gather.graph | 1 - .../reproGraphs/simpleNoScatter.graph | 7 -- .../reproGraphs/simpleScatter.graph | 5 -- 12 files changed, 135 deletions(-) diff --git a/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph b/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph index bd905c98f..65df475c1 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/dataSandwich.graph @@ -111,7 +111,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -214,7 +213,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -317,7 +315,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, diff --git a/daliuge-engine/test/reproducibility/topoGraphs/testNotDAG.graph b/daliuge-engine/test/reproducibility/topoGraphs/testNotDAG.graph index 90d248fee..d9a2fd0e7 100644 --- a/daliuge-engine/test/reproducibility/topoGraphs/testNotDAG.graph +++ b/daliuge-engine/test/reproducibility/topoGraphs/testNotDAG.graph @@ -93,7 +93,6 @@ ], "selected": true, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -164,7 +163,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -235,7 +233,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -306,7 +303,6 @@ ], "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph b/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph index be07d4cdd..16d23daf8 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/HelloWorld_simple.graph @@ -32,8 +32,6 @@ "collapsed": false, "showPorts": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": true, "expanded": false, @@ -123,8 +121,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph b/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph index 6ea85d12c..ad8d6dfd3 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/Plasma_test.graph @@ -32,7 +32,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -105,7 +104,6 @@ "collapsed": false, "showPorts": true, "flipPorts": false, - "streaming": false, "subject": null, "selected": true, "expanded": false, @@ -194,7 +192,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -297,7 +294,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -386,7 +382,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, @@ -496,7 +491,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph b/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph index 725b8940c..503ec35f9 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/cont_img.graph @@ -541,7 +541,6 @@ ], "readonly": true, "showPorts": false, - , "subject": null, "text": "Cal. Source\nFinding", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph index c4bd1e679..8818f15b1 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/eagle_gather.graph @@ -33,8 +33,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -116,8 +114,6 @@ "height": 432, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -337,8 +333,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -530,8 +524,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -613,8 +605,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -696,8 +686,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -888,8 +876,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1013,8 +999,6 @@ "height": 381, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1234,8 +1218,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1286,8 +1268,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1411,8 +1391,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1604,8 +1582,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1687,8 +1663,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, diff --git a/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph b/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph index e066f1033..2b208e87f 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/lofar_std.graph @@ -143,8 +143,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -195,8 +193,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -255,8 +251,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -339,8 +333,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -412,8 +404,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -485,8 +475,6 @@ "height": 680, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -560,8 +548,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -633,8 +619,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -706,8 +690,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -879,8 +861,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1052,8 +1032,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1125,8 +1103,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1314,8 +1290,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1503,8 +1477,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1576,8 +1548,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1649,8 +1619,6 @@ "height": 810, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1723,8 +1691,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -1896,8 +1862,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2077,8 +2041,6 @@ "height": 200, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2159,8 +2121,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2232,8 +2192,6 @@ "height": 200, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2287,8 +2245,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2364,8 +2320,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2536,8 +2490,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2588,8 +2540,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2760,8 +2710,6 @@ "height": 520, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2833,8 +2781,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -2997,8 +2943,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3078,8 +3022,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3251,8 +3193,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3321,8 +3261,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3504,8 +3442,6 @@ "height": 446, "collapsed": false, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3559,8 +3495,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3611,8 +3545,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3685,8 +3617,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, @@ -3859,8 +3789,6 @@ "height": 72, "collapsed": true, "flipPorts": false, - "streaming": false, - "persist": false, "subject": null, "expanded": false, "readonly": true, diff --git a/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph b/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph index 184d8c788..087b57f2b 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/test-20190830-110556.graph @@ -241,10 +241,8 @@ "type": "" } ], - "persist": false, "readonly": true, "sha": "", - "streaming": false, "subject": null, "text": "Enter label", "width": 200, @@ -362,10 +360,8 @@ "outputApplicationType": "None", "outputLocalPorts": [], "outputPorts": [], - "persist": false, "readonly": false, "sha": "0023469", - "streaming": false, "subject": null, "text": "File", "width": 200, diff --git a/daliuge-translator/test/dropmake/logical_graphs/testScatter.graph b/daliuge-translator/test/dropmake/logical_graphs/testScatter.graph index fce39bf46..ea221814f 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/testScatter.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/testScatter.graph @@ -30,7 +30,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": true, @@ -130,7 +129,6 @@ "collapsed": false, "showPorts": false, "flipPorts": false, - "streaming": false, "subject": null, "selected": false, "expanded": false, diff --git a/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph b/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph index b8cfe862b..c3a579f8e 100644 --- a/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph +++ b/daliuge-translator/test/dropmake/logical_graphs/test_grpby_gather.graph @@ -1294,7 +1294,6 @@ "height": 193.6368286445012, "collapsed": false, "flipPorts": false, - false, "subject": null, "expanded": true, "readonly": true, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph b/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph index 17fa3d2b0..c6f24d997 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/simpleNoScatter.graph @@ -153,7 +153,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -259,7 +258,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Scatter", "width": 461, @@ -349,7 +347,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -460,7 +457,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "scatter", "width": 200, @@ -571,7 +567,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -677,7 +672,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Gather", "width": 286, @@ -781,7 +775,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, diff --git a/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph b/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph index 6a54d0d52..86aa76449 100644 --- a/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph +++ b/daliuge-translator/test/reproducibility/reproGraphs/simpleScatter.graph @@ -347,7 +347,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Bash Shell App", "width": 200, @@ -458,7 +457,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "scatter", "width": 200, @@ -569,7 +567,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, @@ -675,7 +672,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "Gather", "width": 286, @@ -779,7 +775,6 @@ "readonly": false, "selected": false, "showPorts": false, - "streaming": false, "subject": null, "text": "File", "width": 200, From e82c095a025b070003907311540030ded3090c16 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Fri, 28 Oct 2022 15:45:49 +0800 Subject: [PATCH 57/61] Removed 'streaming' attribute from reproducibility tests --- .../dlg/common/reproducibility/reproducibility_fields.py | 2 -- .../test/reproducibility/test_accumulatedata.py | 6 ------ 2 files changed, 8 deletions(-) diff --git a/daliuge-common/dlg/common/reproducibility/reproducibility_fields.py b/daliuge-common/dlg/common/reproducibility/reproducibility_fields.py index c62d07f8b..e304a4125 100644 --- a/daliuge-common/dlg/common/reproducibility/reproducibility_fields.py +++ b/daliuge-common/dlg/common/reproducibility/reproducibility_fields.py @@ -74,14 +74,12 @@ def lgt_block_fields(rmode: ReproducibilityFlags): "outputPorts": FieldOps.COUNT, "inputLocalPorts": FieldOps.COUNT, "outputLocalPorts": FieldOps.COUNT, # MKN Nodes - "streaming": FieldOps.STORE, } if rmode == ReproducibilityFlags.REPRODUCE: del data["inputPorts"] del data["outputPorts"] del data["inputLocalPorts"] del data["outputLocalPorts"] - del data["streaming"] return data diff --git a/daliuge-translator/test/reproducibility/test_accumulatedata.py b/daliuge-translator/test/reproducibility/test_accumulatedata.py index 8b0a65030..c8fbf5856 100644 --- a/daliuge-translator/test/reproducibility/test_accumulatedata.py +++ b/daliuge-translator/test/reproducibility/test_accumulatedata.py @@ -106,7 +106,6 @@ class AccumulateLGTRerunData(unittest.TestCase): "inputLocalPorts", "outputLocalPorts", "outputPorts", - "streaming", } ddGraph = "graphs/ddTest.graph" @@ -433,7 +432,6 @@ class AccumulateLGTRepeatData(unittest.TestCase): "inputLocalPorts", "outputPorts", "outputLocalPorts", - "streaming", } file = "reproducibility/reproGraphs/apps.graph" @@ -833,7 +831,6 @@ class AccumulateLGTRecomputeData(unittest.TestCase): "inputLocalPorts", "outputPorts", "outputLocalPorts", - "streaming", } file = "reproducibility/reproGraphs/apps.graph" @@ -1608,7 +1605,6 @@ class AccumulateLGTReplicateSciData(unittest.TestCase): "inputLocalPorts", "outputPorts", "outputLocalPorts", - "streaming", } file = "reproducibility/reproGraphs/apps.graph" @@ -1934,7 +1930,6 @@ class AccumulateLGTReplicateCompData(unittest.TestCase): "inputLocalPorts", "outputPorts", "outputLocalPorts", - "streaming", } file = "reproducibility/reproGraphs/apps.graph" @@ -2333,7 +2328,6 @@ class AccumulateLGTReplicateTotalData(unittest.TestCase): "inputLocalPorts", "outputPorts", "outputLocalPorts", - "streaming", } file = "reproducibility/reproGraphs/apps.graph" From af0a8f174a1b922ac565b099976a20429fd819ca Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 31 Oct 2022 16:35:52 +0800 Subject: [PATCH 58/61] Changes Deployment option enum to (str, Enum) for more intuitive code-use. --- daliuge-common/dlg/common/deployment_methods.py | 2 +- daliuge-engine/dlg/manager/rest.py | 2 +- daliuge-engine/test/manager/test_rest.py | 2 +- daliuge-translator/dlg/dropmake/web/translator_rest.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/daliuge-common/dlg/common/deployment_methods.py b/daliuge-common/dlg/common/deployment_methods.py index 0e65cf4dd..77e984044 100644 --- a/daliuge-common/dlg/common/deployment_methods.py +++ b/daliuge-common/dlg/common/deployment_methods.py @@ -23,7 +23,7 @@ from enum import Enum -class DeploymentMethods(Enum): +class DeploymentMethods(str, Enum): SERVER = "SERVER", BROWSER = "BROWSER", HELM = "HELM", diff --git a/daliuge-engine/dlg/manager/rest.py b/daliuge-engine/dlg/manager/rest.py index baeca87f8..8e3a72801 100644 --- a/daliuge-engine/dlg/manager/rest.py +++ b/daliuge-engine/dlg/manager/rest.py @@ -197,7 +197,7 @@ def initializeSpecifics(self, app): @daliuge_aware def submit_methods(self): - return {"methods": [DeploymentMethods.BROWSER.name]} + return {"methods": [DeploymentMethods.BROWSER]} def _stop_manager(self): self.dm.shutdown() diff --git a/daliuge-engine/test/manager/test_rest.py b/daliuge-engine/test/manager/test_rest.py index f002fbc73..b90a23564 100644 --- a/daliuge-engine/test/manager/test_rest.py +++ b/daliuge-engine/test/manager/test_rest.py @@ -273,4 +273,4 @@ def test_reprostatus_get(self): def test_submit_method(self): c = NodeManagerClient(hostname) response = c.get_submission_method() - self.assertEqual({"methods": [DeploymentMethods.BROWSER.name]}, response) + self.assertEqual({"methods": [DeploymentMethods.BROWSER]}, response) diff --git a/daliuge-translator/dlg/dropmake/web/translator_rest.py b/daliuge-translator/dlg/dropmake/web/translator_rest.py index c7c3406d3..6e84baa60 100644 --- a/daliuge-translator/dlg/dropmake/web/translator_rest.py +++ b/daliuge-translator/dlg/dropmake/web/translator_rest.py @@ -816,7 +816,7 @@ def get_submission_method( available_methods.append(DeploymentMethods.HELM) if mhost is not None: host_available_methods = get_mgr_deployment_methods(mhost, mport, mprefix) - if DeploymentMethods.BROWSER.name in host_available_methods: + if DeploymentMethods.BROWSER in host_available_methods: available_methods.append(DeploymentMethods.SERVER) return {"methods": available_methods} From 3eef05a0fb934f3cfd9db44671a194ff527bcac2 Mon Sep 17 00:00:00 2001 From: pritchardn <21726929@student.uwa.edu.au> Date: Mon, 31 Oct 2022 16:36:14 +0800 Subject: [PATCH 59/61] Condenses two ifs. --- daliuge-translator/dlg/dropmake/web/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/web/main.js b/daliuge-translator/dlg/dropmake/web/main.js index f7b2bc991..4e727490d 100644 --- a/daliuge-translator/dlg/dropmake/web/main.js +++ b/daliuge-translator/dlg/dropmake/web/main.js @@ -117,8 +117,7 @@ async function initiateDeploy(method, selected, clickedName) { $("#gen_pg_button").val("Generate & Deploy Physical Graph") $("#dlg_mgr_deploy").prop("checked", true) $("#pg_form").submit(); - } - if (method === DEFAULT_OPTIONS.HELM) { + } else if (method === DEFAULT_OPTIONS.HELM) { $("#gen_helm_button").val("Generate & Deploy Physical Graph") $("#dlg_helm_deploy").prop("checked", true) $("#pg_helm_form").submit() From ebc97abb8b0a3b87c54f6672883f5ef52523c933 Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Tue, 1 Nov 2022 10:48:20 +0800 Subject: [PATCH 60/61] Add 'applicationArgs' attribute back to the graph schema --- .../dlg/dropmake/lg.graph.schema | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/daliuge-translator/dlg/dropmake/lg.graph.schema b/daliuge-translator/dlg/dropmake/lg.graph.schema index d11634288..2433ea15f 100644 --- a/daliuge-translator/dlg/dropmake/lg.graph.schema +++ b/daliuge-translator/dlg/dropmake/lg.graph.schema @@ -130,6 +130,7 @@ "expanded": { "type": "boolean" }, + "inputApplicationName": { "type": "string" }, @@ -324,6 +325,58 @@ ] } }, + "applicationArgs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": ["string","boolean","number", "null"] + }, + "defaultValue": { + "type": ["string","boolean","number", "null"] + }, + "description": { + "type": "string" + }, + "readonly": { + "type": "boolean", + }, + "type": { + "type": "string" + }, + "precious": { + "type": "boolean" + }, + "options": { + "type": "array" + }, + "positional": { + "type": "boolean", + "default": false + }, + "keyAttribute": { + "type": "boolean" + } + }, + "required": [ + "description", + "name", + "text", + "value" + "type", + "precious", + "options", + "positional" + ] + } + }, "group": { "type": "integer" } @@ -338,6 +391,7 @@ "drawOrderHint", "expanded", "fields", + "applicationArgs", "height", "inputAppFields", "inputApplicationName", From 3e4579ac1f775168d45995cc08da3789680fffbd Mon Sep 17 00:00:00 2001 From: james-strauss-uwa Date: Wed, 2 Nov 2022 16:44:03 +0800 Subject: [PATCH 61/61] Minor fixes to JSON schema --- daliuge-translator/dlg/dropmake/lg.graph.schema | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daliuge-translator/dlg/dropmake/lg.graph.schema b/daliuge-translator/dlg/dropmake/lg.graph.schema index 2433ea15f..ff1d3bf09 100644 --- a/daliuge-translator/dlg/dropmake/lg.graph.schema +++ b/daliuge-translator/dlg/dropmake/lg.graph.schema @@ -346,7 +346,7 @@ "type": "string" }, "readonly": { - "type": "boolean", + "type": "boolean" }, "type": { "type": "string" @@ -369,7 +369,7 @@ "description", "name", "text", - "value" + "value", "type", "precious", "options",