From 7f3e2aa108c7efa8f5182de6930ca4bcbb43af25 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Thu, 19 Aug 2021 10:44:06 -0400 Subject: [PATCH 01/14] draft job services --- src/sasctl/_services/job_definitions.py | 42 +++++++++++++++++++++++++ src/sasctl/_services/job_execution.py | 19 +++++++++++ src/sasctl/services.py | 2 ++ 3 files changed, 63 insertions(+) create mode 100644 src/sasctl/_services/job_definitions.py create mode 100644 src/sasctl/_services/job_execution.py diff --git a/src/sasctl/_services/job_definitions.py b/src/sasctl/_services/job_definitions.py new file mode 100644 index 00000000..e6f60140 --- /dev/null +++ b/src/sasctl/_services/job_definitions.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from .service import Service + + +class JobDefinitions(Service): + """ + Implements the Job Definitions REST API. + + The Job Definitions API manages jobs using the Create, Read, Update, and Delete operations. A Job Definition + is a batch execution that contains a list of input parameters, a job type, and a "code" attribute. A job + definition can run in multiple execution environments, based on the type of the job. + + See Also + -------- + `REST Documentation `_ + """ + + _SERVICE_ROOT = '/jobDefinitions' + + ( + list_definitions, + get_definition, + update_definition, + delete_definition, + ) = Service._crud_funcs('/definitions') + + # def get_definition(self, ): + # raise NotImplementedError() + + def get_summary(self): + raise NotImplementedError() + + def get_headers(self): + raise NotImplementedError() + + def create_definition(self): + raise NotImplementedError() diff --git a/src/sasctl/_services/job_execution.py b/src/sasctl/_services/job_execution.py new file mode 100644 index 00000000..50208a75 --- /dev/null +++ b/src/sasctl/_services/job_execution.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +from .service import Service + + +class JobExecution(Service): + """The Job Execution service provides a common mechanism for executing and managing asynchronous jobs. + + This API works in conjunction with the Job Definitions service. You can use the Job Definitions service to create a + definition for any existing Job Execution Provider. You can then execute this definition using the Job Execution + service. + """ + + _SERVICE_ROOT = '/jobExecution' diff --git a/src/sasctl/services.py b/src/sasctl/services.py index c46f876e..2a68b59f 100644 --- a/src/sasctl/services.py +++ b/src/sasctl/services.py @@ -18,6 +18,8 @@ def _instantiate(name): data_sources = _instantiate('sasctl._services.data_sources.DataSources') files = _instantiate('sasctl._services.files.Files') folders = _instantiate('sasctl._services.folders.Folders') +job_definitions = _instantiate('sasctl._services.job_definitions.JobDefinitions') +job_execution = _instantiate('sasctl._services.job_execution.JobExecution') microanalytic_score = _instantiate( 'sasctl._services.microanalytic_score.MicroAnalyticScore') model_management = _instantiate( From 482c970a59f753d168049bd7667c246f0a822826 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Thu, 19 Aug 2021 15:13:58 -0400 Subject: [PATCH 02/14] create job definition --- src/sasctl/_services/job_definitions.py | 70 ++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/sasctl/_services/job_definitions.py b/src/sasctl/_services/job_definitions.py index e6f60140..56b248df 100644 --- a/src/sasctl/_services/job_definitions.py +++ b/src/sasctl/_services/job_definitions.py @@ -5,6 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 from .service import Service +from ..core import RestObj class JobDefinitions(Service): @@ -38,5 +39,70 @@ def get_summary(self): def get_headers(self): raise NotImplementedError() - def create_definition(self): - raise NotImplementedError() + @classmethod + def create_definition( + cls, + name: str = None, + description: str = None, + type_: str = None, + code: str = None, + parameters=None, + properties: dict = None, + ) -> RestObj: + """ + + Parameters + ---------- + name + description + type_ + code + parameters + properties + + Returns + ------- + RestObj + + """ + + # Convert name:value pairs of properties to separate "name" & "value" fields. + properties = properties or {} + properties = [{'name': k, 'value': v} for k, v in properties.items()] + + clean_parameters = [] + for param in parameters: + param_type = str(param.get('type', '')).upper() + if param_type not in ('TABLE', 'NUMERIC', 'DATE', 'CHARACTER'): + # TODO: warn/raise + continue + + new_param = { + 'version': 1, + 'name': str(param.get('name', ''))[ + :100 + ], # Max length of 100 characters + 'type': param_type, + 'label': str(param.get('label', ''))[ + :250 + ], # Max length of 250 characters + 'required': bool(param.get('required')), + 'defaultValue': param.get('defaultValue'), + } + clean_parameters.append(new_param) + + definition = { + 'version': 2, + 'name': name, + 'description': description, + 'type': type_, + 'code': code, + 'parameters': clean_parameters, + 'properties': properties, + } + + return cls.post( + '/definitions', + json=definition, + headers={'Accept': 'application/vnd.sas.job.definition+json'}, + ) From f72c536e181794cfb9a04fc5c27aa192e30583c5 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Mon, 23 Aug 2021 15:34:37 -0400 Subject: [PATCH 03/14] added compute service --- src/sasctl/_services/compute.py | 20 ++++++++++++++++++++ src/sasctl/services.py | 1 + 2 files changed, 21 insertions(+) create mode 100644 src/sasctl/_services/compute.py diff --git a/src/sasctl/_services/compute.py b/src/sasctl/_services/compute.py new file mode 100644 index 00000000..1b2c84ec --- /dev/null +++ b/src/sasctl/_services/compute.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from .service import Service + + +class Compute(Service): + """The Compute service affords CRUD operations for Compute Contexts. + + A Compute context is analogous to the SAS Application Server from SAS V9. + """ + + _SERVICE_ROOT = '/compute' + + list_contexts, get_context, update_context, delete_context = Service._crud_funcs( + '/contexts' + ) diff --git a/src/sasctl/services.py b/src/sasctl/services.py index 2a68b59f..dee4504e 100644 --- a/src/sasctl/services.py +++ b/src/sasctl/services.py @@ -14,6 +14,7 @@ def _instantiate(name): return cls() cas_management = _instantiate('sasctl._services.cas_management.CASManagement') +compute = _instantiate('sasctl._services.compute.Compute') concepts = _instantiate('sasctl._services.concepts.Concepts') data_sources = _instantiate('sasctl._services.data_sources.DataSources') files = _instantiate('sasctl._services.files.Files') From 9d25733017fb8acd537180275151d6ec2302cd6e Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Tue, 24 Aug 2021 16:44:32 -0400 Subject: [PATCH 04/14] create_job() & create_session() --- src/sasctl/_services/compute.py | 75 +++++++++++++++++++++++++ src/sasctl/_services/job_definitions.py | 2 + src/sasctl/_services/job_execution.py | 17 ++++++ 3 files changed, 94 insertions(+) diff --git a/src/sasctl/_services/compute.py b/src/sasctl/_services/compute.py index 1b2c84ec..5f38ce93 100644 --- a/src/sasctl/_services/compute.py +++ b/src/sasctl/_services/compute.py @@ -18,3 +18,78 @@ class Compute(Service): list_contexts, get_context, update_context, delete_context = Service._crud_funcs( '/contexts' ) + + list_servers, _, _, _ = Service._crud_funcs('/servers') + + list_sessions, _, _, _ = Service._crud_funcs('/sessions') + + @classmethod + def create_job( + cls, + session, + code, + name=None, + description=None, + environment=None, + variables=None, + resources=None, + attributes=None, + ): + """ + + Parameters + ---------- + session + code + name + description + environment + variables + resources + attributes + + Returns + ------- + + """ + + # TODO: if code is URI pass in codeUri field. + code = code.split('\n') + + variables = variables or [] + resources = resources or [] + environment = environment or {} + attributes = attributes or {} + data = { + 'version': 3, + 'name': name, + 'description': description, + 'environment': environment, + 'variables': variables, + 'code': code, + 'resources': resources, + 'attributes': attributes, + } + + return cls.request_link(session, 'execute', json=data) + + @classmethod + def create_session(cls, context): + """Create a new session based on an existing Compute Context. + + If a reusable SAS Compute Server is available to handle this session, a new session is created on that SAS + Compute Server. Otherwise, a new SAS Compute Server is created and the new session is created there. + + Parameters + ---------- + context : RestObj + An existing Compute Context as returned by `get_context`. + + Returns + ------- + RestObj + Session details + """ + context = cls.get_context(context) + + return cls.request_link(context, 'createSession') diff --git a/src/sasctl/_services/job_definitions.py b/src/sasctl/_services/job_definitions.py index 56b248df..daa472bf 100644 --- a/src/sasctl/_services/job_definitions.py +++ b/src/sasctl/_services/job_definitions.py @@ -70,6 +70,8 @@ def create_definition( properties = properties or {} properties = [{'name': k, 'value': v} for k, v in properties.items()] + parameters = parameters or [] + clean_parameters = [] for param in parameters: param_type = str(param.get('type', '')).upper() diff --git a/src/sasctl/_services/job_execution.py b/src/sasctl/_services/job_execution.py index 50208a75..a9e530e5 100644 --- a/src/sasctl/_services/job_execution.py +++ b/src/sasctl/_services/job_execution.py @@ -17,3 +17,20 @@ class JobExecution(Service): """ _SERVICE_ROOT = '/jobExecution' + + @classmethod + def create_job(cls, definition, name=None, description=None, parameters=None): + + # TODO: parameters + # TODO: definition id not RestObj passed + # TODO: get link fails + uri = cls.get_link(definition, 'self')['uri'] + + data = { + 'jobDefinitionUri': uri + } + + headers = { + 'Accept': 'application/vnd.sas.job.execution.job+json' + } + return cls.post('/jobs', headers=headers, json=data) \ No newline at end of file From 61cd4e533b22aa1819c0c3bf3921285c2267240c Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Tue, 14 Sep 2021 15:30:01 -0400 Subject: [PATCH 05/14] added support for arguments --- src/sasctl/_services/job_execution.py | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/sasctl/_services/job_execution.py b/src/sasctl/_services/job_execution.py index a9e530e5..1eb02016 100644 --- a/src/sasctl/_services/job_execution.py +++ b/src/sasctl/_services/job_execution.py @@ -20,17 +20,41 @@ class JobExecution(Service): @classmethod def create_job(cls, definition, name=None, description=None, parameters=None): + """Execute a job from an existing job definition. + + Parameters + ---------- + definition : str or RestObj + name : str, optional + Name for the requested job. + description : str, optional + Description of the requested job + parameters : dict, optional + Parameter name/value pairs used to overwrite default parameters defined in job definition. + + Returns + ------- + RestObj + Job request details + + """ - # TODO: parameters # TODO: definition id not RestObj passed # TODO: get link fails uri = cls.get_link(definition, 'self')['uri'] + parameters = parameters or {} + # TODO: expiresAfter - set to reasonable default + data = { - 'jobDefinitionUri': uri + 'name': name, + 'description': description, + 'jobDefinitionUri': uri, + 'arguments': parameters } headers = { - 'Accept': 'application/vnd.sas.job.execution.job+json' + 'Accept': 'application/vnd.sas.job.execution.job+json', + 'Content-Type': 'application/vnd.sas.job.execution.job.request+json' } - return cls.post('/jobs', headers=headers, json=data) \ No newline at end of file + return cls.post('/jobs', headers=headers, json=data) From 89d54b7e900f96fc643e8590f69a4eac80eca3f5 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Tue, 14 Sep 2021 16:07:00 -0400 Subject: [PATCH 06/14] initial documentation draft --- doc/api/services/compute.rst | 7 +++++++ doc/api/services/job_definitions.rst | 7 +++++++ doc/api/services/job_execution.rst | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 doc/api/services/compute.rst create mode 100644 doc/api/services/job_definitions.rst create mode 100644 doc/api/services/job_execution.rst diff --git a/doc/api/services/compute.rst b/doc/api/services/compute.rst new file mode 100644 index 00000000..9f63b4e3 --- /dev/null +++ b/doc/api/services/compute.rst @@ -0,0 +1,7 @@ +sasctl.services.compute +======================= + +.. automodule:: sasctl._services.compute + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/services/job_definitions.rst b/doc/api/services/job_definitions.rst new file mode 100644 index 00000000..0b50fef6 --- /dev/null +++ b/doc/api/services/job_definitions.rst @@ -0,0 +1,7 @@ +sasctl.services.job_definitions +=============================== + +.. automodule:: sasctl._services.job_definitions + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/api/services/job_execution.rst b/doc/api/services/job_execution.rst new file mode 100644 index 00000000..a96573d0 --- /dev/null +++ b/doc/api/services/job_execution.rst @@ -0,0 +1,7 @@ +sasctl.services.job_execution +============================= + +.. automodule:: sasctl._services.job_execution + :members: + :undoc-members: + :show-inheritance: From 7543ff6718de2a7b6a3ada173344afc712f8af04 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Tue, 14 Sep 2021 16:50:06 -0400 Subject: [PATCH 07/14] test creating definition --- src/sasctl/_services/job_definitions.py | 24 +-- ...JobDefinitions.test_create_definition.json | 192 ++++++++++++++++++ tests/integration/test_job_definitions.py | 42 ++++ 3 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition.json create mode 100644 tests/integration/test_job_definitions.py diff --git a/src/sasctl/_services/job_definitions.py b/src/sasctl/_services/job_definitions.py index daa472bf..e5dd1c5c 100644 --- a/src/sasctl/_services/job_definitions.py +++ b/src/sasctl/_services/job_definitions.py @@ -30,15 +30,6 @@ class JobDefinitions(Service): delete_definition, ) = Service._crud_funcs('/definitions') - # def get_definition(self, ): - # raise NotImplementedError() - - def get_summary(self): - raise NotImplementedError() - - def get_headers(self): - raise NotImplementedError() - @classmethod def create_definition( cls, @@ -49,14 +40,19 @@ def create_definition( parameters=None, properties: dict = None, ) -> RestObj: - """ + """Define a new job that can be run in the SAS environment Parameters ---------- - name - description - type_ - code + name : str + Job name + description : str + Job description + type_ : {'casl', 'Compute'} + Indicates type of code specified by `code`. Use 'casl' if `code` is CASL or 'Compute' if `code` is + data step. + code : str + Code to be executed whenever this job is run. parameters properties diff --git a/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition.json b/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition.json new file mode 100644 index 00000000..2cf11d45 --- /dev/null +++ b/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition.json @@ -0,0 +1,192 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-14T20:49:33", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE2ODg1NzMsImlhdCI6MTYzMTY1MjU3MywiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiJlYzNjYTA0OTAzZTk0YTc4ODI4MjJkNTc4YTExYzJjYiIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE2NTI0NDE4MDgsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE2NTI1NzMsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.b0KeW46rt0nLj62byclH9dNVZmLAhggYXZ3qa1H28qV7M4abWqwM0a1dStAvnTrm-Pn-Q3LmhRHcwN312LUnLsUdvnLl1o_u8C5dEAvivg1VOKCiIw8HQbsmRsEYlhZF60KQyhNDGSJUeu64GSBVHJCVj3i5y0ietqoIEIZnJGz7cbbdo_fzz7dUOduZEGP9mwciRHsJSlE0195GEARqUhPwPmJdDwY72-DeJHORfhO8muIfLjrd7ahVjvHdE2U4xAGiYcJw6gQYuFjT79uTYeQMN7WaP0PpJUAL9wWwggSe1YKqCmV2E8ZsRl3RnCT-yuwHMw_1oAm4ybTxmzRO0Q\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"ec3ca04903e94a7882822d578a11c2cb\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Tue, 14 Sep 2021 20:49:32 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + }, + { + "recorded_at": "2021-09-14T20:49:33", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"version\": 2, \"name\": \"sasctl_test_job\", \"description\": \"Test Job Definition from sasctl\", \"type\": \"Compute\", \"code\": \"proc print data=&TABLE;\", \"parameters\": [{\"version\": 1, \"name\": \"_contextName\", \"type\": \"CHARACTER\", \"label\": \"Context Name\", \"required\": false, \"defaultValue\": \"SAS Studio compute context\"}, {\"version\": 1, \"name\": \"TABLE\", \"type\": \"CHARACTER\", \"label\": \"Table Name\", \"required\": true, \"defaultValue\": null}], \"properties\": []}" + }, + "headers": { + "Accept": [ + "application/vnd.sas.job.definition+json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "447" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/jobDefinitions/definitions" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"creationTimeStamp\":\"2021-09-14T20:49:33.350Z\",\"modifiedTimeStamp\":\"2021-09-14T20:49:33.351Z\",\"createdBy\":\"USERNAME\",\"modifiedBy\":\"USERNAME\",\"version\":2,\"id\":\"6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"name\":\"sasctl_test_job\",\"description\":\"Test Job Definition from sasctl\",\"type\":\"Compute\",\"parameters\":[{\"version\":1,\"name\":\"_contextName\",\"defaultValue\":\"SAS Studio compute context\",\"type\":\"CHARACTER\",\"label\":\"Context Name\",\"required\":false},{\"version\":1,\"name\":\"TABLE\",\"type\":\"CHARACTER\",\"label\":\"Table Name\",\"required\":true}],\"code\":\"proc print data=&TABLE;\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"uri\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"type\":\"application/vnd.sas.job.definition\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"uri\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"type\":\"application/vnd.sas.summary\"},{\"method\":\"PUT\",\"rel\":\"update\",\"href\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"uri\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"type\":\"application/vnd.sas.job.definition\",\"responseType\":\"application/vnd.sas.job.definition\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\",\"uri\":\"/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff\"}],\"properties\":[]}" + }, + "headers": { + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Security-Policy": [ + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;" + ], + "Content-Type": [ + "application/vnd.sas.job.definition+json" + ], + "Date": [ + "Tue, 14 Sep 2021 20:49:32 GMT" + ], + "ETag": [ + "\"ktkjtb53\"" + ], + "Expires": [ + "0" + ], + "Keep-Alive": [ + "timeout=5, max=99" + ], + "Last-Modified": [ + "Tue, 14 Sep 2021 20:49:33 GMT" + ], + "Location": [ + "/jobDefinitions/definitions/6e2c5006-d32a-4c85-ae39-b5325e2a1eff" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 201, + "message": "" + }, + "url": "https://hostname.com/jobDefinitions/definitions" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/integration/test_job_definitions.py b/tests/integration/test_job_definitions.py new file mode 100644 index 00000000..d15ecd2d --- /dev/null +++ b/tests/integration/test_job_definitions.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +from unittest import mock + +import pytest +from sasctl import RestObj +from sasctl.services import job_definitions + +# Each test will receive a (recorded) Session instance +pytestmark = pytest.mark.usefixtures('session') + + +@pytest.mark.incremental +class TestJobDefinitions: + def test_create_definition(self): + + params = dict( + name='sasctl_test_job', + description='Test Job Definition from sasctl', + type_='Compute', + code='proc print data=&TABLE;', + parameters=[ + dict(name='_contextName', + defaultValue='SAS Studio compute context', + type='character', + label='Context Name'), + dict(name='TABLE', + type='character', + label='Table Name', + required=True) + ] + ) + definition = job_definitions.create_definition(**params) + + assert isinstance(definition, RestObj) + assert definition['name'] == params['name'] + assert definition['description'] == params['description'] From 76a5e977fdb9a18428b4f0b8ae4d6099ae68e167 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Tue, 14 Sep 2021 17:13:38 -0400 Subject: [PATCH 08/14] check parameter types --- src/sasctl/_services/job_definitions.py | 24 ++++- ...ions.test_create_definition_bad_param.json | 91 +++++++++++++++++++ tests/integration/test_job_definitions.py | 18 ++++ 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition_bad_param.json diff --git a/src/sasctl/_services/job_definitions.py b/src/sasctl/_services/job_definitions.py index e5dd1c5c..83861020 100644 --- a/src/sasctl/_services/job_definitions.py +++ b/src/sasctl/_services/job_definitions.py @@ -53,8 +53,20 @@ def create_definition( data step. code : str Code to be executed whenever this job is run. - parameters - properties + parameters : list of dict + List of parameters used by the job. Each entry in the list should be a dictionary with the following items: + - name : str + parameter name + - type : {'character', 'date', 'numeric', 'table'} + parameter type + - label : str + human-readable label for parameter + - required : bool + is it required to specify a parameter value when running the job + - defaultValue : any + parameter's default value if not specified when running job + properties : dict + An arbitrary collection of name/value pairs to associate with the job definition. Returns ------- @@ -72,8 +84,12 @@ def create_definition( for param in parameters: param_type = str(param.get('type', '')).upper() if param_type not in ('TABLE', 'NUMERIC', 'DATE', 'CHARACTER'): - # TODO: warn/raise - continue + raise ValueError( + "Type '{}' for parameter '{}' is invalid. " + "Expected one of ('TABLE', 'NUMERIC', 'DATE', 'CHARACTER')".format( + param_type, param['name'] + ) + ) new_param = { 'version': 1, diff --git a/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition_bad_param.json b/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition_bad_param.json new file mode 100644 index 00000000..2f1be96e --- /dev/null +++ b/tests/cassettes/tests.integration.test_job_definitions.TestJobDefinitions.test_create_definition_bad_param.json @@ -0,0 +1,91 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-14T21:11:15", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE2ODk4NzUsImlhdCI6MTYzMTY1Mzg3NSwiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiJkOTg3N2I4ZWE3NmM0YzY2YWM0Zjg4N2M2ZjFiZmZmYiIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE2NTI1NzMxNTYsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE2NTM4NzUsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.gdG9fh8zkN9Evabq2MzxgJbHwHvq5-0rTqqxAXLDcnYez9M41KzdDSh883aLHMfeGtmGc4pezEnXWALwkJLFQN3wVucAN06GWG76bKsVgvC8YqZMhdL_xpL__XrnokgN5LO_SIikqmqyTjNDQ1bBZhQONQVKa_2PCxqIXGMn8BO5ErEehCzGb4S06VvOoAcc1_i9yurh4zi_7GZPnuZBSwK242_UdXGw2cqpQ1zg-BD7oi9Jhznp5ThRMFESNhc6igK-uGMN1ATdvnhBJkwH3UdfHIaHspe-az9KgDbSl-GHX0Zu4P1tQSHxwjIfKKubdXX85225VEOE_hzol7SS1A\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"d9877b8ea76c4c66ac4f887c6f1bfffb\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Tue, 14 Sep 2021 21:11:14 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/integration/test_job_definitions.py b/tests/integration/test_job_definitions.py index d15ecd2d..73a92891 100644 --- a/tests/integration/test_job_definitions.py +++ b/tests/integration/test_job_definitions.py @@ -40,3 +40,21 @@ def test_create_definition(self): assert isinstance(definition, RestObj) assert definition['name'] == params['name'] assert definition['description'] == params['description'] + + def test_create_definition_bad_param(self): + params = dict( + name='sasctl_test_job', + type_='Compute', + code='proc print data=&TABLE;', + parameters=[ + dict(name='TABLE', + type='decimal', + label='Table Name', + required=True) + ] + ) + + with pytest.raises(ValueError) as e: + job_definitions.create_definition(**params) + + assert "'DECIMAL'" in str(e.value) From fa1f16fff63ad318cf1d75d1ccd88fd74d275f25 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Wed, 15 Sep 2021 11:45:55 -0400 Subject: [PATCH 09/14] get job state --- src/sasctl/_services/job_execution.py | 39 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/sasctl/_services/job_execution.py b/src/sasctl/_services/job_execution.py index 1eb02016..03195fb2 100644 --- a/src/sasctl/_services/job_execution.py +++ b/src/sasctl/_services/job_execution.py @@ -6,6 +6,7 @@ from .service import Service +from .job_definitions import JobDefinitions class JobExecution(Service): @@ -18,6 +19,8 @@ class JobExecution(Service): _SERVICE_ROOT = '/jobExecution' + list_jobs, get_job, update_job, delete_job = Service._crud_funcs('/jobs') + @classmethod def create_job(cls, definition, name=None, description=None, parameters=None): """Execute a job from an existing job definition. @@ -39,22 +42,46 @@ def create_job(cls, definition, name=None, description=None, parameters=None): """ - # TODO: definition id not RestObj passed - # TODO: get link fails - uri = cls.get_link(definition, 'self')['uri'] + # Convert id/name into full definition object + definition_obj = JobDefinitions.get_definition(definition) + + if definition_obj is None: + raise ValueError("Unable to find job definition '%s'." % definition) + + uri = cls.get_link(definition_obj, 'self')['uri'] parameters = parameters or {} - # TODO: expiresAfter - set to reasonable default data = { 'name': name, 'description': description, 'jobDefinitionUri': uri, - 'arguments': parameters + 'arguments': parameters, } headers = { 'Accept': 'application/vnd.sas.job.execution.job+json', - 'Content-Type': 'application/vnd.sas.job.execution.job.request+json' + 'Content-Type': 'application/vnd.sas.job.execution.job.request+json', } return cls.post('/jobs', headers=headers, json=data) + + @classmethod + def get_job_state(cls, job): + """Check the status of an existing job. + + Parameters + ---------- + job : str or dict + The name or id of the job, or a dictionary representation of the job. + + Returns + ------- + str + {'pending', 'running', 'canceled', 'completed', failed'} + + """ + + job_obj = cls.get_job(job) + uri = '/jobs/%s/state' % job_obj.id + + return cls.get(uri) From 83aab6ff0d237860628e5fc47e9622b9b22a2a97 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Wed, 15 Sep 2021 12:17:15 -0400 Subject: [PATCH 10/14] list_sessions allows filtering by server. get server state. --- src/sasctl/_services/compute.py | 60 +++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/sasctl/_services/compute.py b/src/sasctl/_services/compute.py index 5f38ce93..c032f764 100644 --- a/src/sasctl/_services/compute.py +++ b/src/sasctl/_services/compute.py @@ -19,9 +19,64 @@ class Compute(Service): '/contexts' ) - list_servers, _, _, _ = Service._crud_funcs('/servers') + list_servers, get_server, _, stop_server = Service._crud_funcs('/servers') - list_sessions, _, _, _ = Service._crud_funcs('/sessions') + _, _, _, delete_session = Service._crud_funcs('/sessions') + + @classmethod + def list_sessions(cls, server=None): + """List currently active sessions. + + Parameters + ---------- + server : str or dict, optional + The name or id of the server, or a dictionary representation of the server. If specified, only sessions + for that server will be returned. Otherwise, sessions for all running servers are returned. + + Returns + ------- + list of RestObj + + Raises + ------ + ValueError + If `server` is specified but not found. + + """ + if server is not None: + server_obj = cls.get_server(server) + if server_obj is None: + raise ValueError("Unable to find server '%s'." % server) + uri = '/servers/%s/sessions' % server_obj['id'] + else: + uri = '/sessions' + + results = cls.get(uri) + if isinstance(results, list): + return results + + return [results] + + @classmethod + def get_server_state(cls, server): + """ + + Parameters + ---------- + server : str or dict + The name or id of the server, or a dictionary representation of the server. + + + Returns + ------- + str + {'running', 'stopped'} + + """ + server_obj = cls.get_server(server) + uri = '/servers/%s/state' % server_obj['id'] + + return cls.get(uri) @classmethod def create_job( @@ -89,6 +144,7 @@ def create_session(cls, context): ------- RestObj Session details + """ context = cls.get_context(context) From d98ce72b15f4f2dddbdf708c3473489b38d4bec3 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Wed, 15 Sep 2021 12:23:43 -0400 Subject: [PATCH 11/14] add job services --- src/sasctl/services.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sasctl/services.py b/src/sasctl/services.py index 5ee3d6bc..3382d5f1 100644 --- a/src/sasctl/services.py +++ b/src/sasctl/services.py @@ -8,12 +8,14 @@ # a single location for importing services. All services should utilize # classmethods allowing them to be used without instantiation from ._services.cas_management import CASManagement as cas_management +from ._services.compute import Compute as compute from ._services.concepts import Concepts as concepts from ._services.data_sources import DataSources as data_sources from ._services.files import Files as files from ._services.folders import Folders as folders -from ._services.microanalytic_score import \ - MicroAnalyticScore as microanalytic_score +from ._services.job_definitions import JobDefinitions as job_definitions +from ._services.job_execution import JobExecution as job_execution +from ._services.microanalytic_score import MicroAnalyticScore as microanalytic_score from ._services.model_management import ModelManagement as model_management from ._services.model_publish import ModelPublish as model_publish from ._services.model_repository import ModelRepository as model_repository @@ -21,8 +23,6 @@ from ._services.relationships import Relationships as relationships from ._services.reports import Reports as reports from ._services.report_images import ReportImages as report_images -from ._services.sentiment_analysis import \ - SentimentAnalysis as sentiment_analysis -from ._services.text_categorization import \ - TextCategorization as text_categorization +from ._services.sentiment_analysis import SentimentAnalysis as sentiment_analysis +from ._services.text_categorization import TextCategorization as text_categorization from ._services.text_parsing import TextParsing as text_parsing From e12a66d2ac88b6cae24fca698787fcf144cc666f Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Wed, 15 Sep 2021 13:11:56 -0400 Subject: [PATCH 12/14] get job log, listing, and results --- src/sasctl/_services/compute.py | 93 +++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/sasctl/_services/compute.py b/src/sasctl/_services/compute.py index c032f764..ac31c4ba 100644 --- a/src/sasctl/_services/compute.py +++ b/src/sasctl/_services/compute.py @@ -149,3 +149,96 @@ def create_session(cls, context): context = cls.get_context(context) return cls.request_link(context, 'createSession') + + @classmethod + def get_listing(cls, job=None, session=None): + """Retrieve the SAS listing (program output) for a job or an entire session. + + Parameters + ---------- + job : dict, optional + Dictionary representation of an existing job. + session : dict, optional + Dictionary representation of an active session. + + Returns + ------- + list of RestObj + + Raises + ------ + ValueError + If neither `job` nor `session` are provided. + + """ + if job is None and session is None: + raise ValueError("Either a job or a session must be specified.") + + if job: + uri = '/sessions/%s/jobs/%s/listing' % (job['sessionId'], job['id']) + else: + uri = '/sessions/%s/listing' % session['id'] + + return cls.get(uri) + + @classmethod + def get_log(cls, job=None, session=None): + """Retrieve the SAS log for a job or an entire session. + + Parameters + ---------- + job : dict, optional + Dictionary representation of an existing job. + session : dict, optional + Dictionary representation of an active session. + + Returns + ------- + list of RestObj + + Raises + ------ + ValueError + If neither `job` nor `session` are provided. + + """ + if job is None and session is None: + raise ValueError("Either a job or a session must be specified.") + + if job: + uri = '/sessions/%s/jobs/%s/log' % (job['sessionId'], job['id']) + else: + uri = '/sessions/%s/log' % session['id'] + + return cls.get(uri) + + @classmethod + def get_results(cls, job=None, session=None): + """Retrieve the output files for a job or an entire session. + + Parameters + ---------- + job : dict, optional + Dictionary representation of an existing job. + session : dict, optional + Dictionary representation of an active session. + + Returns + ------- + list of RestObj + + Raises + ------ + ValueError + If neither `job` nor `session` are provided. + + """ + if job is None and session is None: + raise ValueError("Either a job or a session must be specified.") + + if job: + uri = '/sessions/%s/jobs/%s/results' % (job['sessionId'], job['id']) + else: + uri = '/sessions/%s/results' % session['id'] + + return cls.get(uri) \ No newline at end of file From 7525836e33435d0e40bf03e065b2f3380fe07e08 Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Wed, 15 Sep 2021 17:33:44 -0400 Subject: [PATCH 13/14] fix create_job() --- src/sasctl/_services/compute.py | 12 +- ...te.TestJobDefinitions.test_create_job.json | 376 ++++++++++++++++++ tests/integration/test_compute.py | 46 +++ 3 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_create_job.json create mode 100644 tests/integration/test_compute.py diff --git a/src/sasctl/_services/compute.py b/src/sasctl/_services/compute.py index ac31c4ba..9ed49f4a 100644 --- a/src/sasctl/_services/compute.py +++ b/src/sasctl/_services/compute.py @@ -94,8 +94,8 @@ def create_job( Parameters ---------- - session - code + session : str or dict + code : str name description environment @@ -111,10 +111,13 @@ def create_job( # TODO: if code is URI pass in codeUri field. code = code.split('\n') - variables = variables or [] + uri = '/sessions/%s/jobs' % session['id'] + + # NOTE: variables must be None not [] resources = resources or [] environment = environment or {} attributes = attributes or {} + data = { 'version': 3, 'name': name, @@ -126,7 +129,8 @@ def create_job( 'attributes': attributes, } - return cls.request_link(session, 'execute', json=data) + return cls.post(uri, json=data) + # return cls.request_link(session, 'execute', json=data) @classmethod def create_session(cls, context): diff --git a/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_create_job.json b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_create_job.json new file mode 100644 index 00000000..59ae5e8d --- /dev/null +++ b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_create_job.json @@ -0,0 +1,376 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-15T21:33:06", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE3Nzc1ODcsImlhdCI6MTYzMTc0MTU4NywiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiIwZDk3ZGJhMzlkODA0ZGE1ODAwNTFlZDgwNmZhZmQ2YiIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE3NDEwODY4MjQsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE3NDE1ODYsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.EZzKmxWtQltEED6ZPlUCYs1Wfk9VxB525cceP_4MwuytgUfoC4MxY3879oc_KQngXhcFeMfj2lmyJPSadYXFILBY0lRfjZJGHyh6dUvWfrQC2vNAkN_UNbcNr7KdEAwEgEai8NG9fbPWmTtSFuQMrhulBhDedMdPXRiZIA5D3DTwu9SCM0FG9hatBOpeEx-KX-QcTHxtiQM56_GM7ds2ut6byczqhOQmdyleriGYHASZ31VcM3Xc796tZHYNO5nl6WV7SQhKRGnlBD0dXP6K9px13IOSnGXidUZQtqrdWV8xfY9lRwPdfvEm1DbM8tw7v2CATuqFI_qCBv55dN6aRQ\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"0d97dba39d804da580051ed806fafd6b\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Wed, 15 Sep 2021 21:33:07 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + }, + { + "recorded_at": "2021-09-15T21:33:07", + "request": { + "body": { + "encoding": "utf-8", + "string": "" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "GET", + "uri": "https://hostname.com/compute/contexts" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"links\":[{\"method\":\"GET\",\"rel\":\"collection\",\"href\":\"/compute/contexts\",\"uri\":\"/compute/contexts\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts?start=0&limit=10\",\"uri\":\"/compute/contexts?start=0&limit=10\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"POST\",\"rel\":\"createContext\",\"href\":\"/compute/contexts\",\"uri\":\"/compute/contexts\",\"type\":\"application/vnd.sas.compute.context.request\",\"responseType\":\"application/vnd.sas.compute.context.summary\"}],\"name\":\"items\",\"accept\":\"application/vnd.sas.compute.context.summary\",\"start\":0,\"count\":7,\"items\":[{\"createdBy\":\"sas.import9\",\"id\":\"9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"uri\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"uri\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\",\"uri\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de/sessions\",\"uri\":\"/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"Import 9 service compute context\"},{\"createdBy\":\"sas.casFormats\",\"id\":\"2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"uri\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"uri\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\",\"uri\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a/sessions\",\"uri\":\"/compute/contexts/2cd8325f-f4ca-4c8d-8aed-90dd9ea78d0a/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"CAS Formats service compute context\"},{\"createdBy\":\"sas.SASJobExecution\",\"id\":\"a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"uri\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"uri\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\",\"uri\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636/sessions\",\"uri\":\"/compute/contexts/a72ed0cc-9fa7-476f-9c3e-7b07b96f5636/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"SAS Job Execution compute context\"},{\"createdBy\":\"sas.modelPublish\",\"id\":\"93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"uri\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"uri\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\",\"uri\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b/sessions\",\"uri\":\"/compute/contexts/93e83a20-d0ad-4ea7-afe1-6057eed2126b/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"SAS Model Manager compute context\"},{\"createdBy\":\"sas.SASStudioV\",\"id\":\"1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"uri\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"uri\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\",\"uri\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab/sessions\",\"uri\":\"/compute/contexts/1f42f4d3-de57-4d72-b0b4-0057250824ab/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"SAS Studio compute context\"},{\"createdBy\":\"sas.dataMining\",\"id\":\"073b25d4-3107-4b67-b727-1c0253027a80\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\",\"uri\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\",\"uri\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\",\"uri\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80/sessions\",\"uri\":\"/compute/contexts/073b25d4-3107-4b67-b727-1c0253027a80/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"Data Mining compute context\"},{\"createdBy\":\"sas.forecastingGateway\",\"id\":\"31c1321a-9b12-4369-93ed-b461d6331d03\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\",\"uri\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\",\"type\":\"application/vnd.sas.compute.context\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\",\"uri\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\",\"type\":\"application/vnd.sas.compute.context.summary\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\",\"uri\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03\"},{\"method\":\"POST\",\"rel\":\"createSession\",\"href\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03/sessions\",\"uri\":\"/compute/contexts/31c1321a-9b12-4369-93ed-b461d6331d03/sessions\",\"type\":\"application/vnd.sas.compute.session.request\",\"responseType\":\"application/vnd.sas.compute.session\"}],\"version\":2,\"name\":\"SAS Visual Forecasting compute context\"}],\"limit\":10,\"version\":2}" + }, + "headers": { + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Security-Policy": [ + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;" + ], + "Content-Type": [ + "application/vnd.sas.collection+json;charset=UTF-8" + ], + "Date": [ + "Wed, 15 Sep 2021 21:33:07 GMT" + ], + "Expires": [ + "0" + ], + "Keep-Alive": [ + "timeout=5, max=99" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/compute/contexts" + } + }, + { + "recorded_at": "2021-09-15T21:33:08", + "request": { + "body": { + "encoding": "utf-8", + "string": "" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "0" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de/sessions" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"owner\":\"USERNAME\",\"state\":\"pending\",\"attributes\":{\"homeDirectory\":\"/opt/sas/viya/config/var/run/compsrv/default/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8\",\"sessionInactiveTimeout\":900},\"serverId\":\"d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8\",\"creationTimeStamp\":\"2021-09-15T21:33:08.000Z\",\"applicationName\":\"sas.ec\",\"sessionConditionCode\":0,\"stateElapsedTime\":0,\"id\":\"d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"links\":[{\"method\":\"GET\",\"rel\":\"self\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"type\":\"application/vnd.sas.compute.session\"},{\"method\":\"GET\",\"rel\":\"alternate\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"type\":\"application/vnd.sas.compute.session.summary\"},{\"method\":\"GET\",\"rel\":\"state\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/state\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/state\",\"type\":\"text/plain\"},{\"method\":\"PUT\",\"rel\":\"cancel\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/state?value=canceled\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/state?value=canceled\"},{\"method\":\"DELETE\",\"rel\":\"delete\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\"},{\"method\":\"POST\",\"rel\":\"execute\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs\",\"type\":\"application/vnd.sas.compute.job.request\",\"responseType\":\"application/vnd.sas.compute.job\"},{\"method\":\"GET\",\"rel\":\"jobs\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.job\"},{\"method\":\"GET\",\"rel\":\"files\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/filerefs\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/filerefs\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.fileref.summary\"},{\"method\":\"POST\",\"rel\":\"assign\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/filerefs\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/filerefs\",\"type\":\"application/vnd.sas.compute.fileref.request\",\"responseType\":\"application/vnd.sas.compute.fileref\"},{\"method\":\"GET\",\"rel\":\"librefs\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/data\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/data\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.library.summary\"},{\"method\":\"GET\",\"rel\":\"log\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/log\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/log\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.log.line\"},{\"method\":\"GET\",\"rel\":\"listing\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/listing\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/listing\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.log.line\"},{\"method\":\"GET\",\"rel\":\"results\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/results\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/results\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.result\"},{\"method\":\"GET\",\"rel\":\"variables\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/variables\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/variables\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.compute.session.variable\"},{\"method\":\"GET\",\"rel\":\"engines\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/engines\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/engines\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.data.engine\"},{\"method\":\"GET\",\"rel\":\"getFiles\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/files\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/files\",\"type\":\"application/vnd.sas.compute.file.properties\"},{\"method\":\"GET\",\"rel\":\"getOption\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/options/{optionName}\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/options/{optionName}\",\"type\":\"text/plain\"},{\"method\":\"PUT\",\"rel\":\"updateOption\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/options/{optionName}\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/options/{optionName}\",\"type\":\"text/plain\",\"responseType\":\"text/plain\"},{\"method\":\"GET\",\"rel\":\"formats\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/formats\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/formats\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.format.summary\"},{\"method\":\"GET\",\"rel\":\"informats\",\"href\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/informats\",\"uri\":\"/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/informats\",\"type\":\"application/vnd.sas.collection\",\"itemType\":\"application/vnd.sas.format.summary\"}],\"version\":1}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Security-Policy": [ + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;" + ], + "Content-Type": [ + "application/vnd.sas.compute.session+json; version=1" + ], + "Date": [ + "Wed, 15 Sep 2021 21:33:08 GMT" + ], + "ETag": [ + "\"j6a571x48z\"" + ], + "Keep-Alive": [ + "timeout=5, max=98" + ], + "Location": [ + "/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000" + ], + "Origin": [ + "https://hostname.com:35273" + ], + "Pragma": [ + "no-cache" + ], + "Public": [ + "GET, PUT, HEAD, POST, DELETE, TRACE, OPTIONS, PATCH" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 201, + "message": "" + }, + "url": "https://hostname.com/compute/contexts/9fbc2c5f-884a-4f01-a941-6ab020d7e7de/sessions" + } + }, + { + "recorded_at": "2021-09-15T21:33:08", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"version\": 3, \"name\": \"sasctl test job\", \"description\": \"Test job execution from sasctl\", \"environment\": {}, \"variables\": null, \"code\": [\"\", \" ods html style=HTMLBlue;\", \" proc print data=sashelp.class(OBS=5); \", \" run; \", \" quit;\", \" ods html close;\", \" \"], \"resources\": [], \"attributes\": {}}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "336" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs" + }, + "response": { + "body": { + "encoding": null, + "string": "{\"creationTimeStamp\":\"2021-09-15T21:33:08Z\",\"id\":\"D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\",\"links\":[{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\",\"method\":\"GET\",\"rel\":\"self\",\"type\":\"application\\/vnd.sas.compute.job\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/state\",\"method\":\"GET\",\"rel\":\"state\",\"type\":\"text\\/plain\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/state\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/state?value=canceled\",\"method\":\"PUT\",\"rel\":\"cancel\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/state?value=canceled\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\",\"method\":\"DELETE\",\"rel\":\"delete\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/log\",\"itemType\":\"application\\/vnd.sas.compute.log.line\",\"method\":\"GET\",\"rel\":\"log\",\"type\":\"application\\/vnd.sas.collection\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/log\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/listing\",\"itemType\":\"application\\/vnd.sas.compute.log.line\",\"method\":\"GET\",\"rel\":\"listing\",\"type\":\"application\\/vnd.sas.collection\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/listing\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/results\",\"itemType\":\"application\\/vnd.sas.compute.result\",\"method\":\"GET\",\"rel\":\"results\",\"type\":\"application\\/vnd.sas.collection\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\\/jobs\\/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA\\/results\"},{\"href\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"method\":\"GET\",\"rel\":\"up\",\"type\":\"application\\/vnd.sas.compute.session\",\"uri\":\"\\/compute\\/sessions\\/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\"}],\"sessionId\":\"d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000\",\"state\":\"pending\",\"stateElapsedTime\":0,\"version\":1}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Length": [ + "2754" + ], + "Content-Security-Policy": [ + "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' *.sas.com blob: data:; style-src 'self' 'unsafe-inline'; child-src 'self' blob: data: mailto:;" + ], + "Content-Type": [ + "application/vnd.sas.compute.job+json; version=1" + ], + "Date": [ + "Wed, 15 Sep 2021 21:33:08 GMT" + ], + "ETag": [ + "\"j6a571zq5j\"" + ], + "Keep-Alive": [ + "timeout=5, max=97" + ], + "Location": [ + "/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs/D9D29C99-7099-1D45-8760-FAD0A5EBDFDA" + ], + "Origin": [ + "https://hostname.com:35273" + ], + "Pragma": [ + "no-cache" + ], + "Public": [ + "GET, PUT, HEAD, POST, DELETE, TRACE, OPTIONS, PATCH" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 201, + "message": "" + }, + "url": "https://hostname.com/compute/sessions/d5dbd7dc-d84e-4535-aa1c-94c459e6eeb8-ses0000/jobs" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/integration/test_compute.py b/tests/integration/test_compute.py new file mode 100644 index 00000000..f34a9a81 --- /dev/null +++ b/tests/integration/test_compute.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# Copyright © 2021, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +import pytest +from sasctl import RestObj +from sasctl.services import compute + +# Each test will receive a (recorded) Session instance +pytestmark = pytest.mark.usefixtures('session') + + +@pytest.mark.incremental +class TestJobDefinitions: + + def test_create_job(self): + all_contexts = compute.list_contexts() + assert isinstance(all_contexts, list) + assert len(all_contexts) > 0 + + sess = compute.create_session(all_contexts[0]) + assert isinstance(sess, RestObj) + + code = """ + ods html style=HTMLBlue; + proc print data=sashelp.class(OBS=5); + run; + quit; + ods html close; + """ + + params = dict( + name='sasctl test job', + description='Test job execution from sasctl' + ) + job = compute.create_job(sess, code, **params) + assert isinstance(job, RestObj) + assert job.sessionId == sess.id + assert job.state == 'pending' + + + + From 32b6a11b8928af2d90897eb8b6de714a072b3fee Mon Sep 17 00:00:00 2001 From: jlwalke2 Date: Thu, 16 Sep 2021 12:35:25 -0400 Subject: [PATCH 14/14] test invalid inputs --- ...e.TestJobDefinitions.test_get_listing.json | 91 +++++++++++++++++++ ...mpute.TestJobDefinitions.test_get_log.json | 91 +++++++++++++++++++ ...e.TestJobDefinitions.test_get_results.json | 91 +++++++++++++++++++ tests/integration/test_compute.py | 11 +++ 4 files changed, 284 insertions(+) create mode 100644 tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_listing.json create mode 100644 tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_log.json create mode 100644 tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_results.json diff --git a/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_listing.json b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_listing.json new file mode 100644 index 00000000..62eaea8b --- /dev/null +++ b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_listing.json @@ -0,0 +1,91 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-16T16:34:55", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE4NDYwOTUsImlhdCI6MTYzMTgxMDA5NSwiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiJkNTlkNjE5NmVmMmQ0YjZiODAwZWYyMWY3NzkzYzBkZiIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE4MDk5ODExNzAsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE4MTAwOTUsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.Xte8tOJu1fUrAkFIM72MIcaw76dBqY7U3M9u1FotpkuEvxrYP48BxI7CkSQwxC38f0xBCir0_q5sFPAxyCWfsBTM7Ockj9TyV4AAUxJPnFtm2_Q4fcKYWPwPwb3DcNNWxLW-tcoqA7XZ9HRLaxui1yEoJtRYrDpGRKhWmS-y4oGXD9dQsSGwQgy3Zno0XbqNPUkQRiLNP3sQVvUzI88tbK1-Q6MZ-RhGIRjYGjMjOxDG71ZQ58pDUETux5RxxoXPns0ttw7Lo9M3NQnQZUCVH9CbNBnR21yH9a-yI06nPfT3w4ADWHMV2rFGvwrQL-21phBmmwR7ZiJY5LgNL7_SVg\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"d59d6196ef2d4b6b800ef21f7793c0df\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Thu, 16 Sep 2021 16:34:55 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_log.json b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_log.json new file mode 100644 index 00000000..2380d0fb --- /dev/null +++ b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_log.json @@ -0,0 +1,91 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-16T16:34:55", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE4NDYwOTUsImlhdCI6MTYzMTgxMDA5NSwiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiIxZWNjMjA4Yzc1MzQ0ODkxYmZkNjlkYjQ5OWVjZTIzNiIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE4MTAwOTUyMzgsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE4MTAwOTUsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.bsYm7Ubgta0AgJ0lMFNYzfLCpWBKtMEcU1CN-xXNLACBJegZw6kQRKAptg6CQD2WgLr0SMUJHPaWNQwAflK4PlvUGXLuSGMMIi8druScvMtXLWcLyIa0H6Hxggjvl4AQWLru9Txdc-vwLRQh2k5VdTxCuWlZoQIOnEwJubAX7SE_t-8uxPR8ysc1y8SzN3s44-qUbmm-WU8LZYrVRV2DwiE1TU6s_orb6u3AAcLDtuECoOJfF15BqbmtUYzj4les7QvHpp3kza2zL2WJ6NSFaiHlf94bgDhM1CT2PIBZZAiR5-7mrlQuINdPc7j1zwSjo0kq-a3XGZld6F8At39Ofg\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"1ecc208c75344891bfd69db499ece236\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Thu, 16 Sep 2021 16:34:55 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_results.json b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_results.json new file mode 100644 index 00000000..9826b31d --- /dev/null +++ b/tests/cassettes/tests.integration.test_compute.TestJobDefinitions.test_get_results.json @@ -0,0 +1,91 @@ +{ + "http_interactions": [ + { + "recorded_at": "2021-09-16T16:34:56", + "request": { + "body": { + "encoding": "utf-8", + "string": "grant_type=password&username=USERNAME&password=*****" + }, + "headers": { + "Accept": [ + "application/json" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic [redacted]" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "62" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "python-requests/2.22.0" + ] + }, + "method": "POST", + "uri": "https://hostname.com/SASLogon/oauth/token" + }, + "response": { + "body": { + "encoding": "UTF-8", + "string": "{\"access_token\":\"[redacted]\",\"token_type\":\"bearer\",\"id_token\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0L1NBU0xvZ29uL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMiLCJhdWQiOlsic2FzLmVjIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvU0FTTG9nb24vb2F1dGgvdG9rZW4iLCJleHAiOjE2MzE4NDYwOTYsImlhdCI6MTYzMTgxMDA5NiwiYW1yIjpbImV4dCIsInB3ZCJdLCJhenAiOiJzYXMuZWMiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJKb25hdGhhbi5XYWxrZXJAc2FzLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6ImxkYXAiLCJqdGkiOiJkMjg5OGE0MTY1YzQ0OWQ5OWM4YWQ5NTljZThiZDFkZSIsInByZXZpb3VzX2xvZ29uX3RpbWUiOjE2MzE4MTAwOTU2MjQsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50X2lkIjoic2FzLmVjIiwiY2lkIjoic2FzLmVjIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoiam96d2FsIiwicmV2X3NpZyI6ImUxODA1M2MiLCJhdXRoX3RpbWUiOjE2MzE4MTAwOTYsInVzZXJfaWQiOiIyM2I3NDJhOS1jN2IyLTRkNGUtYjBlYy0xZDJhNjdjNjIwOTMifQ.VzYlyofJjQF5bbuFmx_gyHnkyLvC4qdbgLQN7_504HiFnd4gSWkA8rv4kJZ6sCz7zcr2RHAP3UvOFmq8em5EiJ_1vHXYBC94VXizOImakQxT4umIuzJBiyeNtRVQQe4lv_ctnMDDspdEw-RpTe5wDb7dfho6RgMYyJ9kmbvJBE6RWBtWo7WnPaM1zoiOasgr9SH81RTB99D8JrcSPwEkn04tgt2Ze3upNhl5OFd0sb00CDxCmixgBdcTv9gzdr0k-W9HVe4vpsZulZ8gPdXAePxacNc1JAXWRP18OL51laCtVb7sLKu0aJJxzdVrbcER-fXPCY1C64dJ3NVh3DG5Mw\",\"expires_in\":35999,\"scope\":\"esvakmcorionterritoryaccess sviusrs SCR viyapython mismakeordertstval CARYEMPS SAS_Visa_TE esvanemeainstructorconsumers harrisindirectreports rdinformoptin esvakmcrenewalforecastconsumers esvakmcorionoppaccess orcoevasrv02 COESRV tksem AWSSandboxAccount01 cloudacadusers modelmanager uaa.resource otggenrnd RNDSIMSUsers openid domainemps1 gelupdates OR_RD_COE scim.read sasvspusers esvakmcriskconsumers SASAdministrators OneDriveUsers openstackusers CASDiscussion scr_us acoe_server_group scr_ci esvarndperftestconsumers esvakmcorionconsultforecastconsumers intcawebcertreq ANALYSIS cooallamer esvakmcorionriskconsumers CTRYUNITEDSTATES tisurvey gtt_users intcausers BLDGR HADOOP-DEV clients.secret esvaallempsconsumers midena SIMSEdit rdtcaccess Safari_list RDAnnounce DSCIENCE unix_r&d TACCDevelopment SAS_TE AWSsalesenableAccount csatechxchange USemps rdanncauto SIMSView Weekly_Wrap defsusers opsglobalorion DomainEmployees AWS-TAM-Call esvanemeaeduconsumers bryanharrisALLemps esvakmcorionriskaccess exnet.simsview KSA_mail_list SSODLPUsers revegyusercommunity uaa.admin clients.admin AMTX esvakmcorionconsumers AMPSX USSAS esvanemeaeduadmins Skypeplugin TSDATASTORUSERS lanpanfilter exnet.simsedit SalesXchange ORE EMINERS sas92_install techoffice COOALLSTAFF sviadms scr_edu rdengineering allamazonusers R&DUS UDSREPORTS clients.read WWMGlobalBanking atlassiangroups aideas hrsb jaredpetersonallreports BLDALL TESSAUSERS KSA_ADGroup birdrace scr_psd R&D-AAD ITatSAS orclus05 scr_og R5TH scr_ga democenterusers HADOOP-L RE_PYTHON clients.write ts-tba-test scim.write ORCLUS08_USERS\",\"jti\":\"d2898a4165c449d99c8ad959ce8bd1de\"}" + }, + "headers": { + "Cache-Control": [ + "no-store" + ], + "Connection": [ + "Keep-Alive" + ], + "Content-Type": [ + "application/json;charset=UTF-8" + ], + "Date": [ + "Thu, 16 Sep 2021 16:34:55 GMT" + ], + "Keep-Alive": [ + "timeout=5, max=100" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4" + ], + "Strict-Transport-Security": [ + "max-age=31536000 ; includeSubDomains" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "User-Agent" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-Frame-Options": [ + "DENY" + ], + "X-XSS-Protection": [ + "1; mode=block" + ] + }, + "status": { + "code": 200, + "message": "" + }, + "url": "https://hostname.com/SASLogon/oauth/token" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/integration/test_compute.py b/tests/integration/test_compute.py index f34a9a81..fe15996d 100644 --- a/tests/integration/test_compute.py +++ b/tests/integration/test_compute.py @@ -41,6 +41,17 @@ def test_create_job(self): assert job.sessionId == sess.id assert job.state == 'pending' + def test_get_listing(self): + with pytest.raises(ValueError): + compute.get_listing() + + def test_get_log(self): + with pytest.raises(ValueError): + compute.get_log() + + def test_get_results(self): + with pytest.raises(ValueError): + compute.get_results()