157 lines
4.8 KiB
Python
157 lines
4.8 KiB
Python
"""
|
|
The following are helper functions to handle the volumes of a Notebook.
|
|
The new API Volume will work with objects of the following format:
|
|
|
|
volume:
|
|
mount: "mount path"
|
|
newPvc?:
|
|
metadata: ...
|
|
spec: ...
|
|
existingSource?:
|
|
nfs?: ...
|
|
persistentVolumeClaim?: ...
|
|
...
|
|
|
|
These functions will parse such objects and map them to K8s constructs.
|
|
"""
|
|
from kubernetes import client
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
from kubeflow.kubeflow.crud_backend import api, logging
|
|
|
|
from . import utils
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
PVC_SOURCE = "persistentVolumeClaim"
|
|
EXISTING_SOURCE = "existingSource"
|
|
NEW_PVC = "newPvc"
|
|
MOUNT = "mount"
|
|
NAME = "name"
|
|
|
|
|
|
def check_volume_format(api_volume):
|
|
"""
|
|
Ensure that the JSON object received has the expected structure.
|
|
|
|
api_volume: The JSON API Volume object
|
|
"""
|
|
if MOUNT not in api_volume:
|
|
raise BadRequest("Volume should have a mount: %s" % api_volume)
|
|
|
|
if EXISTING_SOURCE not in api_volume and NEW_PVC not in api_volume:
|
|
raise BadRequest("Volume has neither %s nor %s: %s"
|
|
% (EXISTING_SOURCE, NEW_PVC, api_volume))
|
|
|
|
if EXISTING_SOURCE in api_volume and NEW_PVC in api_volume:
|
|
raise BadRequest("Volume has both %s and %s: %s"
|
|
% (EXISTING_SOURCE, NEW_PVC, api_volume))
|
|
|
|
|
|
def get_volume_name(api_volume):
|
|
"""
|
|
Return the name of the K8s V1Volume given an API volume with an existing
|
|
source.
|
|
|
|
api_volume: The API Volume submitted from client/UI
|
|
"""
|
|
# if the volume source is an existing PVC then use the requested PVC's name
|
|
# as the V1Volume.name
|
|
if EXISTING_SOURCE not in api_volume:
|
|
raise BadRequest("Failed to retrieve a volume name from '%s'"
|
|
% api_volume)
|
|
if PVC_SOURCE in api_volume[EXISTING_SOURCE]:
|
|
if "claimName" not in api_volume[EXISTING_SOURCE][PVC_SOURCE]:
|
|
raise BadRequest("Failed to retrieve the PVC name from '%s'"
|
|
% api_volume)
|
|
return api_volume[EXISTING_SOURCE][PVC_SOURCE]["claimName"]
|
|
|
|
# A user requested a different source for the V1Volume. In this case we
|
|
# use a randomly generated name
|
|
return "existing-source-volume-%s" % utils.random_string(8)
|
|
|
|
|
|
def get_pod_volume(api_volume, pvc):
|
|
"""
|
|
Return a V1Volume dict object based on the API Volume the client/UI sent.
|
|
|
|
api_volume: The API Volume submitted from client/UI
|
|
pvc: The created PVC, whose spec was defined in the api_volume
|
|
"""
|
|
check_volume_format(api_volume)
|
|
|
|
if pvc is not None:
|
|
# Mount a new PVC. We use the created PVC since the api_volume.newPvc
|
|
# spec might have defined a metadata.generateName. In this case, the
|
|
# name is set after the creation of the PVC
|
|
return {"name": pvc.metadata.name,
|
|
"persistentVolumeClaim": {"claimName": pvc.metadata.name}}
|
|
|
|
# User has explicitly asked to use an existing volume source
|
|
v1_volume = {"name": get_volume_name(api_volume)}
|
|
v1_volume.update(api_volume[EXISTING_SOURCE])
|
|
return v1_volume
|
|
|
|
|
|
def get_container_mount(api_volume, volume_name):
|
|
"""
|
|
Return a V1VolumeMount dict object from the request's JSON API Volume
|
|
|
|
api_volume: The API Volume submitted from client/UI
|
|
volume_name: The name of the V1Volume which the mount should refer to
|
|
"""
|
|
check_volume_format(api_volume)
|
|
|
|
return {"name": volume_name, "mountPath": api_volume["mount"]}
|
|
|
|
|
|
def get_new_pvc(api_volume) -> client.V1PersistentVolumeClaim:
|
|
"""
|
|
Return a V1PersistentVolumeClaim dict object from the request's JSON
|
|
API Volume.
|
|
|
|
api_volume: The JSON V1Volume object, in cammelCase as defined in the docs
|
|
"""
|
|
check_volume_format(api_volume)
|
|
if NEW_PVC not in api_volume:
|
|
return None
|
|
|
|
pvc = api.deserialize(api_volume[NEW_PVC], "V1PersistentVolumeClaim")
|
|
|
|
# don't allow users to explicitly set the Namespace
|
|
if pvc.metadata.namespace is not None:
|
|
raise BadRequest("PVC should not specify the namespace.")
|
|
|
|
return pvc
|
|
|
|
|
|
def add_notebook_volume(notebook, volume):
|
|
"""
|
|
Add the provided podvolume (dict V1Volume) to the Notebook's PodSpec.
|
|
|
|
notebook: Notebook CR dict
|
|
volume: Podvolume dict
|
|
"""
|
|
podspec = notebook["spec"]["template"]["spec"]
|
|
if "volumes" not in podspec:
|
|
podspec["volumes"] = []
|
|
|
|
podspec["volumes"].append(volume)
|
|
return notebook
|
|
|
|
|
|
def add_notebook_container_mount(notebook, container_mount):
|
|
"""
|
|
Add the provided container mount (dict V1VolumeMount) to the Notebook's
|
|
PodSpec.
|
|
|
|
notebook: Notebook CR dict
|
|
volume: Podvolume dict
|
|
"""
|
|
container = notebook["spec"]["template"]["spec"]["containers"][0]
|
|
if "volumeMounts" not in container:
|
|
container["volumeMounts"] = []
|
|
|
|
container["volumeMounts"].append(container_mount)
|
|
return notebook
|