Skip to content

Commit

Permalink
Merge pull request #681 from dolfinus/test_spawn_in_different_namespace
Browse files Browse the repository at this point in the history
Add test to spawn a pod in a separate namespace
  • Loading branch information
consideRatio committed Jan 17, 2023
2 parents 7488b5e + 4a8c714 commit 21642eb
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 26 deletions.
64 changes: 38 additions & 26 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ def kube_ns():
return os.environ.get("KUBESPAWNER_TEST_NAMESPACE") or "kubespawner-test"


@pytest.fixture(scope="session")
def kube_another_ns():
"""Fixture for the another kubernetes namespace"""
return os.environ.get("KUBESPAWNER_ANOTHER_NAMESPACE") or "kubespawner-another"


@pytest.fixture
def config(kube_ns):
"""Return a traitlets Config object
Expand Down Expand Up @@ -231,55 +237,61 @@ async def watch_kubernetes(kube_client, kube_ns):


@pytest_asyncio.fixture(scope="session")
async def kube_client(request, kube_ns):
async def kube_client(request, kube_ns, kube_another_ns):
"""fixture for the Kubernetes client object.
skips test that require kubernetes if kubernetes cannot be contacted
- Ensures kube_ns namespace exists
- Ensures kube_ns and kube_another_ns namespaces do exist
- Hooks up kubernetes events and logs to pytest capture
- Cleans up kubernetes namespace on exit
"""
await load_kube_config()
client = shared_client("CoreV1Api")
stop_signal = asyncio.Queue()

expected_namespaces = [kube_ns, kube_another_ns]
try:
namespaces = await client.list_namespace(_request_timeout=3)
except Exception as e:
pytest.skip("Kubernetes not found: %s" % e)

if not any(ns.metadata.name == kube_ns for ns in namespaces.items):
print("Creating namespace %s" % kube_ns)
await client.create_namespace(V1Namespace(metadata=dict(name=kube_ns)))
else:
print("Using existing namespace %s" % kube_ns)
for namespace in expected_namespaces:
if not any(ns.metadata.name == namespace for ns in namespaces.items):
print("Creating namespace %s" % namespace)
await client.create_namespace(V1Namespace(metadata=dict(name=namespace)))
else:
print("Using existing namespace %s" % namespace)

# begin streaming all logs and events in our test namespace
t = asyncio.create_task(watch_kubernetes(client, kube_ns))
log_tasks = [
asyncio.create_task(watch_kubernetes(client, namespace))
for namespace in expected_namespaces
]

yield client

# Clean up at close by sending a cancel to watch_kubernetes and letting
# it handle the signal, cancel the tasks *it* started, and then raising
# it back to us.
try:
t.cancel()
except asyncio.CancelledError:
pass
for task in log_tasks:
try:
task.cancel()
except asyncio.CancelledError:
pass

# allow opting out of namespace cleanup, for post-mortem debugging
if not os.environ.get("KUBESPAWNER_DEBUG_NAMESPACE"):
await client.delete_namespace(kube_ns, body={}, grace_period_seconds=0)
for i in range(20): # Usually finishes a good deal faster
try:
ns = await client.read_namespace(kube_ns)
except ApiException as e:
if e.status == 404:
return
for namespace in expected_namespaces:
await client.delete_namespace(namespace, body={}, grace_period_seconds=0)
for _ in range(20): # Usually finishes a good deal faster
try:
await client.read_namespace(namespace)
except ApiException as e:
if e.status == 404:
return
else:
raise
else:
raise
else:
print("waiting for %s to delete" % kube_ns)
await asyncio.sleep(1)
print("waiting for %s to delete" % namespace)
await asyncio.sleep(1)


async def wait_for_pod(kube_client, kube_ns, pod_name, timeout=90):
Expand Down
59 changes: 59 additions & 0 deletions tests/test_spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,65 @@ async def test_spawn_start(
assert isinstance(status, int)


async def test_spawn_start_in_different_namespace(
kube_another_ns,
kube_client,
config,
hub,
exec_python,
reset_pod_reflector,
):
# hub is running in `kube_ns`. pods, PVC and other objects are created in `kube_another_ns`
config.KubeSpawner.namespace = kube_another_ns

spawner = KubeSpawner(
hub=hub,
user=MockUser(name="start"),
config=config,
api_token="abc123",
oauth_client_id="unused",
)
# empty spawner isn't running
status = await spawner.poll()
assert isinstance(status, int)

pod_name = spawner.pod_name

# start the spawner
url = await spawner.start()

# verify the pod exists
pods = (await kube_client.list_namespaced_pod(kube_another_ns)).items
pod_names = [p.metadata.name for p in pods]
assert pod_name in pod_names

# pod should be running when start returns
pod = await kube_client.read_namespaced_pod(
namespace=kube_another_ns, name=pod_name
)
assert pod.status.phase == "Running"

# verify poll while running
status = await spawner.poll()
assert status is None

# make sure spawn url is correct
r = await exec_python(check_up, {"url": url}, _retries=3)
assert r == "302"

# stop the pod
await spawner.stop()

# verify pod is gone
pods = (await kube_client.list_namespaced_pod(kube_another_ns)).items
pod_names = [p.metadata.name for p in pods]
assert pod_name not in pod_names

# verify exit status
status = await spawner.poll()
assert isinstance(status, int)


async def test_spawn_component_label(
kube_ns,
kube_client,
Expand Down

0 comments on commit 21642eb

Please sign in to comment.