Skip to content
This repository was archived by the owner on Jun 12, 2024. It is now read-only.

Commit 6418088

Browse files
committed
Add Job support
1 parent bf49c92 commit 6418088

File tree

5 files changed

+171
-18
lines changed

5 files changed

+171
-18
lines changed

features/job.feature

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Feature: Manage Jobs
2+
3+
Scenario: Try retrieve a Job that does not exist
4+
Given a Job called sleepy-bohr does not exist
5+
When the user attempts to retrieve the Job sleepy-bohr
6+
Then None is returned instead of the Job
7+
8+
9+
Scenario: Create a Job
10+
Given a Job called strange-bhaskara does not exist
11+
When the Job called strange-bhaskara is created
12+
Then a valid Job called strange-bhaskara can be found
13+
14+
15+
Scenario: Retrieve a Job that exists
16+
Given the Job called lucid-lovelace exists
17+
When the user attempts to retrieve the Job lucid-lovelace
18+
Then Results for the Job lucid-lovelace are returned

features/steps/cronjob.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
@when("the CronJob called {cronjob_name} is created")
9+
@given("the CronJob called {cronjob_name} exists")
910
def step_impl(context, cronjob_name):
1011
cronjob_namespace = "default"
1112

@@ -31,29 +32,13 @@ def step_impl(context, cronjob_name):
3132
cronjob.delete(context.k8s_v1_batch_client, cronjob_name)
3233

3334

34-
@given("the CronJob called {cronjob_name} exists")
35-
def step_impl(context, cronjob_name):
36-
cronjob_namespace = "default"
37-
38-
env = Environment(
39-
loader=FileSystemLoader("./templates"), trim_blocks=True, lstrip_blocks=True
40-
)
41-
cronjob_tmpl = env.get_template("cronjob.j2")
42-
cronjob_spec_file = cronjob_tmpl.render(
43-
cronjob_name=cronjob_name, cronjob_namespace=cronjob_namespace
44-
)
45-
context.create_resp = cronjob.create(
46-
context.k8s_v1_batch_client, cronjob_spec_file, cronjob_namespace
47-
)
48-
49-
5035
@then("None is returned instead of the CronJob")
5136
def step_impl(context):
5237
assert context.get_resp is None, "Did not return None"
5338

5439

55-
@then("Results for the CronJob hello are returned")
56-
def step_impl(context):
40+
@then("Results for the CronJob {cronjob_name} are returned")
41+
def step_impl(context, cronjob_name):
5742
assert context.get_resp, "Should've returned a valid response"
5843

5944

features/steps/job.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from behave import *
2+
3+
from kelpy import job
4+
from kubernetes import config, client
5+
from jinja2 import Environment, FileSystemLoader
6+
7+
8+
@given("a Job called {job_name} does not exist")
9+
def step_impl(context, job_name):
10+
job.delete(context.k8s_v1_batch_client, job_name)
11+
12+
13+
@given("the Job called {job_name} exists")
14+
@when("the Job called {job_name} is created")
15+
def step_impl(context, job_name):
16+
job_namespace = "default"
17+
18+
env = Environment(
19+
loader=FileSystemLoader("./templates"), trim_blocks=True, lstrip_blocks=True
20+
)
21+
job_tmpl = env.get_template("job.j2")
22+
job_spec_file = job_tmpl.render(job_name=job_name, job_namespace=job_namespace)
23+
context.create_resp = job.create(
24+
context.k8s_v1_batch_client, job_spec_file, job_namespace
25+
)
26+
27+
28+
@when("the user attempts to retrieve the Job {job_name}")
29+
def step_impl(context, job_name):
30+
context.get_resp = job.get(context.k8s_v1_batch_client, job_name)
31+
32+
33+
@then("None is returned instead of the Job")
34+
def step_impl(context):
35+
assert context.get_resp is None, "Did not return None"
36+
37+
38+
@then("a valid Job called {job_name} can be found")
39+
def step_impl(context, job_name):
40+
assert context.create_resp, f"Should've found {job_name} Job"
41+
42+
43+
@then("Results for the Job {job_name} are returned")
44+
def step_impl(context, job_name):
45+
assert context.get_resp, "Should've returned a valid response"

kelpy/job.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import yaml
2+
from kubernetes.client.exceptions import ApiException
3+
from kubernetes import watch
4+
5+
6+
def create(client, spec: str, namespace: str = "default", timeout=100):
7+
"""Create a Job.
8+
9+
:batch_v1_api: The Batch V1 API object.
10+
:spec: A valid Job YAML manifest.
11+
:namespace: The namespace of the Job.
12+
:timeout: Timeout in seconds to wait for object creation/modification
13+
14+
:returns: True on creation, False if it already exists.
15+
"""
16+
body = yaml.safe_load(spec)
17+
18+
try:
19+
response = client.create_namespaced_job(namespace, body)
20+
except ApiException as e:
21+
# If the object already exists, return False.
22+
if e.reason == "Conflict":
23+
return False
24+
raise e
25+
26+
name = body["metadata"]["name"]
27+
if get(client, name, namespace) is None:
28+
w = watch.Watch()
29+
for event in w.stream(
30+
client.list_job_for_all_namespaces, timeout_seconds=timeout
31+
):
32+
if (
33+
(event["type"] == "ADDED" or event["type"] == "MODIFIED")
34+
and event["object"].kind == "Job"
35+
and event["object"].metadata.name == response.metadata.name
36+
and event["object"].metadata.namespace == response.metadata.namespace
37+
):
38+
break
39+
40+
return response
41+
42+
43+
def get(client, name: str, namespace: str = "default"):
44+
"""Get a Job if it does exist, if it doesn't, return None.
45+
46+
:batch_v1_api: The Batch V1 API object.
47+
:name: The name of the Job.
48+
:namespace: The namespace of the Job.
49+
50+
:returns: Job if it does exist, None if it doesn't exist.
51+
"""
52+
field_selector = "metadata.name={0}".format(name)
53+
54+
response = client.list_namespaced_job(namespace, field_selector=field_selector)
55+
56+
if len(response.items) == 1:
57+
return response
58+
59+
return None
60+
61+
62+
def delete(client, name: str, namespace: str = "default", timeout=100):
63+
"""Delete a Job if it does exist, if it doesn't, return False.
64+
65+
:batch_v1_api: The Batch V1 API object.
66+
:name: The name of the Job.
67+
:namespace: The namespace of the Job.
68+
:timeout: Timeout in seconds to wait for object deletion
69+
70+
:returns: Batch API Response if it does exist, False if it doesn't exist.
71+
"""
72+
try:
73+
response = client.delete_namespaced_job(name, namespace)
74+
except ApiException as e:
75+
if e.reason == "Not Found":
76+
return False
77+
raise e
78+
79+
if get(client, name, namespace) is not None:
80+
w = watch.Watch()
81+
for event in w.stream(
82+
client.list_job_for_all_namespaces, timeout_seconds=timeout
83+
):
84+
if (
85+
event["type"] == "DELETED"
86+
and event["object"].metadata.name == name
87+
and event["object"].metadata.namespace == namespace
88+
):
89+
break
90+
91+
return response

templates/job.j2

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: batch/v1
2+
kind: Job
3+
metadata:
4+
name: {{ job_name }}
5+
namespace: {{ job_namespace }}
6+
spec:
7+
template:
8+
spec:
9+
containers:
10+
- name: pi
11+
image: perl
12+
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
13+
restartPolicy: Never
14+
backoffLimit: 4

0 commit comments

Comments
 (0)