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

[REVIEW] Add tolerations and node/pod affinities, add field verifications, add some default values #205

Closed
wants to merge 37 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9b277d3
Add tolerations to the pod object
kozhukalov May 8, 2018
0f6d15a
updated tests
consideRatio Jul 4, 2018
06cc452
make_pod tolerations param. documentation
consideRatio Jul 4, 2018
b3b2b36
ensure passed tolerations match API
consideRatio Jul 4, 2018
e0abfb4
boilerplate of the affinity field
consideRatio Jul 4, 2018
f9066a4
initial tests added
consideRatio Jul 4, 2018
1f91320
added affinity traitlets
consideRatio Jul 5, 2018
08abd2c
fix imports
consideRatio Jul 5, 2018
7ca1b8f
added affinity test
consideRatio Jul 5, 2018
4333303
added pod anti affinity tests
consideRatio Jul 5, 2018
359a588
adds affinity api verification
consideRatio Jul 5, 2018
826f6b4
fixed camelCase / snake_case issue
consideRatio Jul 5, 2018
a09e980
default values: None -> []
consideRatio Jul 5, 2018
275bf36
traitlet verification with k8s API specification
consideRatio Jul 5, 2018
2db1b62
removed 'volumes: []' from test validations
consideRatio Jul 6, 2018
9290f73
backwards compabtible > aesthetic
consideRatio Jul 6, 2018
7931e41
extra_pod_config override warning
consideRatio Jul 9, 2018
0fbb7db
support setting priority_class_name
consideRatio Jul 10, 2018
5fe57e0
Added .vscode to .gitignore
consideRatio Jul 19, 2018
a35cb34
Bugfix: singleuser_ prefix deprecation test
consideRatio Jul 19, 2018
122ca6c
Model verification now works (?)
consideRatio Jul 19, 2018
521b84a
Added test for dict/object inputs
consideRatio Jul 19, 2018
c8542c7
Removed Py3.4 testing
consideRatio Jul 19, 2018
03b870c
Added tests of utility functions
consideRatio Jul 19, 2018
a152cc8
Bumped JupyterHub requirement to 0.9
consideRatio Jul 19, 2018
3b511d7
Ensure stability in for loop
consideRatio Jul 28, 2018
0c84ccf
Revert mutable default keyword arguments
consideRatio Aug 1, 2018
64d6451
Bump kubespawner version to 0.9.1.
consideRatio Aug 1, 2018
8110c87
No need for setattr
consideRatio Aug 1, 2018
ee68998
Use traitlet's @default decorator
consideRatio Aug 1, 2018
663ea2c
Improve readability: traitlets first argument
consideRatio Aug 1, 2018
d65ab4a
Conform to known naming conventions
consideRatio Aug 1, 2018
100c4e8
Testing instructions added
consideRatio Aug 1, 2018
6714b62
Complemented scheduler_name configuration
consideRatio Aug 1, 2018
e528e63
Added scheduler_name test
consideRatio Aug 1, 2018
d35b1e8
Added priority_class_name test
consideRatio Aug 1, 2018
3445a8d
Fix merge artifacts
consideRatio Aug 1, 2018
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
Prev Previous commit
Next Next commit
Added test for dict/object inputs
  • Loading branch information
consideRatio committed Aug 1, 2018
commit 521b84a5fd78b4329abbee6a02946690c1be1930
60 changes: 42 additions & 18 deletions kubespawner/spawner.py
Original file line number Diff line number Diff line change
@@ -63,6 +63,9 @@ def events(self):
)


class MockObject(object):
pass

class KubeSpawner(Spawner):
"""
Implement a JupyterHub spawner to spawn pods in a Kubernetes Cluster.
@@ -90,27 +93,46 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

if _mock:
# if testing, skip the rest of initialization
# FIXME: rework initialization for easier mocking
return
# initialization for testing
user = MockObject()
setattr(user, "name", "mock_name")
Copy link
Member

Choose a reason for hiding this comment

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

No need to call setattr, this can be user.name = "mock_name"

Copy link
Member Author

Choose a reason for hiding this comment

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

Aha!

❤️

setattr(user, "id", "mock_id")
hub = MockObject
setattr(hub, "public_host", "mock_public_host")
setattr(hub, "url", "mock_url")
setattr(hub, "base_url", "mock_base_url")
setattr(hub, "api_url", "mock_api_url")
self.user = user
self.hub = hub
else:
# By now, all the traitlets have been set, so we can use them to compute
# other attributes
if self.__class__.executor is None:
self.__class__.executor = ThreadPoolExecutor(
max_workers=self.k8s_api_threadpool_workers
)

# By now, all the traitlets have been set, so we can use them to compute
# other attributes
if self.__class__.executor is None:
self.__class__.executor = ThreadPoolExecutor(
max_workers=self.k8s_api_threadpool_workers
)
main_loop = IOLoop.current()
def on_reflector_failure():
self.log.critical("Pod reflector failed, halting Hub.")
main_loop.stop()

# This will start watching in __init__, so it'll start the first
# time any spawner object is created. Not ideal but works!
if self.__class__.pod_reflector is None:
self.__class__.pod_reflector = PodReflector(
parent=self, namespace=self.namespace,
on_failure=on_reflector_failure
)

# This will start watching in __init__, so it'll start the first
# time any spawner object is created. Not ideal but works!
self._start_watching_pods()
if self.events_enabled:
self._start_watching_events()

self.api = shared_client('CoreV1Api')

self.pod_name = self._expand_user_properties(self.pod_name_template)
self.pvc_name = self._expand_user_properties(self.pvc_name_template)
if self.hub_connect_ip:
scheme, netloc, path, params, query, fragment = urlparse(self.hub.api_url)
netloc = '{ip}:{port}'.format(
@@ -121,6 +143,8 @@ def __init__(self, *args, **kwargs):
else:
self.accessible_hub_api_url = self.hub.api_url

self.pod_name = self._expand_user_properties(self.pod_name_template)
self.pvc_name = self._expand_user_properties(self.pvc_name_template)
if self.port == 0:
# Our default port is 8888
self.port = 8888
@@ -846,14 +870,14 @@ def _hub_connect_port_default(self):
config=True,
help="""
List of tolerations that are to be assigned to the pod in order to be able to schedule the pod
on a node with the corresponding taints. See the official Kubernetes documentation for additional details
on a node with the corresponding taints. See the official Kubernetes documentation for additional details
https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
Copy link
Member Author

Choose a reason for hiding this comment

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

Reword this


Pass this field an array of "Toleration" objects.*
* https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#nodeselectorterm-v1-core

Example:

[
{
'key': 'key',
@@ -867,7 +891,7 @@ def _hub_connect_port_default(self):
'effect': 'NoSchedule'
}
]

"""
)

@@ -931,7 +955,7 @@ def _hub_connect_port_default(self):
may prefer or require a node to have a certain label or be in proximity
/ remoteness to another pod. To learn more visit
https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

Pass this field an array of "WeightedPodAffinityTerm" objects.*
* https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#weightedpodaffinityterm-v1-core
"""
@@ -944,7 +968,7 @@ def _hub_connect_port_default(self):
may prefer or require a node to have a certain label or be in proximity
/ remoteness to another pod. To learn more visit
https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

Pass this field an array of "PodAffinityTerm" objects.*
* https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#podaffinityterm-v1-core
"""
44 changes: 44 additions & 0 deletions tests/test_spawner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from traitlets.config import Config
from asyncio import get_event_loop
from kubespawner import KubeSpawner
from kubernetes.client.models import (
V1SecurityContext, V1Container, V1Capabilities, V1Pod
)

def sync_wait(future):
loop = get_event_loop()
loop.run_until_complete(future)
return future.result()

def test_deprecated_config():
"""Deprecated config is handled correctly"""
@@ -28,3 +36,39 @@ def test_deprecated_runtime_access():
spawner.uid = 20
assert spawner.uid == 20
assert spawner.singleuser_uid == 20

def test_get_pod_manifest_tolerates_mixed_input():
"""
Test that the get_pod_manifest function can handle a either a dictionary or
an object both representing V1Container objects and that the function
returns a V1Pod object containing V1Container objects.
"""
cfg = Config()
ks_cfg = cfg.KubeSpawner

dict_model = {
'name': 'mock_name_1',
'image': 'mock_image_1',
'command': ['mock_command_1']
}
object_model = V1Container(
name="mock_name_2",
image="mock_image_2",
command=['mock_command_2'],
security_context=V1SecurityContext(
privileged=True,
run_as_user=0,
capabilities=V1Capabilities(add=['NET_ADMIN'])
)
)
ks_cfg.init_containers = [dict_model, object_model]

spawner = KubeSpawner(config=cfg, _mock=True)

# this test ensures the following line doesn't raise an error
manifest = sync_wait(spawner.get_pod_manifest())

# and tests the return value's types
assert isinstance(manifest, V1Pod)
assert isinstance(manifest.spec.init_containers[0], V1Container)
assert isinstance(manifest.spec.init_containers[1], V1Container)