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.
Files changed (23) hide show
  1. {airflow_unicore_integration-0.2.1/src/airflow_unicore_integration.egg-info → airflow_unicore_integration-0.2.3}/PKG-INFO +1 -1
  2. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/pyproject.toml +1 -1
  3. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/unicore_executor.py +57 -23
  4. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/hooks/unicore_hooks.py +7 -3
  5. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/util/job.py +4 -0
  6. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3/src/airflow_unicore_integration.egg-info}/PKG-INFO +1 -1
  7. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/LICENSE +0 -0
  8. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/README.rst +0 -0
  9. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/setup.cfg +0 -0
  10. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/__init__.py +0 -0
  11. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/__init__.py +0 -0
  12. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/executors/run_task_via_supervisor.py +0 -0
  13. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/hooks/__init__.py +0 -0
  14. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/__init__.py +0 -0
  15. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/container.py +0 -0
  16. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/operators/unicore_operators.py +0 -0
  17. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/policies/__init__.py +0 -0
  18. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration/util/launch_script_content.py +0 -0
  19. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/SOURCES.txt +0 -0
  20. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/dependency_links.txt +0 -0
  21. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/entry_points.txt +0 -0
  22. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/requires.txt +0 -0
  23. {airflow_unicore_integration-0.2.1 → airflow_unicore_integration-0.2.3}/src/airflow_unicore_integration.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airflow-unicore-integration
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Running Unicore Jobs from airflow DAGs.
5
5
  Author-email: Christian Böttcher <c.boettcher@fz-juelich.de>
6
6
  License-Expression: BSD-3-Clause
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "airflow-unicore-integration"
10
- version = "0.2.1"
10
+ version = "0.2.3"
11
11
  authors = [
12
12
  { name="Christian Böttcher", email="c.boettcher@fz-juelich.de" },
13
13
  ]
@@ -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
- elif state == TaskInstanceState.RUNNING:
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 _forward_unicore_log(self, task: TaskInstanceKey, job: uc_client.Job) -> List[str]:
72
- # TODO retrieve unicore logs from job directory and return
73
- return []
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
- if overwrite_unicore_credential is not None:
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
- uc_client = self._get_unicore_client(executor_config=workload.ti.executor_config)
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 = uc_client.new_job(job_descr)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airflow-unicore-integration
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Running Unicore Jobs from airflow DAGs.
5
5
  Author-email: Christian Böttcher <c.boettcher@fz-juelich.de>
6
6
  License-Expression: BSD-3-Clause