mlrun 1.8.0rc45__py3-none-any.whl → 1.8.0rc47__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.
- mlrun/alerts/alert.py +1 -1
- mlrun/common/schemas/model_monitoring/constants.py +5 -0
- mlrun/config.py +2 -0
- mlrun/data_types/__init__.py +5 -1
- mlrun/datastore/targets.py +7 -5
- mlrun/model_monitoring/api.py +31 -18
- mlrun/model_monitoring/applications/context.py +14 -1
- mlrun/model_monitoring/applications/evidently/base.py +38 -0
- mlrun/model_monitoring/controller.py +208 -84
- mlrun/model_monitoring/db/_schedules.py +110 -32
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +6 -1
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +11 -5
- mlrun/model_monitoring/helpers.py +46 -53
- mlrun/projects/project.py +29 -24
- mlrun/runtimes/function_reference.py +3 -0
- mlrun/runtimes/nuclio/function.py +48 -0
- mlrun/runtimes/nuclio/serving.py +16 -1
- mlrun/serving/states.py +48 -27
- mlrun/serving/v2_serving.py +51 -1
- mlrun/utils/helpers.py +5 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/METADATA +5 -5
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/RECORD +27 -27
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/WHEEL +1 -1
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/entry_points.txt +0 -0
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.8.0rc45.dist-info → mlrun-1.8.0rc47.dist-info}/top_level.txt +0 -0
|
@@ -13,52 +13,38 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
16
17
|
from contextlib import AbstractContextManager
|
|
17
18
|
from types import TracebackType
|
|
18
19
|
from typing import Final, Optional
|
|
19
20
|
|
|
20
21
|
import botocore.exceptions
|
|
21
22
|
|
|
22
|
-
import mlrun.common.schemas
|
|
23
|
+
import mlrun.common.schemas as schemas
|
|
23
24
|
import mlrun.errors
|
|
24
25
|
import mlrun.model_monitoring.helpers
|
|
25
26
|
from mlrun.utils import logger
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
class
|
|
29
|
+
class ModelMonitoringSchedulesFileBase(AbstractContextManager, ABC):
|
|
29
30
|
DEFAULT_SCHEDULES: Final = {}
|
|
30
31
|
INITIAL_CONTENT = json.dumps(DEFAULT_SCHEDULES)
|
|
31
32
|
ENCODING = "utf-8"
|
|
32
33
|
|
|
33
|
-
def __init__(self
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self._fs = self._item.store.filesystem
|
|
48
|
-
# `self._schedules` is an in-memory copy of the DB for all the applications for
|
|
49
|
-
# the same model endpoint.
|
|
50
|
-
self._schedules: dict[str, int] = self.DEFAULT_SCHEDULES.copy()
|
|
51
|
-
# Does `self._schedules` hold the content of `self._item`?
|
|
52
|
-
self._open_schedules = False
|
|
53
|
-
|
|
54
|
-
@classmethod
|
|
55
|
-
def from_model_endpoint(
|
|
56
|
-
cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
|
|
57
|
-
) -> "ModelMonitoringSchedulesFile":
|
|
58
|
-
return cls(
|
|
59
|
-
project=model_endpoint.metadata.project,
|
|
60
|
-
endpoint_id=model_endpoint.metadata.uid,
|
|
61
|
-
)
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self._item = self.get_data_item_object()
|
|
36
|
+
if self._item:
|
|
37
|
+
self._path = self._item.url
|
|
38
|
+
self._fs = self._item.store.filesystem
|
|
39
|
+
# `self._schedules` is an in-memory copy of the DB for all the applications for
|
|
40
|
+
# the same model endpoint.
|
|
41
|
+
self._schedules = self.DEFAULT_SCHEDULES.copy()
|
|
42
|
+
# Does `self._schedules` hold the content of `self._item`?
|
|
43
|
+
self._open_schedules = False
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
47
|
+
pass
|
|
62
48
|
|
|
63
49
|
def create(self) -> None:
|
|
64
50
|
"""Create a schedules file with initial content - an empty dictionary"""
|
|
@@ -114,7 +100,7 @@ class ModelMonitoringSchedulesFile(AbstractContextManager):
|
|
|
114
100
|
self._schedules = self.DEFAULT_SCHEDULES
|
|
115
101
|
self._open_schedules = False
|
|
116
102
|
|
|
117
|
-
def __enter__(self) -> "
|
|
103
|
+
def __enter__(self) -> "ModelMonitoringSchedulesFileBase":
|
|
118
104
|
self._open()
|
|
119
105
|
return super().__enter__()
|
|
120
106
|
|
|
@@ -132,6 +118,36 @@ class ModelMonitoringSchedulesFile(AbstractContextManager):
|
|
|
132
118
|
"Open the schedules file as a context manager first"
|
|
133
119
|
)
|
|
134
120
|
|
|
121
|
+
|
|
122
|
+
class ModelMonitoringSchedulesFileEndpoint(ModelMonitoringSchedulesFileBase):
|
|
123
|
+
def __init__(self, project: str, endpoint_id: str) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Initialize applications monitoring schedules file object.
|
|
126
|
+
The JSON file stores a dictionary of registered application name as key and Unix timestamp as value.
|
|
127
|
+
When working with the schedules data, use this class as a context manager to read and write the data.
|
|
128
|
+
|
|
129
|
+
:param project: The project name.
|
|
130
|
+
:param endpoint_id: The endpoint ID.
|
|
131
|
+
"""
|
|
132
|
+
# `self._item` is the persistent version of the monitoring schedules.
|
|
133
|
+
self._project = project
|
|
134
|
+
self._endpoint_id = endpoint_id
|
|
135
|
+
super().__init__()
|
|
136
|
+
|
|
137
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
138
|
+
return mlrun.model_monitoring.helpers.get_monitoring_schedules_endpoint_data(
|
|
139
|
+
project=self._project, endpoint_id=self._endpoint_id
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_model_endpoint(
|
|
144
|
+
cls, model_endpoint: schemas.ModelEndpoint
|
|
145
|
+
) -> "ModelMonitoringSchedulesFileEndpoint":
|
|
146
|
+
return cls(
|
|
147
|
+
project=model_endpoint.metadata.project,
|
|
148
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
149
|
+
)
|
|
150
|
+
|
|
135
151
|
def get_application_time(self, application: str) -> Optional[int]:
|
|
136
152
|
self._check_open_schedules()
|
|
137
153
|
return self._schedules.get(application)
|
|
@@ -149,6 +165,68 @@ class ModelMonitoringSchedulesFile(AbstractContextManager):
|
|
|
149
165
|
return min(self._schedules.values(), default=None)
|
|
150
166
|
|
|
151
167
|
|
|
168
|
+
class ModelMonitoringSchedulesFileChief(ModelMonitoringSchedulesFileBase):
|
|
169
|
+
def __init__(self, project: str) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Initialize applications monitoring schedules chief file object.
|
|
172
|
+
The JSON file stores a dictionary of registered model endpoints uid as key and point to a dictionary of
|
|
173
|
+
"last_request" and "last_analyzed" mapped to two Unix timestamps as values.
|
|
174
|
+
When working with the schedules data, use this class as a context manager to read and write the data.
|
|
175
|
+
|
|
176
|
+
:param project: The project name.
|
|
177
|
+
"""
|
|
178
|
+
# `self._item` is the persistent version of the monitoring schedules.
|
|
179
|
+
self._project = project
|
|
180
|
+
super().__init__()
|
|
181
|
+
|
|
182
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
183
|
+
return mlrun.model_monitoring.helpers.get_monitoring_schedules_chief_data(
|
|
184
|
+
project=self._project
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def get_endpoint_last_request(self, endpoint_uid: str) -> Optional[int]:
|
|
188
|
+
self._check_open_schedules()
|
|
189
|
+
if endpoint_uid in self._schedules:
|
|
190
|
+
return self._schedules[endpoint_uid].get(
|
|
191
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def update_endpoint_timestamps(
|
|
197
|
+
self, endpoint_uid: str, last_request: int, last_analyzed: int
|
|
198
|
+
) -> None:
|
|
199
|
+
self._check_open_schedules()
|
|
200
|
+
self._schedules[endpoint_uid] = {
|
|
201
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST: last_request,
|
|
202
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED: last_analyzed,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def get_endpoint_last_analyzed(self, endpoint_uid: str) -> Optional[int]:
|
|
206
|
+
self._check_open_schedules()
|
|
207
|
+
if endpoint_uid in self._schedules:
|
|
208
|
+
return self._schedules[endpoint_uid].get(
|
|
209
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def get_endpoint_list(self) -> set[str]:
|
|
215
|
+
self._check_open_schedules()
|
|
216
|
+
return set(self._schedules.keys())
|
|
217
|
+
|
|
218
|
+
def get_or_create(self) -> None:
|
|
219
|
+
try:
|
|
220
|
+
self._open()
|
|
221
|
+
except (
|
|
222
|
+
mlrun.errors.MLRunNotFoundError,
|
|
223
|
+
# Different errors are raised for S3 or local storage, see ML-8042
|
|
224
|
+
botocore.exceptions.ClientError,
|
|
225
|
+
FileNotFoundError,
|
|
226
|
+
):
|
|
227
|
+
self.create()
|
|
228
|
+
|
|
229
|
+
|
|
152
230
|
def delete_model_monitoring_schedules_folder(project: str) -> None:
|
|
153
231
|
"""Delete the model monitoring schedules folder of the project"""
|
|
154
232
|
folder = mlrun.model_monitoring.helpers._get_monitoring_schedules_folder_path(
|
|
@@ -55,6 +55,9 @@ class TDEngineConnector(TSDBConnector):
|
|
|
55
55
|
|
|
56
56
|
self._init_super_tables()
|
|
57
57
|
|
|
58
|
+
self._run_directly = (
|
|
59
|
+
mlrun.mlconf.model_endpoint_monitoring.tdengine.run_directly
|
|
60
|
+
)
|
|
58
61
|
self._timeout = mlrun.mlconf.model_endpoint_monitoring.tdengine.timeout
|
|
59
62
|
self._retries = mlrun.mlconf.model_endpoint_monitoring.tdengine.retries
|
|
60
63
|
|
|
@@ -74,7 +77,9 @@ class TDEngineConnector(TSDBConnector):
|
|
|
74
77
|
def _create_connection(self) -> TDEngineConnection:
|
|
75
78
|
"""Establish a connection to the TSDB server."""
|
|
76
79
|
logger.debug("Creating a new connection to TDEngine", project=self.project)
|
|
77
|
-
conn = TDEngineConnection(
|
|
80
|
+
conn = TDEngineConnection(
|
|
81
|
+
self._tdengine_connection_profile.dsn(), run_directly=self._run_directly
|
|
82
|
+
)
|
|
78
83
|
conn.prefix_statements = [f"USE {self.database}"]
|
|
79
84
|
|
|
80
85
|
return conn
|
|
@@ -1090,9 +1090,9 @@ class V3IOTSDBConnector(TSDBConnector):
|
|
|
1090
1090
|
Fetch basic metrics from V3IO TSDB and add them to MEP objects.
|
|
1091
1091
|
|
|
1092
1092
|
:param model_endpoint_objects: A list of `ModelEndpoint` objects that will
|
|
1093
|
-
|
|
1093
|
+
be filled with the relevant basic metrics.
|
|
1094
1094
|
:param project: The name of the project.
|
|
1095
|
-
:param run_in_threadpool:
|
|
1095
|
+
:param run_in_threadpool: A function that runs another function in a thread pool.
|
|
1096
1096
|
|
|
1097
1097
|
:return: A list of `ModelEndpointMonitoringMetric` objects.
|
|
1098
1098
|
"""
|
|
@@ -1104,9 +1104,15 @@ class V3IOTSDBConnector(TSDBConnector):
|
|
|
1104
1104
|
uids.append(uid)
|
|
1105
1105
|
model_endpoint_objects_by_uid[uid] = model_endpoint_object
|
|
1106
1106
|
|
|
1107
|
-
error_count_res =
|
|
1108
|
-
|
|
1109
|
-
|
|
1107
|
+
error_count_res = await run_in_threadpool(
|
|
1108
|
+
self.get_error_count, endpoint_ids=uids, get_raw=True
|
|
1109
|
+
)
|
|
1110
|
+
avg_latency_res = await run_in_threadpool(
|
|
1111
|
+
self.get_avg_latency, endpoint_ids=uids, get_raw=True
|
|
1112
|
+
)
|
|
1113
|
+
drift_status_res = await run_in_threadpool(
|
|
1114
|
+
self.get_drift_status, endpoint_ids=uids, get_raw=True
|
|
1115
|
+
)
|
|
1110
1116
|
|
|
1111
1117
|
def add_metric(
|
|
1112
1118
|
metric: str,
|
|
@@ -432,57 +432,23 @@ def update_model_endpoint_last_request(
|
|
|
432
432
|
:param current_request: current request time
|
|
433
433
|
:param db: DB interface.
|
|
434
434
|
"""
|
|
435
|
-
is_batch_endpoint = (
|
|
436
|
-
model_endpoint.metadata.endpoint_type == mm_constants.EndpointType.BATCH_EP
|
|
437
|
-
)
|
|
438
|
-
if not is_batch_endpoint:
|
|
439
|
-
logger.info(
|
|
440
|
-
"Update model endpoint last request time (EP with serving)",
|
|
441
|
-
project=project,
|
|
442
|
-
endpoint_id=model_endpoint.metadata.uid,
|
|
443
|
-
name=model_endpoint.metadata.name,
|
|
444
|
-
function_name=model_endpoint.spec.function_name,
|
|
445
|
-
last_request=model_endpoint.status.last_request,
|
|
446
|
-
current_request=current_request,
|
|
447
|
-
)
|
|
448
|
-
db.patch_model_endpoint(
|
|
449
|
-
project=project,
|
|
450
|
-
endpoint_id=model_endpoint.metadata.uid,
|
|
451
|
-
name=model_endpoint.metadata.name,
|
|
452
|
-
attributes={mm_constants.EventFieldType.LAST_REQUEST: current_request},
|
|
453
|
-
)
|
|
454
|
-
else: # model endpoint without any serving function - close the window "manually"
|
|
455
|
-
try:
|
|
456
|
-
time_window = _get_monitoring_time_window_from_controller_run(project, db)
|
|
457
|
-
except mlrun.errors.MLRunNotFoundError:
|
|
458
|
-
logger.warn(
|
|
459
|
-
"Not bumping model endpoint last request time - the monitoring controller isn't deployed yet.\n"
|
|
460
|
-
"Call `project.enable_model_monitoring()` first."
|
|
461
|
-
)
|
|
462
|
-
return
|
|
463
435
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
project=project,
|
|
481
|
-
endpoint_id=model_endpoint.metadata.uid,
|
|
482
|
-
name=model_endpoint.metadata.name,
|
|
483
|
-
function_name=model_endpoint.spec.function_name,
|
|
484
|
-
attributes={mm_constants.EventFieldType.LAST_REQUEST: bumped_last_request},
|
|
485
|
-
)
|
|
436
|
+
logger.info(
|
|
437
|
+
"Update model endpoint last request time (EP with serving)",
|
|
438
|
+
project=project,
|
|
439
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
440
|
+
name=model_endpoint.metadata.name,
|
|
441
|
+
function_name=model_endpoint.spec.function_name,
|
|
442
|
+
last_request=model_endpoint.status.last_request,
|
|
443
|
+
current_request=current_request,
|
|
444
|
+
)
|
|
445
|
+
db.patch_model_endpoint(
|
|
446
|
+
project=project,
|
|
447
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
448
|
+
name=model_endpoint.metadata.name,
|
|
449
|
+
function_name=model_endpoint.spec.function_name,
|
|
450
|
+
attributes={mm_constants.EventFieldType.LAST_REQUEST: current_request},
|
|
451
|
+
)
|
|
486
452
|
|
|
487
453
|
|
|
488
454
|
def calculate_inputs_statistics(
|
|
@@ -586,16 +552,43 @@ def _get_monitoring_schedules_folder_path(project: str) -> str:
|
|
|
586
552
|
)
|
|
587
553
|
|
|
588
554
|
|
|
589
|
-
def
|
|
555
|
+
def _get_monitoring_schedules_file_endpoint_path(
|
|
556
|
+
*, project: str, endpoint_id: str
|
|
557
|
+
) -> str:
|
|
590
558
|
return os.path.join(
|
|
591
559
|
_get_monitoring_schedules_folder_path(project), f"{endpoint_id}.json"
|
|
592
560
|
)
|
|
593
561
|
|
|
594
562
|
|
|
595
|
-
def
|
|
563
|
+
def get_monitoring_schedules_endpoint_data(
|
|
564
|
+
*, project: str, endpoint_id: str
|
|
565
|
+
) -> "DataItem":
|
|
596
566
|
"""
|
|
597
567
|
Get the model monitoring schedules' data item of the project's model endpoint.
|
|
598
568
|
"""
|
|
599
569
|
return mlrun.datastore.store_manager.object(
|
|
600
|
-
|
|
570
|
+
_get_monitoring_schedules_file_endpoint_path(
|
|
571
|
+
project=project, endpoint_id=endpoint_id
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def get_monitoring_schedules_chief_data(
|
|
577
|
+
*,
|
|
578
|
+
project: str,
|
|
579
|
+
) -> "DataItem":
|
|
580
|
+
"""
|
|
581
|
+
Get the model monitoring schedules' data item of the project's model endpoint.
|
|
582
|
+
"""
|
|
583
|
+
return mlrun.datastore.store_manager.object(
|
|
584
|
+
_get_monitoring_schedules_file_chief_path(project=project)
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _get_monitoring_schedules_file_chief_path(
|
|
589
|
+
*,
|
|
590
|
+
project: str,
|
|
591
|
+
) -> str:
|
|
592
|
+
return os.path.join(
|
|
593
|
+
_get_monitoring_schedules_folder_path(project), f"{project}.json"
|
|
601
594
|
)
|
mlrun/projects/project.py
CHANGED
|
@@ -2144,29 +2144,34 @@ class MlrunProject(ModelObj):
|
|
|
2144
2144
|
reset_policy: mlrun.common.schemas.alert.ResetPolicy = mlrun.common.schemas.alert.ResetPolicy.AUTO,
|
|
2145
2145
|
) -> list[mlrun.alerts.alert.AlertConfig]:
|
|
2146
2146
|
"""
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2147
|
+
Generate alert configurations based on specified model endpoints and result names, which can be defined
|
|
2148
|
+
explicitly or using regex patterns.
|
|
2149
|
+
|
|
2150
|
+
:param name: The name of the AlertConfig template. It will be combined with
|
|
2151
|
+
mep id, app name and result name to generate a unique name.
|
|
2152
|
+
:param summary: Summary of the alert, will be sent in the generated notifications
|
|
2153
|
+
:param endpoints: The endpoints from which metrics will be retrieved to configure
|
|
2154
|
+
the alerts.
|
|
2155
|
+
The ModelEndpointList object is obtained via the `list_model_endpoints`
|
|
2156
|
+
method or created manually using `ModelEndpoint` objects.
|
|
2157
|
+
:param events: AlertTrigger event types (EventKind).
|
|
2158
|
+
:param notifications: List of notifications to invoke once the alert is triggered
|
|
2159
|
+
:param result_names: Optional. Filters the result names used to create the alert
|
|
2160
|
+
configuration, constructed from the app and result_name regex.
|
|
2161
|
+
|
|
2162
|
+
For example:
|
|
2163
|
+
[`app1.result-*`, `*.result1`]
|
|
2164
|
+
will match "mep_uid1.app1.result.result-1" and
|
|
2165
|
+
"mep_uid1.app2.result.result1".
|
|
2166
|
+
A specific result_name (not a wildcard) will always create a new alert
|
|
2167
|
+
config, regardless of whether the result name exists.
|
|
2168
|
+
:param severity: Severity of the alert.
|
|
2169
|
+
:param criteria: The threshold for triggering the alert based on the
|
|
2170
|
+
specified number of events within the defined time period.
|
|
2171
|
+
:param reset_policy: When to clear the alert. Either "manual" for manual reset of the alert,
|
|
2172
|
+
or "auto" if the criteria contains a time period.
|
|
2173
|
+
:returns: List of AlertConfig according to endpoints results,
|
|
2174
|
+
filtered by result_names.
|
|
2170
2175
|
"""
|
|
2171
2176
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2172
2177
|
matching_results = []
|
|
@@ -5266,7 +5271,7 @@ class MlrunProject(ModelObj):
|
|
|
5266
5271
|
)
|
|
5267
5272
|
|
|
5268
5273
|
# if engine is remote then skip the local file validation
|
|
5269
|
-
if engine and
|
|
5274
|
+
if engine and engine.startswith("remote"):
|
|
5270
5275
|
return
|
|
5271
5276
|
|
|
5272
5277
|
code_path = self.spec.get_code_path()
|
|
@@ -36,6 +36,7 @@ class FunctionReference(ModelObj):
|
|
|
36
36
|
spec=None,
|
|
37
37
|
kind=None,
|
|
38
38
|
name=None,
|
|
39
|
+
track_models=None,
|
|
39
40
|
):
|
|
40
41
|
self.url = url
|
|
41
42
|
self.kind = kind
|
|
@@ -46,6 +47,7 @@ class FunctionReference(ModelObj):
|
|
|
46
47
|
spec = spec.to_dict()
|
|
47
48
|
self.spec = spec
|
|
48
49
|
self.code = code
|
|
50
|
+
self.track_models = track_models
|
|
49
51
|
|
|
50
52
|
self._function = None
|
|
51
53
|
self._address = None
|
|
@@ -130,6 +132,7 @@ class FunctionReference(ModelObj):
|
|
|
130
132
|
if self.requirements:
|
|
131
133
|
func.with_requirements(self.requirements)
|
|
132
134
|
self._function = func
|
|
135
|
+
func.spec.track_models = self.track_models
|
|
133
136
|
return func
|
|
134
137
|
|
|
135
138
|
@property
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
16
|
+
import copy
|
|
16
17
|
import json
|
|
17
18
|
import typing
|
|
18
19
|
import warnings
|
|
@@ -50,6 +51,19 @@ from mlrun.runtimes.utils import get_item_name, log_std
|
|
|
50
51
|
from mlrun.utils import get_in, logger, update_in
|
|
51
52
|
from mlrun_pipelines.common.ops import deploy_op
|
|
52
53
|
|
|
54
|
+
SENSITIVE_PATHS_IN_TRIGGER_CONFIG = {
|
|
55
|
+
"password",
|
|
56
|
+
"secret",
|
|
57
|
+
"attributes/password",
|
|
58
|
+
"attributes/accesskeyid",
|
|
59
|
+
"attributes/secretaccesskey",
|
|
60
|
+
"attributes/cacert",
|
|
61
|
+
"attributes/accesskey",
|
|
62
|
+
"attributes/accesscertificate",
|
|
63
|
+
"attributes/sasl/password",
|
|
64
|
+
"attributes/sasl/oauth/clientsecret",
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
|
|
54
68
|
def validate_nuclio_version_compatibility(*min_versions):
|
|
55
69
|
"""
|
|
@@ -274,6 +288,37 @@ class RemoteRuntime(KubeResource):
|
|
|
274
288
|
if self.metadata.tag:
|
|
275
289
|
mlrun.utils.validate_tag_name(self.metadata.tag, "function.metadata.tag")
|
|
276
290
|
|
|
291
|
+
def mask_sensitive_data_in_config(self):
|
|
292
|
+
if not self.spec.config:
|
|
293
|
+
return {}
|
|
294
|
+
|
|
295
|
+
raw_config = copy.deepcopy(self.spec.config)
|
|
296
|
+
|
|
297
|
+
for key, value in self.spec.config.items():
|
|
298
|
+
if key.startswith("spec.triggers"):
|
|
299
|
+
trigger_name = key.split(".")[-1]
|
|
300
|
+
|
|
301
|
+
for path in SENSITIVE_PATHS_IN_TRIGGER_CONFIG:
|
|
302
|
+
# Handle nested keys
|
|
303
|
+
nested_keys = path.split("/")
|
|
304
|
+
target = value
|
|
305
|
+
for sub_key in nested_keys[:-1]:
|
|
306
|
+
target = target.get(sub_key, {})
|
|
307
|
+
|
|
308
|
+
last_key = nested_keys[-1]
|
|
309
|
+
if last_key in target:
|
|
310
|
+
sensitive_field = target[last_key]
|
|
311
|
+
if sensitive_field.startswith(
|
|
312
|
+
mlrun.model.Credentials.secret_reference_prefix
|
|
313
|
+
):
|
|
314
|
+
# already masked
|
|
315
|
+
continue
|
|
316
|
+
target[last_key] = (
|
|
317
|
+
f"{mlrun.model.Credentials.secret_reference_prefix}/spec/triggers/{trigger_name}/{path}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return raw_config
|
|
321
|
+
|
|
277
322
|
def set_config(self, key, value):
|
|
278
323
|
self.spec.config[key] = value
|
|
279
324
|
return self
|
|
@@ -1230,6 +1275,9 @@ class RemoteRuntime(KubeResource):
|
|
|
1230
1275
|
if remote_env.get("name") in credentials_env_var_names:
|
|
1231
1276
|
new_env.append(remote_env)
|
|
1232
1277
|
|
|
1278
|
+
# update nuclio-specific credentials
|
|
1279
|
+
self.mask_sensitive_data_in_config()
|
|
1280
|
+
|
|
1233
1281
|
self.spec.env = new_env
|
|
1234
1282
|
|
|
1235
1283
|
def _set_as_mock(self, enable):
|
mlrun/runtimes/nuclio/serving.py
CHANGED
|
@@ -337,6 +337,17 @@ class ServingRuntime(RemoteRuntime):
|
|
|
337
337
|
"""
|
|
338
338
|
# Applying model monitoring configurations
|
|
339
339
|
self.spec.track_models = enable_tracking
|
|
340
|
+
if self._spec and self._spec.function_refs:
|
|
341
|
+
logger.debug(
|
|
342
|
+
"Set tracking for children references", enable_tracking=enable_tracking
|
|
343
|
+
)
|
|
344
|
+
for name in self._spec.function_refs.keys():
|
|
345
|
+
self._spec.function_refs[name].track_models = enable_tracking
|
|
346
|
+
# Check if function_refs _function is filled if so update track_models field:
|
|
347
|
+
if self._spec.function_refs[name]._function:
|
|
348
|
+
self._spec.function_refs[
|
|
349
|
+
name
|
|
350
|
+
]._function.spec.track_models = enable_tracking
|
|
340
351
|
|
|
341
352
|
if not 0 < sampling_percentage <= 100:
|
|
342
353
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
@@ -506,7 +517,11 @@ class ServingRuntime(RemoteRuntime):
|
|
|
506
517
|
:return function object
|
|
507
518
|
"""
|
|
508
519
|
function_reference = FunctionReference(
|
|
509
|
-
url,
|
|
520
|
+
url,
|
|
521
|
+
image,
|
|
522
|
+
requirements=requirements,
|
|
523
|
+
kind=kind or "serving",
|
|
524
|
+
track_models=self.spec.track_models,
|
|
510
525
|
)
|
|
511
526
|
self._spec.function_refs.update(function_reference, name)
|
|
512
527
|
func = function_reference.to_function(self.kind)
|
mlrun/serving/states.py
CHANGED
|
@@ -363,15 +363,22 @@ class BaseStep(ModelObj):
|
|
|
363
363
|
event: {"x": 5} , result_path="y" means the output of the step will be written
|
|
364
364
|
to event["y"] resulting in {"x": 5, "y": <result>}
|
|
365
365
|
:param model_endpoint_creation_strategy: Strategy for creating or updating the model endpoint:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
366
|
+
|
|
367
|
+
* **overwrite**:
|
|
368
|
+
|
|
369
|
+
1. If model endpoints with the same name exist, delete the `latest` one.
|
|
370
|
+
2. Create a new model endpoint entry and set it as `latest`.
|
|
371
|
+
|
|
372
|
+
* **inplace** (default):
|
|
373
|
+
|
|
374
|
+
1. If model endpoints with the same name exist, update the `latest` entry.
|
|
375
|
+
2. Otherwise, create a new entry.
|
|
376
|
+
|
|
377
|
+
* **archive**:
|
|
378
|
+
|
|
379
|
+
1. If model endpoints with the same name exist, preserve them.
|
|
380
|
+
2. Create a new model endpoint with the same name and set it to `latest`.
|
|
381
|
+
|
|
375
382
|
:param class_args: class init arguments
|
|
376
383
|
"""
|
|
377
384
|
if hasattr(self, "steps"):
|
|
@@ -810,15 +817,22 @@ class RouterStep(TaskStep):
|
|
|
810
817
|
:param handler: class handler to invoke on run/event
|
|
811
818
|
:param function: function this step should run in
|
|
812
819
|
:param creation_strategy: Strategy for creating or updating the model endpoint:
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
820
|
+
|
|
821
|
+
* **overwrite**:
|
|
822
|
+
|
|
823
|
+
1. If model endpoints with the same name exist, delete the `latest` one.
|
|
824
|
+
2. Create a new model endpoint entry and set it as `latest`.
|
|
825
|
+
|
|
826
|
+
* **inplace** (default):
|
|
827
|
+
|
|
828
|
+
1. If model endpoints with the same name exist, update the `latest` entry.
|
|
829
|
+
2. Otherwise, create a new entry.
|
|
830
|
+
|
|
831
|
+
* **archive**:
|
|
832
|
+
|
|
833
|
+
1. If model endpoints with the same name exist, preserve them.
|
|
834
|
+
2. Create a new model endpoint with the same name and set it to `latest`.
|
|
835
|
+
|
|
822
836
|
"""
|
|
823
837
|
|
|
824
838
|
if len(self.routes.keys()) >= MAX_MODELS_PER_ROUTER and key not in self.routes:
|
|
@@ -1207,15 +1221,22 @@ class FlowStep(BaseStep):
|
|
|
1207
1221
|
event: {"x": 5} , result_path="y" means the output of the step will be written
|
|
1208
1222
|
to event["y"] resulting in {"x": 5, "y": <result>}
|
|
1209
1223
|
:param model_endpoint_creation_strategy: Strategy for creating or updating the model endpoint:
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1224
|
+
|
|
1225
|
+
* **overwrite**:
|
|
1226
|
+
|
|
1227
|
+
1. If model endpoints with the same name exist, delete the `latest` one.
|
|
1228
|
+
2. Create a new model endpoint entry and set it as `latest`.
|
|
1229
|
+
|
|
1230
|
+
* **inplace** (default):
|
|
1231
|
+
|
|
1232
|
+
1. If model endpoints with the same name exist, update the `latest` entry.
|
|
1233
|
+
2. Otherwise, create a new entry.
|
|
1234
|
+
|
|
1235
|
+
* **archive**:
|
|
1236
|
+
|
|
1237
|
+
1. If model endpoints with the same name exist, preserve them.
|
|
1238
|
+
2. Create a new model endpoint with the same name and set it to `latest`.
|
|
1239
|
+
|
|
1219
1240
|
:param class_args: class init arguments
|
|
1220
1241
|
"""
|
|
1221
1242
|
|