mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc16__py3-none-any.whl

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.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

Files changed (107) hide show
  1. mlrun/__init__.py +10 -1
  2. mlrun/__main__.py +18 -109
  3. mlrun/{runtimes/mpijob/v1alpha1.py → alerts/__init__.py} +2 -16
  4. mlrun/alerts/alert.py +141 -0
  5. mlrun/artifacts/__init__.py +8 -3
  6. mlrun/artifacts/base.py +36 -253
  7. mlrun/artifacts/dataset.py +9 -190
  8. mlrun/artifacts/manager.py +20 -41
  9. mlrun/artifacts/model.py +8 -140
  10. mlrun/artifacts/plots.py +14 -375
  11. mlrun/common/schemas/__init__.py +4 -2
  12. mlrun/common/schemas/alert.py +46 -4
  13. mlrun/common/schemas/api_gateway.py +4 -0
  14. mlrun/common/schemas/artifact.py +15 -0
  15. mlrun/common/schemas/auth.py +2 -0
  16. mlrun/common/schemas/model_monitoring/__init__.py +8 -1
  17. mlrun/common/schemas/model_monitoring/constants.py +40 -4
  18. mlrun/common/schemas/model_monitoring/model_endpoints.py +73 -2
  19. mlrun/common/schemas/project.py +2 -0
  20. mlrun/config.py +7 -4
  21. mlrun/data_types/to_pandas.py +4 -4
  22. mlrun/datastore/base.py +41 -9
  23. mlrun/datastore/datastore_profile.py +54 -4
  24. mlrun/datastore/inmem.py +2 -2
  25. mlrun/datastore/sources.py +43 -2
  26. mlrun/datastore/store_resources.py +2 -6
  27. mlrun/datastore/targets.py +106 -39
  28. mlrun/db/base.py +23 -3
  29. mlrun/db/httpdb.py +101 -47
  30. mlrun/db/nopdb.py +20 -2
  31. mlrun/errors.py +5 -0
  32. mlrun/feature_store/__init__.py +0 -2
  33. mlrun/feature_store/api.py +12 -47
  34. mlrun/feature_store/feature_set.py +9 -0
  35. mlrun/feature_store/retrieval/base.py +9 -4
  36. mlrun/feature_store/retrieval/conversion.py +4 -4
  37. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  38. mlrun/feature_store/retrieval/job.py +2 -0
  39. mlrun/feature_store/retrieval/local_merger.py +2 -0
  40. mlrun/feature_store/retrieval/spark_merger.py +5 -0
  41. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +5 -10
  42. mlrun/launcher/base.py +4 -3
  43. mlrun/launcher/client.py +1 -1
  44. mlrun/lists.py +4 -2
  45. mlrun/model.py +25 -11
  46. mlrun/model_monitoring/__init__.py +1 -1
  47. mlrun/model_monitoring/api.py +41 -18
  48. mlrun/model_monitoring/application.py +5 -305
  49. mlrun/model_monitoring/applications/__init__.py +11 -0
  50. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  51. mlrun/model_monitoring/applications/base.py +282 -0
  52. mlrun/model_monitoring/applications/context.py +214 -0
  53. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  54. mlrun/model_monitoring/applications/histogram_data_drift.py +132 -91
  55. mlrun/model_monitoring/applications/results.py +99 -0
  56. mlrun/model_monitoring/controller.py +3 -1
  57. mlrun/model_monitoring/db/__init__.py +2 -0
  58. mlrun/model_monitoring/db/stores/base/store.py +9 -36
  59. mlrun/model_monitoring/db/stores/sqldb/models/base.py +7 -6
  60. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +63 -110
  61. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +104 -187
  62. mlrun/model_monitoring/db/tsdb/__init__.py +71 -0
  63. mlrun/model_monitoring/db/tsdb/base.py +135 -0
  64. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  65. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  66. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +404 -0
  67. mlrun/model_monitoring/db/v3io_tsdb_reader.py +134 -0
  68. mlrun/model_monitoring/evidently_application.py +6 -118
  69. mlrun/model_monitoring/helpers.py +1 -1
  70. mlrun/model_monitoring/model_endpoint.py +3 -2
  71. mlrun/model_monitoring/stream_processing.py +48 -213
  72. mlrun/model_monitoring/writer.py +101 -121
  73. mlrun/platforms/__init__.py +10 -9
  74. mlrun/platforms/iguazio.py +21 -202
  75. mlrun/projects/operations.py +11 -7
  76. mlrun/projects/pipelines.py +13 -76
  77. mlrun/projects/project.py +73 -45
  78. mlrun/render.py +11 -13
  79. mlrun/run.py +6 -41
  80. mlrun/runtimes/__init__.py +3 -3
  81. mlrun/runtimes/base.py +6 -6
  82. mlrun/runtimes/funcdoc.py +0 -28
  83. mlrun/runtimes/kubejob.py +2 -1
  84. mlrun/runtimes/local.py +1 -1
  85. mlrun/runtimes/mpijob/__init__.py +0 -20
  86. mlrun/runtimes/mpijob/v1.py +1 -1
  87. mlrun/runtimes/nuclio/api_gateway.py +75 -9
  88. mlrun/runtimes/nuclio/function.py +9 -35
  89. mlrun/runtimes/pod.py +16 -36
  90. mlrun/runtimes/remotesparkjob.py +1 -1
  91. mlrun/runtimes/sparkjob/spark3job.py +1 -1
  92. mlrun/runtimes/utils.py +1 -39
  93. mlrun/utils/helpers.py +72 -71
  94. mlrun/utils/notifications/notification/base.py +1 -1
  95. mlrun/utils/notifications/notification/slack.py +12 -5
  96. mlrun/utils/notifications/notification/webhook.py +1 -1
  97. mlrun/utils/notifications/notification_pusher.py +134 -14
  98. mlrun/utils/version/version.json +2 -2
  99. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/METADATA +4 -3
  100. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/RECORD +105 -95
  101. mlrun/kfpops.py +0 -865
  102. mlrun/platforms/other.py +0 -305
  103. /mlrun/{runtimes → common/runtimes}/constants.py +0 -0
  104. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/LICENSE +0 -0
  105. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/WHEEL +0 -0
  106. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/entry_points.txt +0 -0
  107. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc16.dist-info}/top_level.txt +0 -0
@@ -12,25 +12,21 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import datetime
16
15
  import json
17
16
  from typing import Any, NewType
18
17
 
19
- import pandas as pd
20
- from v3io.dataplane import Client as V3IOClient
21
- from v3io_frames.client import ClientBase as V3IOFramesClient
22
- from v3io_frames.errors import Error as V3IOFramesError
23
- from v3io_frames.frames_pb2 import IGNORE
24
-
25
18
  import mlrun.common.model_monitoring
19
+ import mlrun.common.schemas
26
20
  import mlrun.common.schemas.alert as alert_constants
27
21
  import mlrun.model_monitoring
28
- import mlrun.model_monitoring.db.stores
29
- import mlrun.utils.v3io_clients
30
22
  from mlrun.common.schemas.model_monitoring.constants import (
31
23
  EventFieldType,
24
+ HistogramDataDriftApplicationConstants,
25
+ MetricData,
26
+ ResultData,
32
27
  ResultStatusApp,
33
28
  WriterEvent,
29
+ WriterEventKind,
34
30
  )
35
31
  from mlrun.common.schemas.notification import NotificationKind, NotificationSeverity
36
32
  from mlrun.model_monitoring.helpers import get_endpoint_record
@@ -38,9 +34,6 @@ from mlrun.serving.utils import StepToDict
38
34
  from mlrun.utils import logger
39
35
  from mlrun.utils.notifications.notification_pusher import CustomNotificationPusher
40
36
 
41
- _TSDB_BE = "tsdb"
42
- _TSDB_RATE = "1/s"
43
- _TSDB_TABLE = "app-results"
44
37
  _RawEvent = dict[str, Any]
45
38
  _AppResultEvent = NewType("_AppResultEvent", _RawEvent)
46
39
 
@@ -75,20 +68,20 @@ class _Notifier:
75
68
  self._severity = severity
76
69
 
77
70
  def _should_send_event(self) -> bool:
78
- return self._event[WriterEvent.RESULT_STATUS] >= ResultStatusApp.detected
71
+ return self._event[ResultData.RESULT_STATUS] >= ResultStatusApp.detected.value
79
72
 
80
73
  def _generate_message(self) -> str:
81
74
  return f"""\
82
75
  The monitoring app `{self._event[WriterEvent.APPLICATION_NAME]}` \
83
- of kind `{self._event[WriterEvent.RESULT_KIND]}` \
76
+ of kind `{self._event[ResultData.RESULT_KIND]}` \
84
77
  detected a problem in model endpoint ID `{self._event[WriterEvent.ENDPOINT_ID]}` \
85
78
  at time `{self._event[WriterEvent.START_INFER_TIME]}`.
86
79
 
87
80
  Result data:
88
- Name: `{self._event[WriterEvent.RESULT_NAME]}`
89
- Value: `{self._event[WriterEvent.RESULT_VALUE]}`
90
- Status: `{self._event[WriterEvent.RESULT_STATUS]}`
91
- Extra data: `{self._event[WriterEvent.RESULT_EXTRA_DATA]}`\
81
+ Name: `{self._event[ResultData.RESULT_NAME]}`
82
+ Value: `{self._event[ResultData.RESULT_VALUE]}`
83
+ Status: `{self._event[ResultData.RESULT_STATUS]}`
84
+ Extra data: `{self._event[ResultData.RESULT_EXTRA_DATA]}`\
92
85
  """
93
86
 
94
87
  def notify(self) -> None:
@@ -103,7 +96,7 @@ Extra data: `{self._event[WriterEvent.RESULT_EXTRA_DATA]}`\
103
96
 
104
97
  class ModelMonitoringWriter(StepToDict):
105
98
  """
106
- Write monitoring app events to V3IO KV storage
99
+ Write monitoring application results to the target databases
107
100
  """
108
101
 
109
102
  kind = "monitoring_application_stream_pusher"
@@ -111,128 +104,97 @@ class ModelMonitoringWriter(StepToDict):
111
104
  def __init__(self, project: str) -> None:
112
105
  self.project = project
113
106
  self.name = project # required for the deployment process
114
- self._v3io_container = self.get_v3io_container(self.name)
115
- self._tsdb_client = self._get_v3io_frames_client(self._v3io_container)
107
+
116
108
  self._custom_notifier = CustomNotificationPusher(
117
109
  notification_types=[NotificationKind.slack]
118
110
  )
119
- self._create_tsdb_table()
120
- self._endpoints_records = {}
121
-
122
- @staticmethod
123
- def get_v3io_container(project_name: str) -> str:
124
- return f"users/pipelines/{project_name}/monitoring-apps"
125
-
126
- @staticmethod
127
- def _get_v3io_client() -> V3IOClient:
128
- return mlrun.utils.v3io_clients.get_v3io_client(
129
- endpoint=mlrun.mlconf.v3io_api,
130
- )
131
-
132
- @staticmethod
133
- def _get_v3io_frames_client(v3io_container: str) -> V3IOFramesClient:
134
- return mlrun.utils.v3io_clients.get_frames_client(
135
- address=mlrun.mlconf.v3io_framesd,
136
- container=v3io_container,
137
- )
138
-
139
- def _create_tsdb_table(self) -> None:
140
- self._tsdb_client.create(
141
- backend=_TSDB_BE,
142
- table=_TSDB_TABLE,
143
- if_exists=IGNORE,
144
- rate=_TSDB_RATE,
145
- )
146
111
 
147
- def _update_kv_db(self, event: _AppResultEvent) -> None:
148
- event = _AppResultEvent(event.copy())
149
- application_result_store = mlrun.model_monitoring.get_store_object(
112
+ self._app_result_store = mlrun.model_monitoring.get_store_object(
150
113
  project=self.project
151
114
  )
152
- application_result_store.write_application_result(event=event)
153
-
154
- def _update_tsdb(self, event: _AppResultEvent) -> None:
155
- event = _AppResultEvent(event.copy())
156
- event[WriterEvent.END_INFER_TIME] = datetime.datetime.fromisoformat(
157
- event[WriterEvent.END_INFER_TIME]
115
+ self._tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
116
+ project=self.project,
158
117
  )
159
- del event[WriterEvent.RESULT_EXTRA_DATA]
160
- try:
161
- self._tsdb_client.write(
162
- backend=_TSDB_BE,
163
- table=_TSDB_TABLE,
164
- dfs=pd.DataFrame.from_records([event]),
165
- index_cols=[
166
- WriterEvent.END_INFER_TIME,
167
- WriterEvent.ENDPOINT_ID,
168
- WriterEvent.APPLICATION_NAME,
169
- WriterEvent.RESULT_NAME,
170
- ],
171
- )
172
- logger.info("Updated V3IO TSDB successfully", table=_TSDB_TABLE)
173
- except V3IOFramesError as err:
174
- logger.warn(
175
- "Could not write drift measures to TSDB",
176
- err=err,
177
- table=_TSDB_TABLE,
178
- event=event,
179
- )
118
+ self._endpoints_records = {}
180
119
 
181
120
  @staticmethod
182
121
  def _generate_event_on_drift(
183
- uid: str, drift_status: str, event_value: dict, project_name: str
184
- ):
185
- if (
186
- drift_status == ResultStatusApp.detected
187
- or drift_status == ResultStatusApp.potential_detection
188
- ):
189
- entity = {
190
- "kind": alert_constants.EventEntityKind.MODEL,
191
- "project": project_name,
192
- "id": uid,
193
- }
194
- event_kind = (
195
- alert_constants.EventKind.DRIFT_DETECTED
196
- if drift_status == ResultStatusApp.detected
197
- else alert_constants.EventKind.DRIFT_SUSPECTED
198
- )
199
- event_data = mlrun.common.schemas.Event(
200
- kind=event_kind, entity=entity, value_dict=event_value
201
- )
202
- mlrun.get_run_db().generate_event(event_kind, event_data)
122
+ model_endpoint: str, drift_status: str, event_value: dict, project_name: str
123
+ ) -> None:
124
+ logger.info("Sending an alert")
125
+ entity = mlrun.common.schemas.alert.EventEntities(
126
+ kind=alert_constants.EventEntityKind.MODEL,
127
+ project=project_name,
128
+ ids=[model_endpoint],
129
+ )
130
+ event_kind = (
131
+ alert_constants.EventKind.DRIFT_DETECTED
132
+ if drift_status == ResultStatusApp.detected.value
133
+ else alert_constants.EventKind.DRIFT_SUSPECTED
134
+ )
135
+ event_data = mlrun.common.schemas.Event(
136
+ kind=event_kind, entity=entity, value_dict=event_value
137
+ )
138
+ mlrun.get_run_db().generate_event(event_kind, event_data)
203
139
 
204
140
  @staticmethod
205
- def _reconstruct_event(event: _RawEvent) -> _AppResultEvent:
141
+ def _reconstruct_event(event: _RawEvent) -> tuple[_AppResultEvent, str]:
206
142
  """
207
143
  Modify the raw event into the expected monitoring application event
208
144
  schema as defined in `mlrun.common.schemas.model_monitoring.constants.WriterEvent`
209
145
  """
210
- try:
211
- result_event = _AppResultEvent(
212
- {key: event[key] for key in WriterEvent.list()}
146
+ if not isinstance(event, dict):
147
+ raise _WriterEventTypeError(
148
+ f"The event is of type: {type(event)}, expected a dictionary"
213
149
  )
214
- result_event[WriterEvent.CURRENT_STATS] = json.loads(
215
- event[WriterEvent.CURRENT_STATS]
150
+ kind = event.pop(WriterEvent.EVENT_KIND, WriterEventKind.RESULT)
151
+ result_event = _AppResultEvent(json.loads(event.pop(WriterEvent.DATA, "{}")))
152
+ if not result_event: # BC for < 1.7.0, can be removed in 1.9.0
153
+ result_event = _AppResultEvent(event)
154
+ else:
155
+ result_event.update(_AppResultEvent(event))
156
+
157
+ expected_keys = list(
158
+ set(WriterEvent.list()).difference(
159
+ [WriterEvent.EVENT_KIND, WriterEvent.DATA]
216
160
  )
217
- return result_event
218
- except KeyError as err:
161
+ )
162
+ if kind == WriterEventKind.METRIC:
163
+ expected_keys.extend(MetricData.list())
164
+ elif kind == WriterEventKind.RESULT:
165
+ expected_keys.extend(ResultData.list())
166
+ else:
219
167
  raise _WriterEventValueError(
220
- "The received event misses some keys compared to the expected "
221
- "monitoring application event schema"
222
- ) from err
223
- except TypeError as err:
224
- raise _WriterEventTypeError(
225
- f"The event is of type: {type(event)}, expected a dictionary"
226
- ) from err
168
+ f"Unknown event kind: {kind}, expected one of: {WriterEventKind.list()}"
169
+ )
170
+ missing_keys = [key for key in expected_keys if key not in result_event]
171
+ if missing_keys:
172
+ raise _WriterEventValueError(
173
+ f"The received event misses some keys compared to the expected "
174
+ f"monitoring application event schema: {missing_keys}"
175
+ )
176
+
177
+ return result_event, kind
227
178
 
228
179
  def do(self, event: _RawEvent) -> None:
229
- event = self._reconstruct_event(event)
180
+ event, kind = self._reconstruct_event(event)
230
181
  logger.info("Starting to write event", event=event)
231
- self._update_tsdb(event)
232
- self._update_kv_db(event)
182
+
183
+ self._tsdb_connector.write_application_event(event=event.copy(), kind=kind)
184
+ self._app_result_store.write_application_event(event=event.copy(), kind=kind)
185
+ logger.info("Completed event DB writes")
186
+
233
187
  _Notifier(event=event, notification_pusher=self._custom_notifier).notify()
234
188
 
235
- if mlrun.mlconf.alerts.mode == mlrun.common.schemas.alert.AlertsModes.enabled:
189
+ if (
190
+ mlrun.mlconf.alerts.mode == mlrun.common.schemas.alert.AlertsModes.enabled
191
+ and kind == WriterEventKind.RESULT
192
+ and (
193
+ event[ResultData.RESULT_STATUS] == ResultStatusApp.detected.value
194
+ or event[ResultData.RESULT_STATUS]
195
+ == ResultStatusApp.potential_detection.value
196
+ )
197
+ ):
236
198
  endpoint_id = event[WriterEvent.ENDPOINT_ID]
237
199
  endpoint_record = self._endpoints_records.setdefault(
238
200
  endpoint_id,
@@ -242,13 +204,31 @@ class ModelMonitoringWriter(StepToDict):
242
204
  "app_name": event[WriterEvent.APPLICATION_NAME],
243
205
  "model": endpoint_record.get(EventFieldType.MODEL),
244
206
  "model_endpoint_id": event[WriterEvent.ENDPOINT_ID],
245
- "result_name": event[WriterEvent.RESULT_NAME],
246
- "result_value": event[WriterEvent.RESULT_VALUE],
207
+ "result_name": event[ResultData.RESULT_NAME],
208
+ "result_value": event[ResultData.RESULT_VALUE],
247
209
  }
248
210
  self._generate_event_on_drift(
249
211
  event[WriterEvent.ENDPOINT_ID],
250
- event[WriterEvent.RESULT_STATUS],
212
+ event[ResultData.RESULT_STATUS],
251
213
  event_value,
252
214
  self.project,
253
215
  )
254
- logger.info("Completed event DB writes")
216
+
217
+ if (
218
+ kind == WriterEventKind.RESULT
219
+ and event[WriterEvent.APPLICATION_NAME]
220
+ == HistogramDataDriftApplicationConstants.NAME
221
+ and event[ResultData.RESULT_NAME]
222
+ == HistogramDataDriftApplicationConstants.GENERAL_RESULT_NAME
223
+ ):
224
+ endpoint_id = event[WriterEvent.ENDPOINT_ID]
225
+ logger.info(
226
+ "Updating the model endpoint with metadata specific to the histogram "
227
+ "data drift app",
228
+ endpoint_id=endpoint_id,
229
+ )
230
+ store = mlrun.model_monitoring.get_store_object(project=self.project)
231
+ store.update_model_endpoint(
232
+ endpoint_id=endpoint_id,
233
+ attributes=json.loads(event[ResultData.RESULT_EXTRA_DATA]),
234
+ )
@@ -17,22 +17,23 @@ import json
17
17
  from pprint import pprint
18
18
  from time import sleep
19
19
 
20
- from .iguazio import (
21
- V3ioStreamClient,
22
- VolumeMount,
23
- add_or_refresh_credentials,
24
- is_iguazio_session_cookie,
25
- mount_v3io,
26
- v3io_cred,
27
- )
28
- from .other import (
20
+ from mlrun_pipelines.common.mounts import VolumeMount
21
+ from mlrun_pipelines.mounts import (
29
22
  auto_mount,
30
23
  mount_configmap,
31
24
  mount_hostpath,
32
25
  mount_pvc,
33
26
  mount_s3,
34
27
  mount_secret,
28
+ mount_v3io,
35
29
  set_env_variables,
30
+ v3io_cred,
31
+ )
32
+
33
+ from .iguazio import (
34
+ V3ioStreamClient,
35
+ add_or_refresh_credentials,
36
+ is_iguazio_session_cookie,
36
37
  )
37
38
 
38
39
 
@@ -15,12 +15,9 @@
15
15
  import json
16
16
  import os
17
17
  import urllib
18
- from collections import namedtuple
19
18
  from urllib.parse import urlparse
20
19
 
21
- import kfp.dsl
22
20
  import requests
23
- import semver
24
21
  import v3io
25
22
 
26
23
  import mlrun.errors
@@ -29,203 +26,6 @@ from mlrun.utils import dict_to_json
29
26
 
30
27
  _cached_control_session = None
31
28
 
32
- VolumeMount = namedtuple("Mount", ["path", "sub_path"])
33
-
34
-
35
- def mount_v3io(
36
- name="v3io",
37
- remote="",
38
- access_key="",
39
- user="",
40
- secret=None,
41
- volume_mounts=None,
42
- ):
43
- """Modifier function to apply to a Container Op to volume mount a v3io path
44
-
45
- :param name: the volume name
46
- :param remote: the v3io path to use for the volume. ~/ prefix will be replaced with /users/<username>/
47
- :param access_key: the access key used to auth against v3io. if not given V3IO_ACCESS_KEY env var will be used
48
- :param user: the username used to auth against v3io. if not given V3IO_USERNAME env var will be used
49
- :param secret: k8s secret name which would be used to get the username and access key to auth against v3io.
50
- :param volume_mounts: list of VolumeMount. empty volume mounts & remote will default to mount /v3io & /User.
51
- """
52
- volume_mounts, user = _enrich_and_validate_v3io_mounts(
53
- remote=remote,
54
- volume_mounts=volume_mounts,
55
- user=user,
56
- )
57
-
58
- def _attach_volume_mounts_and_creds(container_op: kfp.dsl.ContainerOp):
59
- from kubernetes import client as k8s_client
60
-
61
- vol = v3io_to_vol(name, remote, access_key, user, secret=secret)
62
- container_op.add_volume(vol)
63
- for volume_mount in volume_mounts:
64
- container_op.container.add_volume_mount(
65
- k8s_client.V1VolumeMount(
66
- mount_path=volume_mount.path,
67
- sub_path=volume_mount.sub_path,
68
- name=name,
69
- )
70
- )
71
-
72
- if not secret:
73
- container_op = v3io_cred(access_key=access_key, user=user)(container_op)
74
- return container_op
75
-
76
- return _attach_volume_mounts_and_creds
77
-
78
-
79
- def _enrich_and_validate_v3io_mounts(remote="", volume_mounts=None, user=""):
80
- if remote and not volume_mounts:
81
- raise mlrun.errors.MLRunInvalidArgumentError(
82
- "volume_mounts must be specified when remote is given"
83
- )
84
-
85
- # Empty remote & volume_mounts defaults are volume mounts of /v3io and /User
86
- if not remote and not volume_mounts:
87
- user = _resolve_mount_user(user)
88
- if not user:
89
- raise mlrun.errors.MLRunInvalidArgumentError(
90
- "user name/env must be specified when using empty remote and volume_mounts"
91
- )
92
- volume_mounts = [
93
- VolumeMount(path="/v3io", sub_path=""),
94
- VolumeMount(path="/User", sub_path="users/" + user),
95
- ]
96
-
97
- if not isinstance(volume_mounts, list) and any(
98
- [not isinstance(x, VolumeMount) for x in volume_mounts]
99
- ):
100
- raise TypeError("mounts should be a list of Mount")
101
-
102
- return volume_mounts, user
103
-
104
-
105
- def _resolve_mount_user(user=None):
106
- return user or os.environ.get("V3IO_USERNAME")
107
-
108
-
109
- def mount_spark_conf():
110
- def _mount_spark(container_op: kfp.dsl.ContainerOp):
111
- from kubernetes import client as k8s_client
112
-
113
- container_op.container.add_volume_mount(
114
- k8s_client.V1VolumeMount(
115
- name="spark-master-config", mount_path="/etc/config/spark"
116
- )
117
- )
118
- return container_op
119
-
120
- return _mount_spark
121
-
122
-
123
- def mount_v3iod(namespace, v3io_config_configmap):
124
- def _mount_v3iod(container_op: kfp.dsl.ContainerOp):
125
- from kubernetes import client as k8s_client
126
-
127
- def add_vol(name, mount_path, host_path):
128
- vol = k8s_client.V1Volume(
129
- name=name,
130
- host_path=k8s_client.V1HostPathVolumeSource(path=host_path, type=""),
131
- )
132
- container_op.add_volume(vol)
133
- container_op.container.add_volume_mount(
134
- k8s_client.V1VolumeMount(mount_path=mount_path, name=name)
135
- )
136
-
137
- # this is a legacy path for the daemon shared memory
138
- host_path = "/dev/shm/"
139
-
140
- # path to shared memory for daemon was changed in Iguazio 3.2.3-b1
141
- igz_version = mlrun.mlconf.get_parsed_igz_version()
142
- if igz_version and igz_version >= semver.VersionInfo.parse("3.2.3-b1"):
143
- host_path = "/var/run/iguazio/dayman-shm/"
144
- add_vol(name="shm", mount_path="/dev/shm", host_path=host_path + namespace)
145
-
146
- add_vol(
147
- name="v3iod-comm",
148
- mount_path="/var/run/iguazio/dayman",
149
- host_path="/var/run/iguazio/dayman/" + namespace,
150
- )
151
-
152
- vol = k8s_client.V1Volume(
153
- name="daemon-health", empty_dir=k8s_client.V1EmptyDirVolumeSource()
154
- )
155
- container_op.add_volume(vol)
156
- container_op.container.add_volume_mount(
157
- k8s_client.V1VolumeMount(
158
- mount_path="/var/run/iguazio/daemon_health", name="daemon-health"
159
- )
160
- )
161
-
162
- vol = k8s_client.V1Volume(
163
- name="v3io-config",
164
- config_map=k8s_client.V1ConfigMapVolumeSource(
165
- name=v3io_config_configmap, default_mode=420
166
- ),
167
- )
168
- container_op.add_volume(vol)
169
- container_op.container.add_volume_mount(
170
- k8s_client.V1VolumeMount(mount_path="/etc/config/v3io", name="v3io-config")
171
- )
172
-
173
- container_op.container.add_env_variable(
174
- k8s_client.V1EnvVar(
175
- name="CURRENT_NODE_IP",
176
- value_from=k8s_client.V1EnvVarSource(
177
- field_ref=k8s_client.V1ObjectFieldSelector(
178
- api_version="v1", field_path="status.hostIP"
179
- )
180
- ),
181
- )
182
- )
183
- container_op.container.add_env_variable(
184
- k8s_client.V1EnvVar(
185
- name="IGZ_DATA_CONFIG_FILE", value="/igz/java/conf/v3io.conf"
186
- )
187
- )
188
-
189
- return container_op
190
-
191
- return _mount_v3iod
192
-
193
-
194
- def v3io_cred(api="", user="", access_key=""):
195
- """
196
- Modifier function to copy local v3io env vars to container
197
-
198
- Usage::
199
-
200
- train = train_op(...)
201
- train.apply(use_v3io_cred())
202
- """
203
-
204
- def _use_v3io_cred(container_op: kfp.dsl.ContainerOp):
205
- from os import environ
206
-
207
- from kubernetes import client as k8s_client
208
-
209
- web_api = api or environ.get("V3IO_API") or mlconf.v3io_api
210
- _user = user or environ.get("V3IO_USERNAME")
211
- _access_key = access_key or environ.get("V3IO_ACCESS_KEY")
212
- v3io_framesd = mlconf.v3io_framesd or environ.get("V3IO_FRAMESD")
213
-
214
- return (
215
- container_op.container.add_env_variable(
216
- k8s_client.V1EnvVar(name="V3IO_API", value=web_api)
217
- )
218
- .add_env_variable(k8s_client.V1EnvVar(name="V3IO_USERNAME", value=_user))
219
- .add_env_variable(
220
- k8s_client.V1EnvVar(name="V3IO_ACCESS_KEY", value=_access_key)
221
- )
222
- .add_env_variable(
223
- k8s_client.V1EnvVar(name="V3IO_FRAMESD", value=v3io_framesd)
224
- )
225
- )
226
-
227
- return _use_v3io_cred
228
-
229
29
 
230
30
  def split_path(mntpath=""):
231
31
  if mntpath[0] == "/":
@@ -525,8 +325,8 @@ def add_or_refresh_credentials(
525
325
  # different access keys for the 2 usages
526
326
  token = (
527
327
  token
528
- # can't use mlrun.runtimes.constants.FunctionEnvironmentVariables.auth_session cause this is running in the
529
- # import execution path (when we're initializing the run db) and therefore we can't import mlrun.runtimes
328
+ # can't use mlrun.common.runtimes.constants.FunctionEnvironmentVariables.auth_session cause this is running
329
+ # in the import execution path (when we're initializing the run db) and therefore we can't import mlrun.runtimes
530
330
  or os.environ.get("MLRUN_AUTH_SESSION")
531
331
  or os.environ.get("V3IO_ACCESS_KEY")
532
332
  )
@@ -582,3 +382,22 @@ def sanitize_username(username: str):
582
382
  So simply replace it with dash
583
383
  """
584
384
  return username.replace("_", "-")
385
+
386
+
387
+ def min_iguazio_versions(*versions):
388
+ def decorator(function):
389
+ def wrapper(*args, **kwargs):
390
+ if mlrun.utils.helpers.validate_component_version_compatibility(
391
+ "iguazio", *versions
392
+ ):
393
+ return function(*args, **kwargs)
394
+
395
+ message = (
396
+ f"{function.__name__} is supported since Iguazio {' or '.join(versions)}, currently using "
397
+ f"Iguazio {mlconf.igz_version}."
398
+ )
399
+ raise mlrun.errors.MLRunIncompatibleVersionError(message)
400
+
401
+ return wrapper
402
+
403
+ return decorator
@@ -15,7 +15,7 @@
15
15
  import warnings
16
16
  from typing import Optional, Union
17
17
 
18
- import kfp
18
+ from mlrun_pipelines.models import PipelineNodeWrapper
19
19
 
20
20
  import mlrun
21
21
  from mlrun.utils import hub_prefix
@@ -76,7 +76,7 @@ def run_function(
76
76
  notifications: list[mlrun.model.Notification] = None,
77
77
  returns: Optional[list[Union[str, dict[str, str]]]] = None,
78
78
  builder_env: Optional[list] = None,
79
- ) -> Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
79
+ ) -> Union[mlrun.model.RunObject, PipelineNodeWrapper]:
80
80
  """Run a local or remote task as part of a local/kubeflow pipeline
81
81
 
82
82
  run_function() allow you to execute a function locally, on a remote cluster, or as part of an automated workflow
@@ -86,7 +86,7 @@ def run_function(
86
86
  when functions run as part of a workflow/pipeline (project.run()) some attributes can be set at the run level,
87
87
  e.g. local=True will run all the functions locally, setting artifact_path will direct all outputs to the same path.
88
88
  project runs provide additional notifications/reporting and exception handling.
89
- inside a Kubeflow pipeline (KFP) run_function() generates KFP "ContainerOps" which are used to form a DAG
89
+ inside a Kubeflow pipeline (KFP) run_function() generates KFP node (see PipelineNodeWrapper) which forms a DAG
90
90
  some behavior may differ between regular runs and deferred KFP runs.
91
91
 
92
92
  example (use with function object)::
@@ -166,7 +166,7 @@ def run_function(
166
166
  artifact type can be given there. The artifact key must appear in the dictionary as
167
167
  "key": "the_key".
168
168
  :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
169
- :return: MLRun RunObject or KubeFlow containerOp
169
+ :return: MLRun RunObject or PipelineNodeWrapper
170
170
  """
171
171
  engine, function = _get_engine_and_function(function, project_object)
172
172
  task = mlrun.new_task(
@@ -254,7 +254,7 @@ def build_function(
254
254
  overwrite_build_params: bool = False,
255
255
  extra_args: str = None,
256
256
  force_build: bool = False,
257
- ) -> Union[BuildStatus, kfp.dsl.ContainerOp]:
257
+ ) -> Union[BuildStatus, PipelineNodeWrapper]:
258
258
  """deploy ML function, build container with its dependencies
259
259
 
260
260
  :param function: Name of the function (in the project) or function object
@@ -294,7 +294,11 @@ def build_function(
294
294
  if overwrite_build_params:
295
295
  function.spec.build.commands = None
296
296
  if requirements or requirements_file:
297
- function.with_requirements(requirements, requirements_file, overwrite=True)
297
+ function.with_requirements(
298
+ requirements=requirements,
299
+ requirements_file=requirements_file,
300
+ overwrite=True,
301
+ )
298
302
  if commands:
299
303
  function.with_commands(commands)
300
304
  return function.deploy_step(
@@ -358,7 +362,7 @@ def deploy_function(
358
362
  builder_env: dict = None,
359
363
  project_object=None,
360
364
  mock: bool = None,
361
- ) -> Union[DeployStatus, kfp.dsl.ContainerOp]:
365
+ ) -> Union[DeployStatus, PipelineNodeWrapper]:
362
366
  """deploy real-time (nuclio based) functions
363
367
 
364
368
  :param function: name of the function (in the project) or function object