airflow-unicore-integration 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {airflow_unicore_integration-0.2.1/src/airflow_unicore_integration.egg-info → airflow_unicore_integration-0.2.3}/PKG-INFO +1 -1
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/pyproject.toml +1 -1
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/unicore_executor.py +57 -23
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/hooks/unicore_hooks.py +7 -3
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/util/job.py +4 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3/src/airflow_unicore_integration.egg-info}/PKG-INFO +1 -1
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/LICENSE +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/README.rst +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/setup.cfg +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/__init__.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/__init__.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/run_task_via_supervisor.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/hooks/__init__.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/__init__.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/container.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/unicore_operators.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/policies/__init__.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/util/launch_script_content.py +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/SOURCES.txt +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/dependency_links.txt +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/entry_points.txt +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/requires.txt +0 -0
- {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/top_level.txt +0 -0
|
@@ -10,19 +10,19 @@ tasks should be allowed to overwrite SITE, CREDENTIALS_*, UNICORE_CONN_ID and DE
|
|
|
10
10
|
import time
|
|
11
11
|
from typing import Any
|
|
12
12
|
from typing import Dict
|
|
13
|
-
from typing import List
|
|
14
13
|
|
|
15
14
|
import pyunicore.client as uc_client
|
|
16
15
|
from airflow.configuration import conf
|
|
17
16
|
from airflow.executors.base_executor import BaseExecutor
|
|
18
17
|
from airflow.executors.workloads import All
|
|
19
18
|
from airflow.executors.workloads import ExecuteTask
|
|
19
|
+
from airflow.models.taskinstance import TaskInstance
|
|
20
20
|
from airflow.models.taskinstancekey import TaskInstanceKey
|
|
21
21
|
from airflow.utils.state import TaskInstanceState
|
|
22
22
|
from pyunicore import client
|
|
23
|
+
from pyunicore.credentials import Credential
|
|
23
24
|
from pyunicore.credentials import create_credential
|
|
24
25
|
|
|
25
|
-
from ..hooks.unicore_hooks import UnicoreHook
|
|
26
26
|
from ..util.job import JobDescriptionGenerator
|
|
27
27
|
from ..util.job import NaiveJobDescriptionGenerator
|
|
28
28
|
|
|
@@ -40,37 +40,74 @@ STATE_MAPPINGS: Dict[uc_client.JobStatus, TaskInstanceState] = {
|
|
|
40
40
|
|
|
41
41
|
class UnicoreExecutor(BaseExecutor):
|
|
42
42
|
|
|
43
|
-
EXECUTOR_CONFIG_UNICORE_CONN_KEY = (
|
|
44
|
-
"unicore_connection_id" # alternative connection id for the Unicore connection to use
|
|
45
|
-
)
|
|
46
43
|
EXECUTOR_CONFIG_UNICORE_SITE_KEY = "unicore_site" # alternative Unicore site to run at, only required if different than connection default
|
|
47
44
|
EXECUTOR_CONFIG_UNICORE_CREDENTIAL_KEY = "unicore_credential" # alternative unicore credential to use for the job, only required if different than connection default
|
|
48
45
|
|
|
46
|
+
serve_logs = True
|
|
47
|
+
|
|
49
48
|
def start(self):
|
|
50
49
|
self.active_jobs: Dict[TaskInstanceKey, uc_client.Job] = {}
|
|
51
50
|
# TODO get job description generator class and init params from config
|
|
52
51
|
self.job_descr_generator: JobDescriptionGenerator = NaiveJobDescriptionGenerator()
|
|
53
52
|
|
|
53
|
+
def _handle_used_compute_time(self, task: TaskInstanceKey, job: uc_client.Job) -> None:
|
|
54
|
+
pass
|
|
55
|
+
|
|
54
56
|
def sync(self) -> None:
|
|
55
57
|
# iterate through task collection and update task/ job status - delete if needed
|
|
56
58
|
for task, job in list(self.active_jobs.items()):
|
|
57
59
|
state = STATE_MAPPINGS[job.status]
|
|
58
60
|
if state == TaskInstanceState.FAILED:
|
|
59
61
|
self.fail(task)
|
|
60
|
-
self._forward_unicore_log(task, job)
|
|
61
62
|
self.active_jobs.pop(task)
|
|
63
|
+
self._handle_used_compute_time(task, job)
|
|
62
64
|
elif state == TaskInstanceState.SUCCESS:
|
|
63
65
|
self.success(task)
|
|
64
|
-
self._forward_unicore_log(task, job)
|
|
65
66
|
self.active_jobs.pop(task)
|
|
66
|
-
|
|
67
|
+
self._handle_used_compute_time(task, job)
|
|
68
|
+
elif state == TaskInstanceState.QUEUED:
|
|
67
69
|
self.running_state(task, state)
|
|
68
70
|
|
|
69
71
|
return super().sync()
|
|
70
72
|
|
|
71
|
-
def
|
|
72
|
-
#
|
|
73
|
-
|
|
73
|
+
def get_task_log(self, ti: TaskInstance, try_number: int) -> tuple[list[str], list[str]]:
|
|
74
|
+
# if task is still active, no neeed to check other jobs for match
|
|
75
|
+
job: uc_client.Job | None = None
|
|
76
|
+
if ti.key in self.active_jobs:
|
|
77
|
+
job = self.active_jobs[ti.key]
|
|
78
|
+
# if job is finished, need to search all jobs (that are still present on the target system)
|
|
79
|
+
else:
|
|
80
|
+
job = self._get_job_from_id(ti.key)
|
|
81
|
+
|
|
82
|
+
if not job:
|
|
83
|
+
return [], []
|
|
84
|
+
|
|
85
|
+
# TODO return filecontent for stdout and stderr from jobdirectory
|
|
86
|
+
logs = job.properties["log"] # type: ignore
|
|
87
|
+
working_dir = job.working_dir
|
|
88
|
+
stdout = working_dir.stat("/stdout")
|
|
89
|
+
stderr = working_dir.stat("/stderr")
|
|
90
|
+
|
|
91
|
+
lines_out = stdout.raw().read().decode("utf-8").split("\n") # type: ignore
|
|
92
|
+
lines_err = stderr.raw().read().decode("utf-8").split("\n") # type: ignore
|
|
93
|
+
|
|
94
|
+
return logs + lines_out + lines_err, ["Test", "Where is this shown??"]
|
|
95
|
+
|
|
96
|
+
def _get_job_from_id(self, key: TaskInstanceKey) -> uc_client.Job | None:
|
|
97
|
+
# iterate over jobs and check if key matches job name
|
|
98
|
+
unicore_client = self._get_unicore_client()
|
|
99
|
+
# check 50 jobs at a time to prevent server from not returning some jobs
|
|
100
|
+
offset = 0
|
|
101
|
+
number = 50
|
|
102
|
+
job_name = self.job_descr_generator.get_job_name(key)
|
|
103
|
+
while True:
|
|
104
|
+
partial_job_list = unicore_client.get_jobs(offset=offset, num=number)
|
|
105
|
+
if len(partial_job_list) == 0:
|
|
106
|
+
return None
|
|
107
|
+
offset += len(partial_job_list)
|
|
108
|
+
for job in partial_job_list:
|
|
109
|
+
if job.properties["name"] == job_name: # type: ignore
|
|
110
|
+
return job
|
|
74
111
|
|
|
75
112
|
def _get_unicore_client(self, executor_config: dict | None = {}):
|
|
76
113
|
overwrite_unicore_site = executor_config.get( # type: ignore
|
|
@@ -79,33 +116,30 @@ class UnicoreExecutor(BaseExecutor):
|
|
|
79
116
|
overwrite_unicore_credential = executor_config.get( # type: ignore
|
|
80
117
|
UnicoreExecutor.EXECUTOR_CONFIG_UNICORE_CREDENTIAL_KEY, None
|
|
81
118
|
) # task can provide a different credential to use, else default from connection is used
|
|
82
|
-
overwrite_conn_id = executor_config.get( # type: ignore
|
|
83
|
-
UnicoreExecutor.EXECUTOR_CONFIG_UNICORE_CONN_KEY, None
|
|
84
|
-
)
|
|
85
119
|
token = conf.get("unicore.executor", "AUTH_TOKEN", fallback="")
|
|
120
|
+
if overwrite_unicore_credential is not None:
|
|
121
|
+
token = overwrite_unicore_credential
|
|
122
|
+
self.log.debug("Using user provided token.")
|
|
86
123
|
base_url = conf.get(
|
|
87
124
|
"unicore.executor", "DEFAULT_URL", fallback="http://localhost:8080/DEMO-SITE/rest/core"
|
|
88
125
|
)
|
|
89
|
-
credential = create_credential(token=token)
|
|
90
|
-
if overwrite_conn_id is not None:
|
|
91
|
-
hook = UnicoreHook(overwrite_conn_id)
|
|
92
|
-
base_url = hook.get_base_url()
|
|
93
|
-
credential = hook.get_credential()
|
|
126
|
+
credential: Credential = create_credential(token=token)
|
|
94
127
|
if overwrite_unicore_site is not None:
|
|
95
128
|
base_url = overwrite_unicore_site
|
|
96
|
-
|
|
97
|
-
credential = overwrite_unicore_credential
|
|
129
|
+
self.log.debug("Using user provided site.")
|
|
98
130
|
if not base_url:
|
|
99
131
|
raise TypeError()
|
|
132
|
+
self.log.debug(f"Using site: {base_url}")
|
|
133
|
+
self.log.debug(f"Using credential: {credential}")
|
|
100
134
|
conn = client.Client(credential, base_url)
|
|
101
135
|
return conn
|
|
102
136
|
|
|
103
137
|
def _submit_job(self, workload: ExecuteTask):
|
|
104
|
-
|
|
138
|
+
unicore_client = self._get_unicore_client(executor_config=workload.ti.executor_config)
|
|
105
139
|
job_descr = self._create_job_description(workload)
|
|
106
140
|
self.log.info("Generated job description")
|
|
107
141
|
self.log.debug(str(job_descr))
|
|
108
|
-
job =
|
|
142
|
+
job = unicore_client.new_job(job_descr)
|
|
109
143
|
self.log.info("Submitted unicore job")
|
|
110
144
|
self.active_jobs[workload.ti.key] = job
|
|
111
145
|
return job
|
|
@@ -28,6 +28,7 @@ class UnicoreHook(BaseHook):
|
|
|
28
28
|
def __init__(self, uc_conn_id: str = default_conn_name) -> None:
|
|
29
29
|
super().__init__()
|
|
30
30
|
self.uc_conn_id = uc_conn_id
|
|
31
|
+
self.get_connection(self.uc_conn_id)
|
|
31
32
|
|
|
32
33
|
@classmethod
|
|
33
34
|
def get_connection_form_fields(cls):
|
|
@@ -38,9 +39,7 @@ class UnicoreHook(BaseHook):
|
|
|
38
39
|
"""Return custom UI field behaviour for UNICORE connection."""
|
|
39
40
|
return {
|
|
40
41
|
"hidden_fields": ["schema", "port", "extra"],
|
|
41
|
-
"relabeling": {
|
|
42
|
-
"login": "Username",
|
|
43
|
-
},
|
|
42
|
+
"relabeling": {"login": "Username", "host": "Site URL"},
|
|
44
43
|
"placeholder": {"auth_token": "UNICORE auth token"},
|
|
45
44
|
}
|
|
46
45
|
|
|
@@ -77,6 +76,11 @@ class UnicoreHook(BaseHook):
|
|
|
77
76
|
credential = credentials.create_credential(token=auth_token)
|
|
78
77
|
return credential
|
|
79
78
|
|
|
79
|
+
def get_token(self) -> str:
|
|
80
|
+
"""Returns a token if present, None if not."""
|
|
81
|
+
params = self.get_connection(self.uc_conn_id)
|
|
82
|
+
return params.extra_dejson.get("auth_token", None)
|
|
83
|
+
|
|
80
84
|
def get_base_url(self) -> str:
|
|
81
85
|
"""Return the base url of the connection."""
|
|
82
86
|
params = self.get_connection(self.uc_conn_id)
|
|
@@ -26,6 +26,7 @@ class JobDescriptionGenerator:
|
|
|
26
26
|
EXECUTOR_CONFIG_PRE_COMMANDS = "precommands" # gets added to the unicore job description
|
|
27
27
|
EXECUTOR_CONFIG_POST_COMMANDS = "postcommands" # gets added to the unicore job descirption
|
|
28
28
|
EXECUTOR_CONFIG_JOB_TYPE = "job_type"
|
|
29
|
+
EXECUTOR_CONFIG_LOGIN_NODE = "login_node"
|
|
29
30
|
EXECUTOR_CONFIG_UNICORE_CONN_KEY = (
|
|
30
31
|
"unicore_connection_id" # alternative connection id for the Unicore connection to use
|
|
31
32
|
)
|
|
@@ -61,6 +62,7 @@ class NaiveJobDescriptionGenerator(JobDescriptionGenerator):
|
|
|
61
62
|
user_defined_python_env: str = workload.ti.executor_config.get(JobDescriptionGenerator.EXECUTOR_CONFIG_PYTHON_ENV_KEY, None) # type: ignore
|
|
62
63
|
user_added_post_commands: list[str] = executor_config.get(JobDescriptionGenerator.EXECUTOR_CONFIG_POST_COMMANDS, []) # type: ignore
|
|
63
64
|
user_defined_job_type: str = executor_config.get(JobDescriptionGenerator.EXECUTOR_CONFIG_JOB_TYPE, None) # type: ignore
|
|
65
|
+
user_defined_login_node: str = executor_config.get(JobDescriptionGenerator.EXECUTOR_CONFIG_LOGIN_NODE, None) # type: ignore
|
|
64
66
|
# get local dag path from cmd and fix dag path in arguments
|
|
65
67
|
dag_rel_path = str(workload.dag_rel_path)
|
|
66
68
|
if dag_rel_path.startswith("DAG_FOLDER"):
|
|
@@ -76,6 +78,8 @@ class NaiveJobDescriptionGenerator(JobDescriptionGenerator):
|
|
|
76
78
|
# set job type
|
|
77
79
|
if user_defined_job_type:
|
|
78
80
|
job_descr_dict["Job type"] = user_defined_job_type
|
|
81
|
+
if user_defined_login_node:
|
|
82
|
+
job_descr_dict["Login Node"] = user_defined_login_node
|
|
79
83
|
|
|
80
84
|
# check which python virtualenv to use
|
|
81
85
|
if user_defined_python_env:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|