Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't create PVCs if no default StorageClass is set #2679

Merged
merged 13 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/jupyter-web-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ RUN pip3 install -r /app/requirements.txt

COPY default /app/default
COPY rok /app/rok
COPY baseui /app/baseui

ENV PYTHONPATH /app

WORKDIR /app/default

Expand Down
42 changes: 7 additions & 35 deletions components/jupyter-web-app/Makefile
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
IMG = gcr.io/kubeflow-images-public/jupyter-web-app
# Load the shared modules dir to PYTHONPATH
MODULE_DIR := $(pwd)

# List any changed files. We only include files in the notebooks directory.
# because that is the code in the docker image.
# In particular we exclude changes to the ksonnet configs.
CHANGED_FILES := $(shell git diff-files --relative=components/jupyter-web-app)
all: run-default

ifeq ($(strip $(CHANGED_FILES)),)
# Changed files is empty; not dirty
# Don't include --dirty because it could be dirty if files outside the ones we care
# about changed.
GIT_VERSION := $(shell git describe --always)
else
GIT_VERSION := $(shell git describe --always)-dirty-$(shell git diff | shasum -a256 | cut -c -6)
endif
run-default:
cd default && PYTHONPATH=${PYTHONPATH}:${MODULE_DIR} python run.py

TAG := $(shell date +v%Y%m%d)-$(GIT_VERSION)
all: build

# To build without the cache set the environment variable
# export DOCKER_BUILD_OPTS=--no-cache
build:
docker build ${DOCKER_BUILD_OPTS} -t $(IMG):$(TAG) . \
--build-arg kubeflowversion=$(shell git describe --abbrev=0 --tags) \
--label=git-verions=$(GIT_VERSION)
docker tag $(IMG):$(TAG) $(IMG):latest
@echo Built $(IMG):latest
@echo Built $(IMG):$(TAG)

# Build but don't attach the latest tag. This allows manual testing/inspection of the image
# first.
push: build
gcloud docker -- push $(IMG):$(TAG)
@echo Pushed $(IMG) with :$(TAG) tags

push-latest: push
gcloud container images add-tag --quiet $(IMG):$(TAG) $(IMG):latest --verbosity=info
echo created $(IMG):latest
run-rok:
cd rok && PYTHONPATH=${PYTHONPATH}:${MODULE_DIR} python run.py
2 changes: 2 additions & 0 deletions components/jupyter-web-app/OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ approvers:
- ioandr
- kimwnasptd
reviewers:
- avdaredevil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please alphabetically sort

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! lgtm-ing

- prodonjs
- vkoukis
1 change: 1 addition & 0 deletions components/jupyter-web-app/baseui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/__pycache__
4 changes: 4 additions & 0 deletions components/jupyter-web-app/baseui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### BaseUI: Shared functions and Utilities
This module holds functions/configs that both the Default and Rok Jupyter UIs use. Each UI will need to handle its own routes.

This module is loaded into the `default` and `rok` UIs with the `PYTHONPATH` environment variable. To test the UIs locally, use the corresponding `make` commands.
Empty file.
144 changes: 144 additions & 0 deletions components/jupyter-web-app/baseui/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import json
from kubernetes import client, config
from kubernetes.config import ConfigException
from baseui.utils import create_logger

logger = create_logger(__name__)

try:
# Load configuration inside the Pod
config.load_incluster_config()
except ConfigException:
# Load configuration for testing
config.load_kube_config()

# Create the Apis
v1_core = client.CoreV1Api()
custom_api = client.CustomObjectsApi()
storage_api = client.StorageV1Api()


def parse_error(e):
try:
err = json.loads(e.body)["message"]
except json.JSONDecodeError:
err = str(e)
except KeyError:
err = str(e)

return err


def create_workspace_pvc(body):
# body: Dict (request body)
"""If the type is New, then create a new PVC, else use an existing one"""
if body["ws_type"] == "New":
pvc = client.V1PersistentVolumeClaim(
metadata=client.V1ObjectMeta(
name=body["ws_name"],
namespace=body["ns"]
),
spec=client.V1PersistentVolumeClaimSpec(
access_modes=[body["ws_access_modes"]],
resources=client.V1ResourceRequirements(
requests={
"storage": body["ws_size"] + "Gi"
}
)
)
)

create_pvc(pvc)

return


def create_datavol_pvc(body, i):
# body: Dict (request body)
pvc_nm = body["vol_name" + i]

# Create a PVC if its a new Data Volume
if body["vol_type" + i] == "New":
size = body["vol_size" + i] + "Gi"
mode = body["vol_access_modes" + i]

pvc = client.V1PersistentVolumeClaim(
metadata=client.V1ObjectMeta(
name=pvc_nm,
namespace=body["ns"]
),
spec=client.V1PersistentVolumeClaimSpec(
access_modes=[mode],
resources=client.V1ResourceRequirements(
requests={
"storage": size
}
)
)
)

create_pvc(pvc)

return


def get_secret(nm, ns):
# nm: string
# ns: string
return v1_core.read_namespaced_secret(nm, ns)


def get_default_storageclass():
strg_classes = storage_api.list_storage_class().items
for strgclss in strg_classes:
annotations = strgclss.metadata.annotations
# List of possible annotations
keys = []
keys.append("storageclass.kubernetes.io/is-default-class")
keys.append("storageclass.beta.kubernetes.io/is-default-class") # GKE

for key in keys:
is_default = annotations.get(key, False)
if is_default:
return strgclss.metadata.name

# No StorageClass is default
return ""


def get_namespaces():
nmsps = v1_core.list_namespace()
return [ns.metadata.name for ns in nmsps.items]


def get_notebooks(ns):
# ns: string
custom_api = client.CustomObjectsApi()

notebooks = \
custom_api.list_namespaced_custom_object("kubeflow.org", "v1alpha1",
ns, "notebooks")
return [nb["metadata"]["name"] for nb in notebooks["items"]]


def delete_notebook(nb, ns):
# nb: Dict
options = client.V1DeleteOptions()

return \
custom_api.delete_namespaced_custom_object("kubeflow.org", "v1alpha1",
ns, "notebooks", nb, options)


def create_notebook(nb):
# nb: Dict
ns = nb["metadata"]["namespace"]
return \
custom_api.create_namespaced_custom_object("kubeflow.org", "v1alpha1",
ns, "notebooks", nb)


def create_pvc(pvc):
# pvc: V1PersistentVolumeClaim
ns = pvc.metadata.namespace
return v1_core.create_namespaced_persistent_volume_claim(ns, pvc)
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
# -*- coding: utf-8 -*-
import yaml
import logging
import sys

CONFIG = "/etc/config/spawner_ui_config.yaml"


def create_logger(name):
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(
"%(asctime)s | %(name)s | %(levelname)s | %(message)s"))
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
logger.addHandler(handler)
return logger


# Functions for handling the JWT token
def load_file(filepath):
with open(filepath, 'r') as f:
file_data = f.read().replace('\n', '')
with open(filepath, "r") as f:
file_data = f.read().replace("\n", "")

return file_data

Expand All @@ -17,10 +28,10 @@ def load_file(filepath):
def spawner_ui_config(username):
c = None
try:
with open(CONFIG, 'r') as f:
with open(CONFIG, "r") as f:
c = f.read().format(username=username)
except IOError:
print('Error opening Spawner UI config file')
print("Error opening Spawner UI config file")

try:
if yaml.safe_load(c) is None:
Expand All @@ -33,7 +44,8 @@ def spawner_ui_config(username):
return None


# Helper functions for the /post-notebook route.
# Since notebook is a CRD, we don't currently have k8s client functions to
# create the corresponding object.
def create_notebook_template():
notebook = {
"apiVersion": "kubeflow.org/v1alpha1",
Expand Down Expand Up @@ -61,59 +73,39 @@ def create_notebook_template():
return notebook


def create_pvc_template():
pvc = {
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": {
"name": "",
"namespace": "",
},
"spec": {
"accessModes": [],
"resources": {
"requests": {
"storage": ""
}
},
}
}
return pvc


def set_notebook_names(nb, body):
nb['metadata']['name'] = body["nm"]
nb['metadata']['labels']['app'] = body["nm"]
nb['spec']['template']['spec']['containers'][0]['name'] = body["nm"]
nb['metadata']['namespace'] = body["ns"]
nb["metadata"]["name"] = body["nm"]
nb["metadata"]["labels"]["app"] = body["nm"]
nb["spec"]["template"]["spec"]["containers"][0]["name"] = body["nm"]
nb["metadata"]["namespace"] = body["ns"]


def set_notebook_image(nb, body):
if body["imageType"] == "standard":
image = body["standardImages"]
else:
image = body["customImage"]
nb["spec"]['template']['spec']['containers'][0]['image'] = image
nb["spec"]["template"]["spec"]["containers"][0]["image"] = image


def set_notebook_cpu_ram(nb, body):
notebook_cont = nb["spec"]['template']['spec']['containers'][0]
notebook_cont = nb["spec"]["template"]["spec"]["containers"][0]

notebook_cont['resources'] = {
'requests': {
'cpu': body['cpu'],
'memory': body['memory']
notebook_cont["resources"] = {
"requests": {
"cpu": body["cpu"],
"memory": body["memory"]
}
}


def add_notebook_volume(nb, vol, claim, mnt_path):
# Create the volume in the Pod
notebook_spec = nb["spec"]['template']['spec']
notebook_cont = nb["spec"]['template']['spec']['containers'][0]
notebook_spec = nb["spec"]["template"]["spec"]
notebook_cont = nb["spec"]["template"]["spec"]["containers"][0]

volume = {"name": vol, "persistentVolumeClaim": {"claimName": claim}}
notebook_spec['volumes'].append(volume)
notebook_spec["volumes"].append(volume)

# Container volumeMounts
mnt = {"mountPath": mnt_path, "name": vol}
Expand Down
Loading