Skip to content

Commit 4f9d507

Browse files
authored
Merge pull request #8 from LamaAni/move_config_to_runner
BUG - Error using configuration file - #7
2 parents 0c1bc70 + 436c67a commit 4f9d507

File tree

5 files changed

+62
-60
lines changed

5 files changed

+62
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Pipefile.lock
66
# ignore the test airflow db.
77
tests/airflow.db
88
tests/logs/
9+
tests/airflow-webserver.pid
910

1011
# Byte-compiled / optimized / DLL files
1112
__pycache__/

airflow_kubernetes_job_operator/job_runner.py

Lines changed: 37 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
ThreadedKubernetesResourcesWatcher,
88
)
99
from airflow_kubernetes_job_operator.event_handler import EventHandler
10-
from airflow_kubernetes_job_operator.utils import randomString, get_yaml_path_value
10+
from airflow_kubernetes_job_operator.utils import (
11+
randomString,
12+
get_yaml_path_value,
13+
)
1114

1215
JOB_RUNNER_INSTANCE_ID_LABEL = "job-runner-instance-id"
13-
KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH = (
14-
"/var/run/secrets/kubernetes.io/serviceaccount"
15-
)
16+
KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount"
1617

1718

1819
class JobRunner(EventHandler):
@@ -31,10 +32,11 @@ def __init__(self):
3132
print(info.status)
3233
print(info.yaml)
3334
"""
35+
self._loaded_config_file = None
3436
super().__init__()
3537

3638
def load_kuberntes_configuration(
37-
self, in_cluster: bool = None, config_file: str = None, context: str = None
39+
self, in_cluster: bool = None, config_file: str = None, context: str = None,
3840
):
3941
"""Loads the appropriate kubernetes configuration into the global
4042
context.
@@ -48,9 +50,7 @@ def load_kuberntes_configuration(
4850
context {str} -- The context to load. If None loads the current
4951
context (default: {None})
5052
"""
51-
in_cluster = (
52-
in_cluster or os.environ.get("KUBERNETES_SERVICE_HOST", None) is not None
53-
)
53+
in_cluster = in_cluster or os.environ.get("KUBERNETES_SERVICE_HOST", None) is not None
5454

5555
# loading the current config to use.
5656
if in_cluster:
@@ -61,39 +61,42 @@ def load_kuberntes_configuration(
6161
config_file
6262
), f"Cannot find kubernetes configuration file @ {config_file}"
6363

64+
# NOTE: When loading from a config file there is no way
65+
# (as of the current kubernetes package) to retrieve the loaded
66+
# configuration. As such, kubernetes.config.list_kube_config_contexts
67+
# will not provide the last loaded configuration and will revert
68+
# to the default config. Therefore if a config file is loaded
69+
# into the runner it will be used to retrieve the default namespace
70+
# (or other locations)
71+
self._loaded_config_file = config_file
72+
6473
kubernetes.config.load_kube_config(config_file=config_file, context=context)
6574

66-
@staticmethod
67-
def get_current_namespace():
75+
def get_current_namespace(self):
6876
"""Returns the current namespace.
6977
Returns:
7078
str
7179
"""
7280
namespace = ""
7381
try:
74-
in_cluster_namespace_fpath = os.path.join(
75-
KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH, "namespace"
76-
)
82+
in_cluster_namespace_fpath = os.path.join(KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH, "namespace")
7783
if os.path.exists(in_cluster_namespace_fpath):
7884
with open(in_cluster_namespace_fpath, "r", encoding="utf-8") as nsfile:
7985
namespace = nsfile.read()
8086
else:
81-
contexts, active_context = kubernetes.config.list_kube_config_contexts()
87+
(contexts, active_context,) = kubernetes.config.list_kube_config_contexts(
88+
config_file=self._loaded_config_file
89+
)
8290
namespace = (
83-
active_context["context"]["namespace"]
84-
if "namespace" in active_context["context"]
85-
else "default"
91+
active_context["context"]["namespace"] if "namespace" in active_context["context"] else "default"
8692
)
8793
except Exception as e:
8894
raise Exception(
89-
"Could not resolve current namespace, you must provide a namespace or a context file",
90-
e,
95+
"Could not resolve current namespace, you must provide a namespace or a context file", e,
9196
)
9297
return namespace
9398

94-
def prepare_job_yaml(
95-
self, job_yaml, random_name_postfix_length: int = 0, force_job_name: str = None
96-
) -> dict:
99+
def prepare_job_yaml(self, job_yaml, random_name_postfix_length: int = 0, force_job_name: str = None,) -> dict:
97100
"""Pre-prepare the job yaml dictionary for execution,
98101
can also accept a string input.
99102
@@ -133,11 +136,7 @@ def prepare_job_yaml(
133136
return
134137

135138
# make sure the yaml is an dict.
136-
job_yaml = (
137-
copy.deepcopy(job_yaml)
138-
if isinstance(job_yaml, dict)
139-
else yaml.safe_load(job_yaml)
140-
)
139+
job_yaml = copy.deepcopy(job_yaml) if isinstance(job_yaml, dict) else yaml.safe_load(job_yaml)
141140

142141
def get(path_names, default=None):
143142
try:
@@ -149,13 +148,10 @@ def get(path_names, default=None):
149148

150149
def assert_defined(path_names: list, def_name=None):
151150
path_string = ".".join(map(lambda v: str(v), path_names))
152-
assert (
153-
get(path_names) is not None
154-
), f"job {def_name or path_names[-1]} must be defined @ {path_string}"
151+
assert get(path_names) is not None, f"job {def_name or path_names[-1]} must be defined @ {path_string}"
155152

156-
assert get(["kind"]) == "Job", (
157-
"job_yaml resource must be of 'kind' 'Job', recived "
158-
+ get(["kind"], "[unknown]")
153+
assert get(["kind"]) == "Job", "job_yaml resource must be of 'kind' 'Job', recived " + get(
154+
["kind"], "[unknown]"
159155
)
160156

161157
assert_defined(["metadata", "name"])
@@ -166,18 +162,15 @@ def assert_defined(path_names: list, def_name=None):
166162
job_yaml["metadata"]["name"] = force_job_name
167163

168164
if random_name_postfix_length > 0:
169-
job_yaml["metadata"]["name"] += "-" + randomString(
170-
random_name_postfix_length
171-
)
165+
job_yaml["metadata"]["name"] += "-" + randomString(random_name_postfix_length)
172166

173167
# assign current namespace if one is not defined.
174168
if "namespace" not in job_yaml["metadata"]:
175169
try:
176170
job_yaml["metadata"]["namespace"] = self.get_current_namespace()
177171
except Exception as ex:
178172
raise Exception(
179-
"Namespace was not provided in yaml and auto namespace resolution failed.",
180-
ex,
173+
"Namespace was not provided in yaml and auto namespace resolution failed.", ex,
181174
)
182175

183176
# FIXME: Should be a better way to add missing values.
@@ -204,9 +197,7 @@ def assert_defined(path_names: list, def_name=None):
204197

205198
instance_id = randomString(15)
206199
job_yaml["metadata"]["labels"][JOB_RUNNER_INSTANCE_ID_LABEL] = instance_id
207-
job_yaml["spec"]["template"]["metadata"]["labels"][
208-
JOB_RUNNER_INSTANCE_ID_LABEL
209-
] = instance_id
200+
job_yaml["spec"]["template"]["metadata"]["labels"][JOB_RUNNER_INSTANCE_ID_LABEL] = instance_id
210201

211202
return job_yaml
212203

@@ -239,10 +230,7 @@ def execute_job(
239230
"metadata" in job_yaml
240231
and "labels" in job_yaml["metadata"]
241232
and JOB_RUNNER_INSTANCE_ID_LABEL in job_yaml["metadata"]["labels"]
242-
), (
243-
"job_yaml is not configured correctly, "
244-
+ "did you forget to call JobRunner.prepare_job_yaml?"
245-
)
233+
), ("job_yaml is not configured correctly, " + "did you forget to call JobRunner.prepare_job_yaml?")
246234

247235
metadata = job_yaml["metadata"]
248236
name = metadata["name"]
@@ -263,34 +251,26 @@ def execute_job(
263251
pass
264252

265253
if status is not None:
266-
raise Exception(
267-
f"Job {name} already exists in namespace {namespace}, cannot exec."
268-
)
254+
raise Exception(f"Job {name} already exists in namespace {namespace}, cannot exec.")
269255

270256
# starting the watcher.
271257
watcher = ThreadedKubernetesNamespaceResourcesWatcher(coreClient)
272258
watcher.auto_watch_pod_logs = read_logs
273259
watcher.remove_deleted_kube_resources_from_memory = False
274260
watcher.pipe(self)
275261
watcher.watch_namespace(
276-
namespace,
277-
label_selector=f"{JOB_RUNNER_INSTANCE_ID_LABEL}={instance_id}",
278-
watch_for_kinds=["Job", "Pod"],
262+
namespace, label_selector=f"{JOB_RUNNER_INSTANCE_ID_LABEL}={instance_id}", watch_for_kinds=["Job", "Pod"],
279263
)
280264

281265
# starting the job
282266
batchClient.create_namespaced_job(namespace, job_yaml)
283267

284268
# wait for job to start
285-
job_watcher = watcher.waitfor_status(
286-
"Job", name, namespace, status="Running", timeout=start_timeout
287-
)
269+
job_watcher = watcher.waitfor_status("Job", name, namespace, status="Running", timeout=start_timeout)
288270
self.emit("job_started", job_watcher, self)
289271

290272
# waiting for the job to completed.
291-
job_watcher = watcher.waitfor_status(
292-
"Job", name, namespace, status_list=["Failed", "Succeeded", "Deleted"]
293-
)
273+
job_watcher = watcher.waitfor_status("Job", name, namespace, status_list=["Failed", "Succeeded", "Deleted"],)
294274

295275
# not need to read status and logs anymore.
296276
watcher.stop()

experimental/core_tester/test_job_runner.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ def resource_status_changed(status, sender):
2727

2828

2929
# load kubernetes configuration.
30-
kubernetes.config.load_kube_config()
31-
current_namespace = JobRunner.get_current_namespace()
30+
config_file = "/home/zav/.kube/config_special" # change to test a specific config file
31+
current_context = "docker-desktop" # Change to test a specific context.
3232

3333
# prepare the runner.
3434
runner = JobRunner()
35+
runner.load_kuberntes_configuration(config_file=config_file, context=current_context)
36+
current_namespace = runner.get_current_namespace()
37+
print("Current namespace: " + current_namespace)
3538
runner.on("log", read_pod_log)
3639
runner.on("status", resource_status_changed)
3740

tests/airflow-webserver.pid

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
from airflow import DAG
3+
from airflow_kubernetes_job_operator.kubernetes_job_operator import KubernetesJobOperator
4+
from airflow.utils.dates import days_ago
5+
6+
default_args = {"owner": "tester", "start_date": days_ago(2), "retries": 0}
7+
dag = DAG(
8+
"job-operator-config-file", default_args=default_args, description="Test base job operator", schedule_interval=None,
9+
)
10+
11+
job_task = KubernetesJobOperator(
12+
task_id="test-job",
13+
dag=dag,
14+
image="ubuntu",
15+
in_cluster=False,
16+
cluster_context="docker-desktop",
17+
config_file=os.path.expanduser("~/.kube/config_special"),
18+
command=["bash", "-c", 'echo "all ok"'],
19+
)

0 commit comments

Comments
 (0)