Skip to content

Commit

Permalink
feat: mount s3 buckets in sessions (#729)
Browse files Browse the repository at this point in the history
Co-authored-by: Andreas Bleuler <andreas.bleuler@sdsc.ethz.ch>
  • Loading branch information
olevski and Andreas Bleuler committed Jan 20, 2022
1 parent 56c2db4 commit 808e767
Show file tree
Hide file tree
Showing 17 changed files with 453 additions and 89 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ sentry-sdk = {extras = ["flask"],version = "*"}
gevent = "*"
flask-apispec = "*"
flask-swagger-ui = "*"
boto3 = "*"
pyjwt = "<2.0.0"

[dev-packages]
Expand Down
52 changes: 42 additions & 10 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion git-clone/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ ENV MOUNT_PATH /code

COPY clone.sh /clone.sh
WORKDIR /
ENTRYPOINT ["/clone.sh"]
ENTRYPOINT ["bash", "/clone.sh"]
1 change: 0 additions & 1 deletion git-clone/clone.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,3 @@ git config http.proxy http://localhost:8080
git config http.sslVerify false
git config --unset credential.helper
rm /tmp/git-credentials

7 changes: 5 additions & 2 deletions helm-chart/renku-notebooks/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ dependencies:
- name: amalthea
repository: https://swissdatasciencecenter.github.io/helm-charts/
version: 0.2.2
digest: sha256:9a86d84650b84ccb31475ae40b4d6e4d1a5f4fcd815304ef225bead8f8448031
generated: "2021-11-30T15:54:29.047228+01:00"
- name: dlf-chart
repository: https://swissdatasciencecenter.github.io/datashim/
version: 0.1.1-renku-1
digest: sha256:2623679a03eb93eedafd5149fcea45e1fcaf7b14b3ad18d0e12a24f47280fa90
generated: "2022-01-19T17:41:34.437304+01:00"
6 changes: 5 additions & 1 deletion helm-chart/renku-notebooks/requirements.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
dependencies:
- name: amalthea
repository: "https://swissdatasciencecenter.github.io/helm-charts/"
version: 0.2.2
version: "0.2.2"
- name: dlf-chart
repository: "https://swissdatasciencecenter.github.io/datashim/"
version: "0.1.1-renku-1"
condition: s3mounts.enabled
2 changes: 2 additions & 0 deletions helm-chart/renku-notebooks/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ spec:
fieldPath: metadata.namespace
- name: ENFORCE_CPU_LIMITS
value: {{ .Values.enforceCPULimits | quote }}
- name: S3_MOUNTS_ENABLED
value: {{ .Values.s3mounts.enabled | quote }}
ports:
- name: http
containerPort: 8000
Expand Down
2 changes: 2 additions & 0 deletions helm-chart/renku-notebooks/templates/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ spec:
{{- end }}
- name: ENFORCE_CPU_LIMITS
value: {{ $.Values.enforceCPULimits | quote }}
- name: S3_MOUNTS_ENABLED
value: {{ $.Values.s3mounts.enabled | quote }}
command:
- pipenv
- run
Expand Down
21 changes: 21 additions & 0 deletions helm-chart/renku-notebooks/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ amalthea:
scope:
clusterWide: false
deployCrd: true
## If enabling S3 mounts uncomment the section below
# extraChildResources:
# - name: datasets
# group: com.ie.ibm.hpsys

## Used only if S3 Mounts are enabled
dlf-chart:
csi-sidecars-rbac:
enabled: true
csi-nfs-chart:
enabled: false
csi-s3-chart:
mounter: "goofys"
enabled: true
csi-h3-chart:
enabled: false
dataset-operator-chart:
enabled: true

s3mounts:
enabled: false

# configuration for user session persistent volumes
userSessionPersistentVolumes:
Expand Down
181 changes: 181 additions & 0 deletions renku_notebooks/api/classes/s3mount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from flask import current_app
import boto3
from botocore import UNSIGNED
from botocore.client import Config
from botocore.exceptions import EndpointConnectionError, ClientError, NoCredentialsError


class S3mount:
def __init__(
self,
bucket,
mount_folder,
endpoint,
access_key=None,
secret_key=None,
read_only=False,
):
self.access_key = access_key if access_key != "" else None
self.secret_key = secret_key if secret_key != "" else None
self.endpoint = endpoint
self.bucket = bucket
self.read_only = read_only
self.public = False
if self.access_key is None and self.secret_key is None:
self.public = True
if self.public:
self.client = boto3.session.Session().client(
service_name="s3",
endpoint_url=self.endpoint,
config=Config(signature_version=UNSIGNED),
)
else:
self.client = boto3.session.Session().client(
service_name="s3",
aws_access_key_id=self.access_key,
aws_secret_access_key=self.secret_key,
endpoint_url=self.endpoint,
)
self.mount_folder = mount_folder

def get_manifest_patches(
self, k8s_res_name, k8s_namespace, labels={}, annotations={}
):
secret_name = f"{k8s_res_name}-secret"
# prepare datashim dataset spec
s3mount_spec = {
"local": {
"type": "COS",
"endpoint": self.endpoint,
"bucket": self.bucket,
"readonly": "true" if self.read_only else "false",
}
}
if not self.public:
s3mount_spec["local"]["secret-name"] = f"{secret_name}"
s3mount_spec["local"]["secret-namespace"] = k8s_namespace
else:
s3mount_spec["local"]["accessKeyID"] = ""
s3mount_spec["local"]["secretAccessKey"] = "secret"
patch = {
"type": "application/json-patch+json",
"patch": [
# add whole datashim dataset spec
{
"op": "add",
"path": f"/{k8s_res_name}",
"value": {
"apiVersion": "com.ie.ibm.hpsys/v1alpha1",
"kind": "Dataset",
"metadata": {
"name": k8s_res_name,
"namespace": k8s_namespace,
"labels": labels,
"annotations": {
**annotations,
current_app.config["RENKU_ANNOTATION_PREFIX"]
+ "mount_folder": self.mount_folder,
},
},
"spec": s3mount_spec,
},
},
# mount dataset into user session
{
"op": "add",
"path": "/statefulset/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"mountPath": self.mount_folder.rstrip("/") + "/" + self.bucket,
"name": k8s_res_name,
},
},
{
"op": "add",
"path": "/statefulset/spec/template/spec/volumes/-",
"value": {
"name": k8s_res_name,
"persistentVolumeClaim": {"claimName": k8s_res_name},
},
},
],
}
# add secret for storing access keys for s3
if not self.public:
patch["patch"].append(
{
"op": "add",
"path": f"/{secret_name}",
"value": {
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": secret_name,
"namespace": k8s_namespace,
"labels": labels,
"annotations": annotations,
},
"stringData": {
"accessKeyID": self.access_key,
"secretAccessKey": self.secret_key,
},
},
},
)
return patch

@property
def bucket_exists(self):
try:
self.client.head_bucket(Bucket=self.bucket)
except (ClientError, EndpointConnectionError, NoCredentialsError, ValueError):
current_app.logger.warning(
f"Failed to confirm bucket {self.bucket} for endpoint {self.endpoint} exists"
)
return False
else:
return True

@classmethod
def s3mounts_from_js(cls, js):
s3mounts = []
for patch_collection in js["spec"]["patches"]:
for patch in patch_collection["patch"]:
if patch["op"] == "test":
continue
if (
type(patch["value"]) is dict
and patch["value"].get("kind") == "Dataset"
):
s3mount_args = {}
s3mount_args.update(
{
"endpoint": patch["value"]["spec"]["local"]["endpoint"],
"bucket": patch["value"]["spec"]["local"]["bucket"],
"read_only": patch["value"]["spec"]["local"]["readonly"]
== "true",
"mount_folder": patch["value"]["metadata"]["annotations"][
current_app.config["RENKU_ANNOTATION_PREFIX"]
+ "mount_folder"
],
}
)
if "secret-name" in patch["value"]["spec"]["local"].keys():
secret_name = patch["value"]["spec"]["local"]["secret-name"]
for patch in patch_collection["patch"]:
if (
type(patch["value"]) is dict
and patch["value"].get("kind") == "Secret"
and patch["value"]["metadata"]["name"] == secret_name
):
s3mount_args.update(
{
"access_key": patch["value"]["stringData"][
"accessKeyID"
],
"secret_key": patch["value"]["stringData"][
"secretAccessKey"
],
}
)
s3mounts.append(cls(**s3mount_args))
return s3mounts
Loading

0 comments on commit 808e767

Please sign in to comment.