-
Notifications
You must be signed in to change notification settings - Fork 6
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
Adapt to new version of the zero to jupyterhub chart (3.1.0) #186
Changes from all commits
3088a15
6dc4c50
e43f7c1
a1eca44
3013430
fb86780
d33cb7d
c6cd3fe
f4f5997
ee62864
15a11d0
b5df246
99d5748
9755e9e
26eac6d
366b588
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,22 @@ | ||
import os, subprocess | ||
|
||
from kubernetes import client | ||
from kubernetes.client.rest import ApiException | ||
import escapism | ||
|
||
from kubernetes_asyncio.client.models import ( | ||
V1EmptyDirVolumeSource, | ||
V1EnvVar, | ||
V1EnvVarSource, | ||
V1ConfigMapVolumeSource, | ||
V1Container, | ||
V1KeyToPath, | ||
V1ObjectFieldSelector, | ||
V1ObjectMeta, | ||
V1Secret, | ||
V1SecretVolumeSource, | ||
V1Volume, | ||
V1VolumeMount, | ||
) | ||
|
||
from kubernetes_asyncio.client.rest import ApiException | ||
|
||
""" | ||
Class handling KubeSpawner.modify_pod_hook(spawner,pod) call | ||
|
@@ -10,7 +25,7 @@ | |
|
||
class SwanPodHookHandlerProd(SwanPodHookHandler): | ||
|
||
def get_swan_user_pod(self): | ||
async def get_swan_user_pod(self): | ||
super().get_swan_user_pod() | ||
|
||
# ATTENTION Spark requires this side container, so we need to create it!! | ||
|
@@ -19,17 +34,19 @@ def get_swan_user_pod(self): | |
# not self.spawner.local_home: | ||
|
||
# get eos token | ||
eos_secret_name = self._init_eos_secret() | ||
eos_secret_name = await self._init_eos_secret() | ||
|
||
# init user containers (notebook and side-container) | ||
self._init_eos_containers(eos_secret_name) | ||
|
||
return self.pod | ||
|
||
def _init_eos_secret(self): | ||
username = self.spawner.user.name | ||
user_uid = self.spawner.user_uid | ||
eos_secret_name ='eos-tokens-%s' % username | ||
async def _init_eos_secret(self): | ||
|
||
username = escapism.escape( | ||
self.spawner.user.name, safe = self.spawner.safe_chars, escape_char = '-' | ||
).lower() | ||
eos_secret_name = 'eos-tokens-%s' % username | ||
|
||
try: | ||
# Retrieve eos token for user | ||
|
@@ -39,34 +56,29 @@ def _init_eos_secret(self): | |
except Exception as e: | ||
raise ValueError("Could not create required user credential") | ||
|
||
|
||
# ITHADOOP-819 - Ports need to be opened using service creation, and later assigning allocated service nodeport to a pod | ||
# Create V1Secret with eos token | ||
try: | ||
secret_data = client.V1Secret() | ||
|
||
secret_meta = client.V1ObjectMeta() | ||
secret_meta.name = eos_secret_name | ||
secret_meta.namespace = swan_container_namespace | ||
secret_meta.labels = { | ||
"swan_user": username | ||
} | ||
secret_data.metadata = secret_meta | ||
secret_data.data = {} | ||
secret_data.data['krb5cc'] = eos_token_base64 | ||
secret_data = V1Secret() | ||
|
||
secret_meta = V1ObjectMeta() | ||
secret_meta.name = eos_secret_name | ||
secret_meta.namespace = swan_container_namespace | ||
secret_meta.labels = { | ||
"swan_user": username | ||
} | ||
secret_data.metadata = secret_meta | ||
secret_data.data = {} | ||
secret_data.data['krb5cc'] = eos_token_base64 | ||
|
||
try: | ||
# eos-tokens secret is cleaned when user session ends, so try creating it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, this reminds me that we split the code... This means that we have similar logic in 2 different places (we assume here that it will be created, but it's done somewhere else... And in that place we assumed it was created here...). I'm ok with leaving it here for now, but it's not the ideal solution. Let's open a PR upstream to fix the async call and then we put them back together (unless you think they should all live in the spawner). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, creation and deletion are in different places now. I'd prefer they are all in the spawner, but that does not mean they can't be hooks. Regarding the contribution upstream, I added a comment to this task https://its.cern.ch/jira/browse/SWAN-85 |
||
await self.spawner.api.create_namespaced_secret(swan_container_namespace, secret_data) | ||
except ApiException: | ||
# A secret with the same name exists, probably a remnant of a wrongly-terminated session, then replace it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a log object? This should be logged as a warning (since even wrongly terminated sessions should still remove it, no?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrongly terminated sessions should remove it, yes, since the removal code that is part of |
||
try: | ||
self.spawner.api.read_namespaced_secret(eos_secret_name, swan_container_namespace) | ||
exists = True | ||
except ApiException: | ||
exists = False | ||
|
||
if exists: | ||
self.spawner.api.replace_namespaced_secret(eos_secret_name, swan_container_namespace, secret_data) | ||
else: | ||
self.spawner.api.create_namespaced_secret(swan_container_namespace, secret_data) | ||
except ApiException as e: | ||
raise Exception("Could not create required eos secret: %s\n" % e) | ||
await self.spawner.api.replace_namespaced_secret(eos_secret_name, swan_container_namespace, secret_data) | ||
except ApiException as e: | ||
raise Exception("Could not create required eos secret: %s\n" % e) | ||
|
||
return eos_secret_name | ||
|
||
|
@@ -82,23 +94,23 @@ def _init_eos_containers(self, eos_secret_name): | |
|
||
# Shared directory between notebook and side-container for tokens with correct privileges | ||
self.pod.spec.volumes.append( | ||
client.V1Volume( | ||
V1Volume( | ||
name='shared-pod-volume', | ||
empty_dir=client.V1EmptyDirVolumeSource( | ||
empty_dir=V1EmptyDirVolumeSource( | ||
medium='Memory' | ||
) | ||
) | ||
) | ||
side_container_volume_mounts.append( | ||
client.V1VolumeMount( | ||
V1VolumeMount( | ||
name='shared-pod-volume', | ||
mount_path='/srv/notebook' | ||
) | ||
) | ||
|
||
# Mount shared tokens volume that contains tokens with correct permissions | ||
notebook_container.volume_mounts.append( | ||
client.V1VolumeMount( | ||
V1VolumeMount( | ||
name='shared-pod-volume', | ||
mount_path='/srv/notebook' | ||
) | ||
|
@@ -107,15 +119,15 @@ def _init_eos_containers(self, eos_secret_name): | |
# pod volume to mount generated eos tokens and | ||
# side-container volume mount with generated tokens | ||
self.pod.spec.volumes.append( | ||
client.V1Volume( | ||
V1Volume( | ||
name=eos_secret_name, | ||
secret=client.V1SecretVolumeSource( | ||
secret=V1SecretVolumeSource( | ||
secret_name='eos-tokens-%s' % username, | ||
) | ||
) | ||
) | ||
side_container_volume_mounts.append( | ||
client.V1VolumeMount( | ||
V1VolumeMount( | ||
name=eos_secret_name, | ||
mount_path='/srv/side-container/eos' | ||
) | ||
|
@@ -124,7 +136,7 @@ def _init_eos_containers(self, eos_secret_name): | |
# define eos kerberos credentials path for Jupyter server in notebook container | ||
notebook_container.env = self._add_or_replace_by_name( | ||
notebook_container.env, | ||
client.V1EnvVar( | ||
V1EnvVar( | ||
name='KRB5CCNAME', | ||
value='/srv/notebook/tokens/krb5cc' | ||
), | ||
|
@@ -133,7 +145,7 @@ def _init_eos_containers(self, eos_secret_name): | |
# define eos kerberos credentials path for notebook and terminal processes in notebook container | ||
notebook_container.env = self._add_or_replace_by_name( | ||
notebook_container.env, | ||
client.V1EnvVar( | ||
V1EnvVar( | ||
name='KRB5CCNAME_NB_TERM', | ||
value='/srv/notebook/tokens/writable/krb5cc_nb_term' | ||
), | ||
|
@@ -142,10 +154,10 @@ def _init_eos_containers(self, eos_secret_name): | |
# Set server hostname of the pod running jupyterhub | ||
notebook_container.env = self._add_or_replace_by_name( | ||
notebook_container.env, | ||
client.V1EnvVar( | ||
V1EnvVar( | ||
name='SERVER_HOSTNAME', | ||
value_from=client.V1EnvVarSource( | ||
field_ref=client.V1ObjectFieldSelector( | ||
value_from=V1EnvVarSource( | ||
field_ref=V1ObjectFieldSelector( | ||
field_path='spec.nodeName' | ||
) | ||
) | ||
|
@@ -155,12 +167,12 @@ def _init_eos_containers(self, eos_secret_name): | |
# append as first (it will be first to spawn) side container which currently: | ||
# - refreshes the kerberos token and adjust permissions for the user | ||
self.pod.spec.volumes.append( | ||
client.V1Volume( | ||
V1Volume( | ||
name='side-container-scripts', | ||
config_map=client.V1ConfigMapVolumeSource( | ||
config_map=V1ConfigMapVolumeSource( | ||
name='swan-scripts-cern', | ||
items=[ | ||
client.V1KeyToPath( | ||
V1KeyToPath( | ||
key='side_container_tokens_perm.sh', | ||
path='side_container_tokens_perm.sh', | ||
) | ||
|
@@ -170,7 +182,7 @@ def _init_eos_containers(self, eos_secret_name): | |
) | ||
) | ||
side_container_volume_mounts.append( | ||
client.V1VolumeMount( | ||
V1VolumeMount( | ||
name='side-container-scripts', | ||
mount_path='/srv/side-container/side_container_tokens_perm.sh', | ||
sub_path='side_container_tokens_perm.sh', | ||
|
@@ -179,7 +191,7 @@ def _init_eos_containers(self, eos_secret_name): | |
|
||
env = self.spawner.get_env() | ||
pod_spec_containers.append( | ||
client.V1Container( | ||
V1Container( | ||
name='side-container', | ||
image='cern/cc7-base:20181210', | ||
command=['/srv/side-container/side_container_tokens_perm.sh'], | ||
|
@@ -203,18 +215,18 @@ def _init_eos_containers(self, eos_secret_name): | |
# This is defined in the configuration to allow overring iindependently | ||
# of which config file is loaded first | ||
# c.SwanKubeSpawner.modify_pod_hook = swan_pod_hook | ||
def swan_pod_hook_prod(spawner, pod): | ||
async def swan_pod_hook_prod(spawner, pod): | ||
""" | ||
:param spawner: Swan Kubernetes Spawner | ||
:type spawner: swanspawner.SwanKubeSpawner | ||
:param pod: default pod definition set by jupyterhub | ||
:type pod: client.V1Pod | ||
:type pod: V1Pod | ||
|
||
:returns: dynamically customized pod specification for user session | ||
:rtype: client.V1Pod | ||
:rtype: V1Pod | ||
""" | ||
pod_hook_handler = SwanPodHookHandlerProd(spawner, pod) | ||
return pod_hook_handler.get_swan_user_pod() | ||
return await pod_hook_handler.get_swan_user_pod() | ||
|
||
|
||
swan_cull_period = get_config('custom.cull.every', 600) | ||
|
@@ -223,15 +235,5 @@ def swan_pod_hook_prod(spawner, pod): | |
|
||
c.SwanKubeSpawner.modify_pod_hook = swan_pod_hook_prod | ||
|
||
def swan_cern_post_stop_hook(spawner): | ||
# Delete Kubernetes Secret storing eos kerberos ticket of the user | ||
username = spawner.user.name | ||
eos_secret_name = f"eos-tokens-{username}" | ||
swan_container_namespace = os.environ.get('POD_NAMESPACE', 'default') | ||
spawner.log.info('Deleting secret %s', eos_secret_name) | ||
spawner.api.delete_namespaced_secret(eos_secret_name, swan_container_namespace) | ||
|
||
c.SwanKubeSpawner.post_stop_hook = swan_cern_post_stop_hook | ||
|
||
# Required for swan systemuser.sh | ||
c.SwanKubeSpawner.cmd = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we align the label name to the same as the container?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What name exactly?