Skip to content

Commit

Permalink
Merge pull request #9 from grycap/dev-calarcon
Browse files Browse the repository at this point in the history
- Update README to last version
- Added create and update services with JSON definition
  • Loading branch information
catttam authored Sep 1, 2023
2 parents f96eb51 + 98b6c32 commit debdfc7
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 34 deletions.
60 changes: 56 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,67 @@ This package provides a client to interact with OSCAR (https://oscar.grycap.net)
### Contents
- [Python OSCAR client](#python-oscar-client)
- [Contents](#contents)
- [Client](#client)
- [Initialize a client with basic authentication](#initialize-a-client-with-basic-authentication)
- [Initialize a client OIDC authentication](#initialize-a-client-oidc-authentication)
- [Sample usage](#sample-usage)
- [Client methods](#client-methods)
- [Cluster methods](#cluster-methods)
- [Service methods](#service-methods)
- [Logs methods](#logs-methods)
- [Storage usage](#storage-usage)

### Client

#### Initialize a client with basic authentication
``` python
options_basic_auth = {'cluster_id':'cluster-id',
'endpoint':'https://cluster-endpoint',
'user':'username',
'password':'password',
'ssl':'True'}

client = Client(options = options_basic_auth)
```
#### Initialize a client OIDC authentication

If you want to use OIDC tokens to authenticate with EGI Check-In, you can use the [OIDC Agent](https://indigo-dc.gitbook.io/oidc-agent/) to create an account configuration for the EGI issuer (https://aai.egi.eu/auth/realms/egi/) and then initialize the client specifying the `shortname` of your account like follows.

``` python
options_oidc_auth = {'cluster_id':'cluster-id',
'endpoint':'https://cluster-endpoint',
'shortname':'oidc-agent-shortname',
'ssl':'True'}

client = Client(options = options_oidc_auth)
```

If you already have a valid token, you can use the parameter `oidc_token` instead.

``` python
options_oidc_auth = {'cluster_id':'cluster-id',
'endpoint':'https://cluster-endpoint',
'oidc_token':'token',
'ssl':'True'}

client = Client(options = options_oidc_auth)
```
An example of using a generated token is if you want to use EGI Notebooks. Since you can't use oidc-agent on the Notebook, you can make use of the generated token that EGI provides on path `/var/run/secrets/egi.eu/access_token`.

### Sample usage

- Sample code that creates a client and gets information about the cluster

``` python
from oscar_python.client import Client

client = Client("cluster-id","https://cluster-endpoint", "username", "password", True)
options_basic_auth = {'cluster_id':'cluster-id',
'endpoint':'https://cluster-endpoint',
'user':'username',
'password':'password',
'ssl':'True'}

client = Client(options = options)

# get the cluster information
try:
Expand All @@ -37,7 +83,13 @@ except Exception as err:
``` python
from oscar_python.client import Client

client = Client("cluster-id","https://cluster-endpoint", "username", "password", True)
options_basic_auth = {'cluster_id':'cluster-id',
'endpoint':'https://cluster-endpoint',
'user':'username',
'password':'password',
'ssl':'True'}

client = Client(options = options)

try:
client.create_service("/absolute_path/cowsay.yaml")
Expand Down Expand Up @@ -83,13 +135,13 @@ services = client.list_services() # returns an http response or an HTTPError
**create_service**
``` python
# create a service
err = client.create_service("path_to_fdl") # returns nothing if the service is created or an error if something goes wrong
err = client.create_service("path_to_fdl" | "JSON_definition") # returns nothing if the service is created or an error if something goes wrong
```

**update_service**
``` python
# update a service
err = client.update_service("service_name","path_to_fdl") # returns nothing if the service is created or an error if something goes wrong
err = client.update_service("service_name","path_to_fdl" | "JSON_definition") # returns nothing if the service is created or an error if something goes wrong
```

**remove_service**
Expand Down
84 changes: 54 additions & 30 deletions oscar_python/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.

import json
import os
import yaml
import liboidcagent as agent
import oscar_python._utils as utils
Expand Down Expand Up @@ -99,44 +100,67 @@ def list_services(self):
def get_service(self, name):
return utils.make_request(self, _SVC_PATH+"/"+name, _GET)

def _apply_service(self, fdl_path, method):
def _check_fdl_definition(self, fdl_path):
with open(fdl_path, "r") as read_fdl:
fdl = self._parse_FDL_yaml(read_fdl)
# Read FDL file and check correct format
if fdl != ValueError:
for element in fdl["functions"]["oscar"]:
try:
svc = element[self.id]
except KeyError as err:
raise("FDL clusterID does not match current clusterID: {0}".format(err))
# Check if service already exists when the function is called from create_service
if method == _POST:
svc_exists = utils.make_request(self, _SVC_PATH+"/"+svc["name"], _GET, handle=False)
if svc_exists.status_code == 200:
raise ValueError("A service with name '{0}' is already present on the cluster".format(svc["name"]))
try:
with open(svc["script"]) as s:
svc["script"] = s.read()
except IOError as err:
raise("Couldn't read script")

# cpu parameter has to be string on the request
if type(svc["cpu"]) is int or type(svc["cpu"]) is float: svc["cpu"]= str(svc["cpu"])
utils.make_request(self, _SVC_PATH, method, data=json.dumps(svc))
try:
for element in fdl["functions"]["oscar"]:
try:
svc = element[self.id]
except KeyError as err:
raise("FDL clusterID does not match current clusterID: {0}".format(err))
try:
with open(svc["script"]) as s:
svc["script"] = s.read()
except IOError as err:
raise("Couldn't read script")

# cpu parameter has to be string on the request
if type(svc["cpu"]) is int or type(svc["cpu"]) is float: svc["cpu"]= str(svc["cpu"])

except ValueError as err:
print(err)
raise
else:
raise ValueError("Bad yaml format: {0}".format(fdl))

""" Create a service on the current cluster from a FDL file """
def create_service(self, fdl_path):
return self._apply_service(fdl_path, _POST)

""" Update a specific service """
def update_service(self, name, fdl_path):
return svc

""" Make the request to create a new service """
def _apply_service(self, svc, method):
# Check if service already exists when the function is called from create_service
if method == _POST:
svc_exists = utils.make_request(self, _SVC_PATH+"/"+svc["name"], _GET, handle=False)
if svc_exists.status_code == 200:
raise ValueError("A service with name '{0}' is already present on the cluster".format(svc["name"]))
utils.make_request(self, _SVC_PATH, method, data=json.dumps(svc))

""" Create a service on the current cluster from a FDL file or a JSON definition """
def create_service(self, service_definition):
if type(service_definition) is dict:
return self._apply_service(service_definition, _POST)
if os.path.isfile(service_definition):
try:
service = self._check_fdl_definition(service_definition)
except Exception:
raise
return self._apply_service(service, _POST)

""" Update a specific service from a FDL file or a JSON definition """
def update_service(self, name, new_service):
# Check if service exists before update
svc = utils.make_request(self, _SVC_PATH+"/"+svc["name"], _GET, handle=False)
svc = utils.make_request(self, _SVC_PATH+"/"+name, _GET, handle=False)
if svc.status_code != 200:
raise ValueError("The service {0} is not present on the cluster".format(name))
return self._apply_service(fdl_path, _PUT)
if type(new_service) is dict:
return self._apply_service(new_service, _PUT)
if os.path.isfile(new_service):
try:
service = self._check_fdl_definition(new_service)
except Exception:
raise
return self._apply_service(service, _PUT)

""" Remove a specific service """
def remove_service(self, name):
Expand All @@ -153,7 +177,7 @@ def run_service(self, name, **kwargs):
send_data = utils.encode_input(exec_input)

if "timeout" in kwargs.keys() and kwargs["timeout"]:
response = utils._make_request(self, _RUN_PATH+"/"+name, _POST, data=send_data, token=token, timeout=kwargs["timeout"])
response = utils.make_request(self, _RUN_PATH+"/"+name, _POST, data=send_data, token=token, timeout=kwargs["timeout"])
else:
response = utils.make_request(self, _RUN_PATH+"/"+name, _POST, data=send_data, token=token)

Expand Down

0 comments on commit debdfc7

Please sign in to comment.