Skip to content

Commit

Permalink
Merge pull request #38 from lsst-sqre/tickets/DM-29532
Browse files Browse the repository at this point in the history
[DM-29532] Fix debug and add clear .local flag
  • Loading branch information
cbanek authored Apr 7, 2021
2 parents ee0afc6 + 19ae8ad commit 69f196a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 33 deletions.
6 changes: 4 additions & 2 deletions dev-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ config:
EXTERNAL_USER: "{{ user }}"
EXTERNAL_UID: "{{ uid }}"
ACCESS_TOKEN: "{{ token }}"
IMAGE_DIGEST: "{{ image_info.digest }}"
IMAGE_DESCRIPTION: "{{ image_info.display_name }}"
IMAGE_DIGEST: "{{ options.image_info.digest }}"
IMAGE_DESCRIPTION: "{{ options.image_info.display_name }}"
CLEAR_DOTLOCAL: "{{ options.clear_dotlocal }}"
DEBUG: "{{ options.debug }}"
- apiVersion: v1
kind: ConfigMap
metadata:
Expand Down
35 changes: 8 additions & 27 deletions src/nublado2/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from jupyterhub.spawner import Spawner
from traitlets.config import LoggingConfigurable

from nublado2.imageinfo import ImageInfo
from nublado2.nublado_config import NubladoConfig
from nublado2.options import DROPDOWN_SENTINEL_VALUE, NubladoOptions
from nublado2.options import NubladoOptions
from nublado2.resourcemgr import ResourceManager
from nublado2.selectedoptions import SelectedOptions


class NubladoHooks(LoggingConfigurable):
Expand All @@ -15,32 +14,14 @@ def __init__(self) -> None:
self.optionsform = NubladoOptions()

async def pre_spawn(self, spawner: Spawner) -> None:
user = spawner.user.name
options = spawner.user_options
options = SelectedOptions(spawner.user_options)
self.log.debug(
f"Pre-spawn hook called for {user} with options {options}"
f"Pre-spawn called for {spawner.user.name} with options {options}"
)

# Look up what the user selected on the options form.
# Each parameter comes back as a list, even if only one is
# selected.
size_name = options["size"][0]
img_list_str = options["image_list"][0]
img_dropdown_str = options["image_dropdown"][0]
if img_list_str == DROPDOWN_SENTINEL_VALUE:
image_info = ImageInfo.from_packed_string(img_dropdown_str)
else:
image_info = ImageInfo.from_packed_string(img_list_str)

# Take size and image info, which are returned as form data,
# look up associated values, and configure the spawner.
# This will help set up the created lab pod.
nc = NubladoConfig()
(cpu, ram) = nc.lookup_size(size_name)
spawner.image = image_info.reference
spawner.debug = options.get("debug_enabled", False)
spawner.mem_limit = ram
spawner.cpu_limit = cpu
spawner.image = options.image_info.reference
spawner.mem_limit = options.ram
spawner.cpu_limit = options.cpu

auth_state = await spawner.user.get_auth_state()

Expand All @@ -55,7 +36,7 @@ async def pre_spawn(self, spawner: Spawner) -> None:
# which is useful for dask.
spawner.service_account = f"{spawner.user.name}-serviceaccount"

await self.resourcemgr.create_user_resources(spawner, image_info)
await self.resourcemgr.create_user_resources(spawner, options)

def post_stop(self, spawner: Spawner) -> None:
user = spawner.user.name
Expand Down
11 changes: 11 additions & 0 deletions src/nublado2/hub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ def configure(self, c: JupyterHub) -> None:

c.KubeSpawner.enable_user_namespaces = True

# This is how long the hub will wait for a lab pod to start.
# For large images, this also includes the time it takes to
# pull the docker image and start it.
c.KubeSpawner.start_timeout = 10 * 60 # 10 minutes

# This is how long to wait after the lab pod starts before
# the hub will give up waiting for the lab to start. When
# using the debug flag, sometimes this can take longer than
# the default, which is 30 seconds.
c.KubeSpawner.http_timeout = 90

# This is put in the lab pod, and tells kubernetes to
# use all the key: values found in the lab-environment
# configmap as environment variables for the lab
Expand Down
6 changes: 5 additions & 1 deletion src/nublado2/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@
<br>
<input type="checkbox"
name="enable_debug" value="true">Enable debug logs<br>
name="enable_debug" value="true">
Enable debug logs<br>
<input type="checkbox"
name="clear_dotlocal" value="true">
Clear <tt>.local</tt> directory (caution!)<br>
</td>
</tr>
Expand Down
6 changes: 3 additions & 3 deletions src/nublado2/resourcemgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from ruamel.yaml import RoundTripDumper, RoundTripLoader
from traitlets.config import LoggingConfigurable

from nublado2.imageinfo import ImageInfo
from nublado2.nublado_config import NubladoConfig
from nublado2.selectedoptions import SelectedOptions

config.load_incluster_config()

Expand All @@ -31,7 +31,7 @@ class ResourceManager(LoggingConfigurable):
http_client = aiohttp.ClientSession()

async def create_user_resources(
self, spawner: Spawner, image_info: ImageInfo
self, spawner: Spawner, options: SelectedOptions
) -> None:
"""Create the user resources for this spawning session."""
try:
Expand Down Expand Up @@ -65,7 +65,7 @@ async def create_user_resources(
"base_url": nc.get("base_url"),
"dask_yaml": await self._build_dask_template(spawner),
"auto_repo_urls": nc.get("auto_repo_urls"),
"image_info": image_info,
"options": options,
}

self.log.debug(f"Template values={template_values}")
Expand Down
67 changes: 67 additions & 0 deletions src/nublado2/selectedoptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Any, Dict

from nublado2.imageinfo import ImageInfo
from nublado2.nublado_config import NubladoConfig
from nublado2.options import DROPDOWN_SENTINEL_VALUE


class SelectedOptions:
"""This class parses the returned options form into fields.
Use this code to add additional options to the spawner form that
get parsed out of the user return form data. That way we can
have strong typing over them, and one place to parse them out."""

def __init__(self, options: Dict[str, Any]) -> None:
"""Create a SelectedOptions instance from the formdata."""

# Each parameter comes back as a list, even if only one is
# selected.
image_list = options["image_list"][0]
image_dropdown = options["image_dropdown"][0]
size_name = options["size"][0]

if image_list == DROPDOWN_SENTINEL_VALUE:
self._image_info = ImageInfo.from_packed_string(image_dropdown)
else:
self._image_info = ImageInfo.from_packed_string(image_list)

nc = NubladoConfig()
(self._cpu, self._ram) = nc.lookup_size(size_name)

self._debug = "TRUE" if "enable_debug" in options else ""
self._clear_dotlocal = "TRUE" if "clear_dotlocal" in options else ""

@property
def debug(self) -> str:
"""String to pass in for the DEBUG environment variable in the lab
This sets up the nublado lab containers to emit more debug info,
but doesn't work the same way the kubespawner.debug attribute does."""
return self._debug

@property
def clear_dotlocal(self) -> str:
"""String to pass in for CLEAR_DOTLOCAL variable in the lab.
This gets rid of the user's .local directory which may
cause issues during startup."""
return self._clear_dotlocal

@property
def image_info(self) -> ImageInfo:
"""Information on the Docker image to run for the lab."""
return self._image_info

@property
def cpu(self) -> float:
"""Number of vCPUs for the lab pod. Comes from the size."""
return self._cpu

@property
def ram(self) -> str:
"""Amount of RAM for the lab pod.
This is in kubernetes format, like 2g or 2048M, and comes
from the size."""
return self._ram

0 comments on commit 69f196a

Please sign in to comment.