mlrun 1.8.0rc29__py3-none-any.whl → 1.8.0rc31__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/__init__.py +2 -34
- mlrun/api/schemas/__init__.py +1 -6
- mlrun/artifacts/document.py +3 -3
- mlrun/artifacts/manager.py +1 -0
- mlrun/artifacts/model.py +3 -3
- mlrun/common/model_monitoring/helpers.py +16 -7
- mlrun/common/runtimes/constants.py +5 -0
- mlrun/common/schemas/__init__.py +0 -2
- mlrun/common/schemas/model_monitoring/__init__.py +0 -2
- mlrun/common/schemas/model_monitoring/constants.py +4 -7
- mlrun/common/schemas/model_monitoring/grafana.py +17 -11
- mlrun/config.py +9 -36
- mlrun/datastore/datastore_profile.py +1 -1
- mlrun/datastore/sources.py +14 -13
- mlrun/datastore/storeytargets.py +20 -3
- mlrun/db/httpdb.py +4 -30
- mlrun/k8s_utils.py +2 -5
- mlrun/launcher/base.py +16 -0
- mlrun/model_monitoring/api.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +23 -37
- mlrun/model_monitoring/applications/base.py +55 -40
- mlrun/model_monitoring/applications/context.py +0 -3
- mlrun/model_monitoring/applications/results.py +16 -16
- mlrun/model_monitoring/controller.py +35 -31
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -5
- mlrun/model_monitoring/db/tsdb/base.py +60 -39
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +122 -53
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +140 -14
- mlrun/model_monitoring/helpers.py +124 -16
- mlrun/model_monitoring/stream_processing.py +6 -21
- mlrun/projects/pipelines.py +11 -3
- mlrun/projects/project.py +104 -115
- mlrun/run.py +2 -2
- mlrun/runtimes/nuclio/function.py +4 -2
- mlrun/serving/routers.py +3 -4
- mlrun/serving/server.py +10 -8
- mlrun/serving/states.py +12 -2
- mlrun/serving/v2_serving.py +25 -20
- mlrun/utils/async_http.py +32 -19
- mlrun/utils/helpers.py +5 -2
- mlrun/utils/logger.py +14 -10
- mlrun/utils/notifications/notification_pusher.py +25 -0
- mlrun/utils/regex.py +1 -0
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/METADATA +4 -4
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/RECORD +50 -50
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/LICENSE +0 -0
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/WHEEL +0 -0
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/entry_points.txt +0 -0
- {mlrun-1.8.0rc29.dist-info → mlrun-1.8.0rc31.dist-info}/top_level.txt +0 -0
mlrun/__init__.py
CHANGED
|
@@ -214,40 +214,8 @@ def set_env_from_file(env_file: str, return_dict: bool = False) -> Optional[dict
|
|
|
214
214
|
if None in env_vars.values():
|
|
215
215
|
raise MLRunInvalidArgumentError("env file lines must be in the form key=value")
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
for key, value in ordered_env_vars.items():
|
|
217
|
+
for key, value in env_vars.items():
|
|
219
218
|
environ[key] = value
|
|
220
219
|
|
|
221
220
|
mlconf.reload() # reload mlrun configuration
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def order_env_vars(env_vars: dict[str, str]) -> dict[str, str]:
|
|
226
|
-
"""
|
|
227
|
-
Order and process environment variables by first handling specific ordered keys,
|
|
228
|
-
then processing the remaining keys in the given dictionary.
|
|
229
|
-
|
|
230
|
-
The function ensures that environment variables defined in the `ordered_keys` list
|
|
231
|
-
are added to the result dictionary first. Any other environment variables from
|
|
232
|
-
`env_vars` are then added in the order they appear in the input dictionary.
|
|
233
|
-
|
|
234
|
-
:param env_vars: A dictionary where each key is the name of an environment variable (str),
|
|
235
|
-
and each value is the corresponding environment variable value (str).
|
|
236
|
-
:return: A dictionary with the processed environment variables, ordered with the specific
|
|
237
|
-
keys first, followed by the rest in their original order.
|
|
238
|
-
"""
|
|
239
|
-
ordered_keys = mlconf.get_ordered_keys()
|
|
240
|
-
|
|
241
|
-
ordered_env_vars: dict[str, str] = {}
|
|
242
|
-
|
|
243
|
-
# First, add the ordered keys to the dictionary
|
|
244
|
-
for key in ordered_keys:
|
|
245
|
-
if key in env_vars:
|
|
246
|
-
ordered_env_vars[key] = env_vars[key]
|
|
247
|
-
|
|
248
|
-
# Then, add the remaining keys (those not in ordered_keys)
|
|
249
|
-
for key, value in env_vars.items():
|
|
250
|
-
if key not in ordered_keys:
|
|
251
|
-
ordered_env_vars[key] = value
|
|
252
|
-
|
|
253
|
-
return ordered_env_vars
|
|
221
|
+
return env_vars if return_dict else None
|
mlrun/api/schemas/__init__.py
CHANGED
|
@@ -193,9 +193,7 @@ FeatureValues = DeprecationHelper(mlrun.common.schemas.FeatureValues)
|
|
|
193
193
|
GrafanaColumn = DeprecationHelper(
|
|
194
194
|
mlrun.common.schemas.model_monitoring.grafana.GrafanaColumn
|
|
195
195
|
)
|
|
196
|
-
|
|
197
|
-
mlrun.common.schemas.model_monitoring.grafana.GrafanaDataPoint
|
|
198
|
-
)
|
|
196
|
+
|
|
199
197
|
GrafanaNumberColumn = DeprecationHelper(
|
|
200
198
|
mlrun.common.schemas.model_monitoring.grafana.GrafanaNumberColumn
|
|
201
199
|
)
|
|
@@ -205,9 +203,6 @@ GrafanaStringColumn = DeprecationHelper(
|
|
|
205
203
|
GrafanaTable = DeprecationHelper(
|
|
206
204
|
mlrun.common.schemas.model_monitoring.grafana.GrafanaTable
|
|
207
205
|
)
|
|
208
|
-
GrafanaTimeSeriesTarget = DeprecationHelper(
|
|
209
|
-
mlrun.common.schemas.model_monitoring.grafana.GrafanaTimeSeriesTarget
|
|
210
|
-
)
|
|
211
206
|
ModelEndpoint = DeprecationHelper(mlrun.common.schemas.ModelEndpoint)
|
|
212
207
|
ModelEndpointList = DeprecationHelper(mlrun.common.schemas.ModelEndpointList)
|
|
213
208
|
ModelEndpointMetadata = DeprecationHelper(mlrun.common.schemas.ModelEndpointMetadata)
|
mlrun/artifacts/document.py
CHANGED
|
@@ -97,9 +97,9 @@ class MLRunLoader:
|
|
|
97
97
|
A factory class for creating instances of a dynamically defined document loader.
|
|
98
98
|
|
|
99
99
|
Args:
|
|
100
|
-
artifact_key (str, optional): The key for the artifact to be logged.
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
artifact_key (str, optional): The key for the artifact to be logged.
|
|
101
|
+
The '%%' pattern in the key will be replaced by the source path
|
|
102
|
+
with any unsupported characters converted to '_'. Defaults to "%%".
|
|
103
103
|
local_path (str): The source path of the document to be loaded.
|
|
104
104
|
loader_spec (DocumentLoaderSpec): Specification for the document loader.
|
|
105
105
|
producer (Optional[Union[MlrunProject, str, MLClientCtx]], optional): The producer of the document.
|
mlrun/artifacts/manager.py
CHANGED
mlrun/artifacts/model.py
CHANGED
|
@@ -429,6 +429,9 @@ def get_model(model_dir, suffix=""):
|
|
|
429
429
|
extra_dataitems = {}
|
|
430
430
|
default_suffix = ".pkl"
|
|
431
431
|
|
|
432
|
+
if hasattr(model_dir, "artifact_url"):
|
|
433
|
+
model_dir = model_dir.artifact_url
|
|
434
|
+
|
|
432
435
|
alternative_suffix = next(
|
|
433
436
|
(
|
|
434
437
|
optional_suffix
|
|
@@ -438,9 +441,6 @@ def get_model(model_dir, suffix=""):
|
|
|
438
441
|
None,
|
|
439
442
|
)
|
|
440
443
|
|
|
441
|
-
if hasattr(model_dir, "artifact_url"):
|
|
442
|
-
model_dir = model_dir.artifact_url
|
|
443
|
-
|
|
444
444
|
if mlrun.datastore.is_store_uri(model_dir):
|
|
445
445
|
model_spec, target = mlrun.datastore.store_manager.get_store_artifact(model_dir)
|
|
446
446
|
if not model_spec or model_spec.kind != "model":
|
|
@@ -36,6 +36,20 @@ def parse_model_endpoint_store_prefix(store_prefix: str):
|
|
|
36
36
|
return endpoint, container, path
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def get_kafka_topic(project: str, function_name: typing.Optional[str] = None) -> str:
|
|
40
|
+
if (
|
|
41
|
+
function_name is None
|
|
42
|
+
or function_name == mm_constants.MonitoringFunctionNames.STREAM
|
|
43
|
+
):
|
|
44
|
+
function_specifier = ""
|
|
45
|
+
else:
|
|
46
|
+
function_specifier = f"_{function_name}"
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
f"monitoring_stream_{mlrun.mlconf.system_id}_{project}{function_specifier}_v1"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
39
53
|
def parse_monitoring_stream_path(
|
|
40
54
|
stream_uri: str, project: str, function_name: typing.Optional[str] = None
|
|
41
55
|
) -> str:
|
|
@@ -43,13 +57,8 @@ def parse_monitoring_stream_path(
|
|
|
43
57
|
if "?topic" in stream_uri:
|
|
44
58
|
raise mlrun.errors.MLRunValueError("Custom kafka topic is not allowed")
|
|
45
59
|
# Add topic to stream kafka uri
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
or function_name == mm_constants.MonitoringFunctionNames.STREAM
|
|
49
|
-
):
|
|
50
|
-
stream_uri += f"?topic=monitoring_stream_{project}_v1"
|
|
51
|
-
else:
|
|
52
|
-
stream_uri += f"?topic=monitoring_stream_{project}_{function_name}_v1"
|
|
60
|
+
topic = get_kafka_topic(project=project, function_name=function_name)
|
|
61
|
+
stream_uri += f"?topic={topic}"
|
|
53
62
|
|
|
54
63
|
return stream_uri
|
|
55
64
|
|
|
@@ -194,6 +194,10 @@ class RunStates:
|
|
|
194
194
|
# TODO: add aborting state once we have it
|
|
195
195
|
]
|
|
196
196
|
|
|
197
|
+
@staticmethod
|
|
198
|
+
def notification_states():
|
|
199
|
+
return RunStates.terminal_states() + [RunStates.running]
|
|
200
|
+
|
|
197
201
|
@staticmethod
|
|
198
202
|
def run_state_to_pipeline_run_status(run_state: str):
|
|
199
203
|
if not run_state:
|
|
@@ -229,6 +233,7 @@ class RunStates:
|
|
|
229
233
|
mlrun_pipelines.common.models.RunStatuses.runtime_state_unspecified: RunStates.unknown,
|
|
230
234
|
mlrun_pipelines.common.models.RunStatuses.error: RunStates.error,
|
|
231
235
|
mlrun_pipelines.common.models.RunStatuses.paused: RunStates.unknown,
|
|
236
|
+
mlrun_pipelines.common.models.RunStatuses.unknown: RunStates.unknown,
|
|
232
237
|
}[pipeline_run_status]
|
|
233
238
|
|
|
234
239
|
|
mlrun/common/schemas/__init__.py
CHANGED
|
@@ -140,11 +140,9 @@ from .model_monitoring import (
|
|
|
140
140
|
FeatureSetFeatures,
|
|
141
141
|
FeatureValues,
|
|
142
142
|
GrafanaColumn,
|
|
143
|
-
GrafanaDataPoint,
|
|
144
143
|
GrafanaNumberColumn,
|
|
145
144
|
GrafanaStringColumn,
|
|
146
145
|
GrafanaTable,
|
|
147
|
-
GrafanaTimeSeriesTarget,
|
|
148
146
|
ModelEndpoint,
|
|
149
147
|
ModelEndpointCreationStrategy,
|
|
150
148
|
ModelEndpointList,
|
|
@@ -51,11 +51,9 @@ from .constants import (
|
|
|
51
51
|
from .grafana import (
|
|
52
52
|
GrafanaColumn,
|
|
53
53
|
GrafanaColumnType,
|
|
54
|
-
GrafanaDataPoint,
|
|
55
54
|
GrafanaNumberColumn,
|
|
56
55
|
GrafanaStringColumn,
|
|
57
56
|
GrafanaTable,
|
|
58
|
-
GrafanaTimeSeriesTarget,
|
|
59
57
|
)
|
|
60
58
|
from .model_endpoints import (
|
|
61
59
|
Features,
|
|
@@ -163,7 +163,6 @@ class ApplicationEvent:
|
|
|
163
163
|
END_INFER_TIME = "end_infer_time"
|
|
164
164
|
ENDPOINT_ID = "endpoint_id"
|
|
165
165
|
ENDPOINT_NAME = "endpoint_name"
|
|
166
|
-
OUTPUT_STREAM_URI = "output_stream_uri"
|
|
167
166
|
|
|
168
167
|
|
|
169
168
|
class WriterEvent(MonitoringStrEnum):
|
|
@@ -251,11 +250,6 @@ class TSDBTarget(MonitoringStrEnum):
|
|
|
251
250
|
TDEngine = "tdengine"
|
|
252
251
|
|
|
253
252
|
|
|
254
|
-
class DefaultProfileName(StrEnum):
|
|
255
|
-
STREAM = "mm-infra-stream"
|
|
256
|
-
TSDB = "mm-infra-tsdb"
|
|
257
|
-
|
|
258
|
-
|
|
259
253
|
class ProjectSecretKeys:
|
|
260
254
|
ACCESS_KEY = "MODEL_MONITORING_ACCESS_KEY"
|
|
261
255
|
TSDB_PROFILE_NAME = "TSDB_PROFILE_NAME"
|
|
@@ -474,10 +468,13 @@ FQN_REGEX = re.compile(FQN_PATTERN)
|
|
|
474
468
|
|
|
475
469
|
# refer to `mlrun.utils.regex.project_name`
|
|
476
470
|
PROJECT_PATTERN = r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$"
|
|
477
|
-
|
|
478
471
|
MODEL_ENDPOINT_ID_PATTERN = r"^[a-zA-Z0-9_-]+$"
|
|
472
|
+
RESULT_NAME_PATTERN = r"[a-zA-Z_][a-zA-Z0-9_]*"
|
|
479
473
|
|
|
480
474
|
INTERSECT_DICT_KEYS = {
|
|
481
475
|
ModelEndpointMonitoringMetricType.METRIC: "intersect_metrics",
|
|
482
476
|
ModelEndpointMonitoringMetricType.RESULT: "intersect_results",
|
|
483
477
|
}
|
|
478
|
+
|
|
479
|
+
CRON_TRIGGER_KINDS = ("http", "cron")
|
|
480
|
+
STREAM_TRIGGER_KINDS = ("v3io-stream", "kafka-cluster")
|
|
@@ -46,14 +46,20 @@ class GrafanaTable(BaseModel):
|
|
|
46
46
|
self.rows.append(list(args))
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
class
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
class GrafanaModelEndpointsTable(GrafanaTable):
|
|
50
|
+
def __init__(self):
|
|
51
|
+
columns = self._init_columns()
|
|
52
|
+
super().__init__(columns=columns)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _init_columns():
|
|
56
|
+
return [
|
|
57
|
+
GrafanaColumn(text="endpoint_id", type=GrafanaColumnType.STRING),
|
|
58
|
+
GrafanaColumn(text="endpoint_name", type=GrafanaColumnType.STRING),
|
|
59
|
+
GrafanaColumn(text="endpoint_function", type=GrafanaColumnType.STRING),
|
|
60
|
+
GrafanaColumn(text="endpoint_model", type=GrafanaColumnType.STRING),
|
|
61
|
+
GrafanaColumn(text="endpoint_model_class", type=GrafanaColumnType.STRING),
|
|
62
|
+
GrafanaColumn(text="error_count", type=GrafanaColumnType.NUMBER),
|
|
63
|
+
GrafanaColumn(text="drift_status", type=GrafanaColumnType.NUMBER),
|
|
64
|
+
GrafanaColumn(text="sampling_percentage", type=GrafanaColumnType.NUMBER),
|
|
65
|
+
]
|
mlrun/config.py
CHANGED
|
@@ -1366,35 +1366,6 @@ class Config:
|
|
|
1366
1366
|
ver in mlrun.mlconf.ce.mode for ver in ["lite", "full"]
|
|
1367
1367
|
)
|
|
1368
1368
|
|
|
1369
|
-
def get_s3_storage_options(self) -> dict[str, typing.Any]:
|
|
1370
|
-
"""
|
|
1371
|
-
Generate storage options dictionary as required for handling S3 path in fsspec. The model monitoring stream
|
|
1372
|
-
graph uses this method for generating the storage options for S3 parquet target path.
|
|
1373
|
-
:return: A storage options dictionary in which each key-value pair represents a particular configuration,
|
|
1374
|
-
such as endpoint_url or aws access key.
|
|
1375
|
-
"""
|
|
1376
|
-
key = mlrun.get_secret_or_env("AWS_ACCESS_KEY_ID")
|
|
1377
|
-
secret = mlrun.get_secret_or_env("AWS_SECRET_ACCESS_KEY")
|
|
1378
|
-
|
|
1379
|
-
force_non_anonymous = mlrun.get_secret_or_env("S3_NON_ANONYMOUS")
|
|
1380
|
-
profile = mlrun.get_secret_or_env("AWS_PROFILE")
|
|
1381
|
-
|
|
1382
|
-
storage_options = dict(
|
|
1383
|
-
anon=not (force_non_anonymous or (key and secret)),
|
|
1384
|
-
key=key,
|
|
1385
|
-
secret=secret,
|
|
1386
|
-
)
|
|
1387
|
-
|
|
1388
|
-
endpoint_url = mlrun.get_secret_or_env("S3_ENDPOINT_URL")
|
|
1389
|
-
if endpoint_url:
|
|
1390
|
-
client_kwargs = {"endpoint_url": endpoint_url}
|
|
1391
|
-
storage_options["client_kwargs"] = client_kwargs
|
|
1392
|
-
|
|
1393
|
-
if profile:
|
|
1394
|
-
storage_options["profile"] = profile
|
|
1395
|
-
|
|
1396
|
-
return storage_options
|
|
1397
|
-
|
|
1398
1369
|
def is_explicit_ack_enabled(self) -> bool:
|
|
1399
1370
|
return self.httpdb.nuclio.explicit_ack == "enabled" and (
|
|
1400
1371
|
not self.nuclio_version
|
|
@@ -1402,13 +1373,6 @@ class Config:
|
|
|
1402
1373
|
>= semver.VersionInfo.parse("1.12.10")
|
|
1403
1374
|
)
|
|
1404
1375
|
|
|
1405
|
-
@staticmethod
|
|
1406
|
-
def get_ordered_keys():
|
|
1407
|
-
# Define the keys to process first
|
|
1408
|
-
return [
|
|
1409
|
-
"MLRUN_HTTPDB__HTTP__VERIFY" # Ensure this key is processed first for proper connection setup
|
|
1410
|
-
]
|
|
1411
|
-
|
|
1412
1376
|
|
|
1413
1377
|
# Global configuration
|
|
1414
1378
|
config = Config.from_dict(default_config)
|
|
@@ -1626,6 +1590,15 @@ def read_env(env=None, prefix=env_prefix):
|
|
|
1626
1590
|
# The default function pod resource values are of type str; however, when reading from environment variable numbers,
|
|
1627
1591
|
# it converts them to type int if contains only number, so we want to convert them to str.
|
|
1628
1592
|
_convert_resources_to_str(config)
|
|
1593
|
+
|
|
1594
|
+
# If the environment variable MLRUN_HTTPDB__HTTP__VERIFY is set, we ensure SSL verification settings take precedence
|
|
1595
|
+
# by moving the 'httpdb' configuration to the beginning of the config dictionary.
|
|
1596
|
+
# This ensures that SSL verification is applied before other settings.
|
|
1597
|
+
if "MLRUN_HTTPDB__HTTP__VERIFY" in env:
|
|
1598
|
+
httpdb = config.pop("httpdb", None)
|
|
1599
|
+
if httpdb:
|
|
1600
|
+
config = {"httpdb": httpdb, **config}
|
|
1601
|
+
|
|
1629
1602
|
return config
|
|
1630
1603
|
|
|
1631
1604
|
|
|
@@ -193,7 +193,7 @@ class DatastoreProfileKafkaSource(DatastoreProfile):
|
|
|
193
193
|
kwargs_public: typing.Optional[dict]
|
|
194
194
|
kwargs_private: typing.Optional[dict]
|
|
195
195
|
|
|
196
|
-
def attributes(self):
|
|
196
|
+
def attributes(self) -> dict[str, typing.Any]:
|
|
197
197
|
attributes = {}
|
|
198
198
|
if self.kwargs_public:
|
|
199
199
|
attributes = merge(attributes, self.kwargs_public)
|
mlrun/datastore/sources.py
CHANGED
|
@@ -1200,19 +1200,20 @@ class KafkaSource(OnlineSource):
|
|
|
1200
1200
|
new_topics = [
|
|
1201
1201
|
NewTopic(topic, num_partitions, replication_factor) for topic in topics
|
|
1202
1202
|
]
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1203
|
+
|
|
1204
|
+
kafka_admin_kwargs = {}
|
|
1205
|
+
if "sasl" in self.attributes:
|
|
1206
|
+
sasl = self.attributes["sasl"]
|
|
1207
|
+
kafka_admin_kwargs.update(
|
|
1208
|
+
{
|
|
1209
|
+
"security_protocol": "SASL_PLAINTEXT",
|
|
1210
|
+
"sasl_mechanism": sasl["mechanism"],
|
|
1211
|
+
"sasl_plain_username": sasl["user"],
|
|
1212
|
+
"sasl_plain_password": sasl["password"],
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
kafka_admin = KafkaAdminClient(bootstrap_servers=brokers, **kafka_admin_kwargs)
|
|
1216
1217
|
try:
|
|
1217
1218
|
kafka_admin.create_topics(new_topics)
|
|
1218
1219
|
finally:
|
mlrun/datastore/storeytargets.py
CHANGED
|
@@ -42,9 +42,21 @@ def get_url_and_storage_options(path, external_storage_options=None):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class TDEngineStoreyTarget(storey.TDEngineTarget):
|
|
45
|
-
def __init__(self, *args, **kwargs):
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
def __init__(self, *args, url: str, **kwargs):
|
|
46
|
+
if url.startswith("ds://"):
|
|
47
|
+
datastore_profile = (
|
|
48
|
+
mlrun.datastore.datastore_profile.datastore_profile_read(url)
|
|
49
|
+
)
|
|
50
|
+
if not isinstance(
|
|
51
|
+
datastore_profile,
|
|
52
|
+
mlrun.datastore.datastore_profile.TDEngineDatastoreProfile,
|
|
53
|
+
):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Unexpected datastore profile type:{datastore_profile.type}."
|
|
56
|
+
"Only TDEngineDatastoreProfile is supported"
|
|
57
|
+
)
|
|
58
|
+
url = datastore_profile.dsn()
|
|
59
|
+
super().__init__(*args, url=url, **kwargs)
|
|
48
60
|
|
|
49
61
|
|
|
50
62
|
class StoreyTargetUtils:
|
|
@@ -69,7 +81,12 @@ class StoreyTargetUtils:
|
|
|
69
81
|
|
|
70
82
|
class ParquetStoreyTarget(storey.ParquetTarget):
|
|
71
83
|
def __init__(self, *args, **kwargs):
|
|
84
|
+
alt_key_name = kwargs.pop("alternative_v3io_access_key", None)
|
|
72
85
|
args, kwargs = StoreyTargetUtils.process_args_and_kwargs(args, kwargs)
|
|
86
|
+
storage_options = kwargs.get("storage_options", {})
|
|
87
|
+
if storage_options and storage_options.get("v3io_access_key") and alt_key_name:
|
|
88
|
+
if alt_key := mlrun.get_secret_or_env(alt_key_name):
|
|
89
|
+
storage_options["v3io_access_key"] = alt_key
|
|
73
90
|
super().__init__(*args, **kwargs)
|
|
74
91
|
|
|
75
92
|
|
mlrun/db/httpdb.py
CHANGED
|
@@ -1734,36 +1734,10 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1734
1734
|
def create_schedule(
|
|
1735
1735
|
self, project: str, schedule: mlrun.common.schemas.ScheduleInput
|
|
1736
1736
|
):
|
|
1737
|
-
"""
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
It also supports a :py:func:`~ScheduleCronTrigger.from_crontab` function that accepts a
|
|
1742
|
-
crontab-formatted string (see https://en.wikipedia.org/wiki/Cron for more information on the format and
|
|
1743
|
-
note that the 0 weekday is always monday).
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
Example::
|
|
1747
|
-
|
|
1748
|
-
from mlrun.common import schemas
|
|
1749
|
-
|
|
1750
|
-
# Execute the get_data_func function every Tuesday at 15:30
|
|
1751
|
-
schedule = schemas.ScheduleInput(
|
|
1752
|
-
name="run_func_on_tuesdays",
|
|
1753
|
-
kind="job",
|
|
1754
|
-
scheduled_object=get_data_func,
|
|
1755
|
-
cron_trigger=schemas.ScheduleCronTrigger(
|
|
1756
|
-
day_of_week="tue", hour=15, minute=30
|
|
1757
|
-
),
|
|
1758
|
-
)
|
|
1759
|
-
db.create_schedule(project_name, schedule)
|
|
1760
|
-
"""
|
|
1761
|
-
|
|
1762
|
-
project = project or config.default_project
|
|
1763
|
-
path = f"projects/{project}/schedules"
|
|
1764
|
-
|
|
1765
|
-
error_message = f"Failed creating schedule {project}/{schedule.name}"
|
|
1766
|
-
self.api_call("POST", path, error_message, body=dict_to_json(schedule.dict()))
|
|
1737
|
+
"""The create_schedule functionality has been deprecated."""
|
|
1738
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
1739
|
+
"The create_schedule functionality has been deprecated."
|
|
1740
|
+
)
|
|
1767
1741
|
|
|
1768
1742
|
def update_schedule(
|
|
1769
1743
|
self, project: str, name: str, schedule: mlrun.common.schemas.ScheduleUpdate
|
mlrun/k8s_utils.py
CHANGED
|
@@ -142,6 +142,7 @@ def verify_label_key(key: str, allow_k8s_prefix: bool = False):
|
|
|
142
142
|
if not key:
|
|
143
143
|
raise mlrun.errors.MLRunInvalidArgumentError("label key cannot be empty")
|
|
144
144
|
|
|
145
|
+
prefix = ""
|
|
145
146
|
parts = key.split("/")
|
|
146
147
|
if len(parts) == 1:
|
|
147
148
|
name = parts[0]
|
|
@@ -180,11 +181,7 @@ def verify_label_key(key: str, allow_k8s_prefix: bool = False):
|
|
|
180
181
|
|
|
181
182
|
# Allow the use of Kubernetes reserved prefixes ('k8s.io/' or 'kubernetes.io/')
|
|
182
183
|
# only when setting node selectors, not when adding new labels.
|
|
183
|
-
if
|
|
184
|
-
key.startswith("k8s.io/")
|
|
185
|
-
or key.startswith("kubernetes.io/")
|
|
186
|
-
and not allow_k8s_prefix
|
|
187
|
-
):
|
|
184
|
+
if not allow_k8s_prefix and prefix in {"k8s.io", "kubernetes.io"}:
|
|
188
185
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
189
186
|
"Labels cannot start with 'k8s.io/' or 'kubernetes.io/'"
|
|
190
187
|
)
|
mlrun/launcher/base.py
CHANGED
|
@@ -401,6 +401,7 @@ class BaseLauncher(abc.ABC):
|
|
|
401
401
|
status=run.status.state,
|
|
402
402
|
name=run.metadata.name,
|
|
403
403
|
)
|
|
404
|
+
self._update_end_time_if_terminal_state(runtime, run)
|
|
404
405
|
if (
|
|
405
406
|
run.status.state
|
|
406
407
|
in mlrun.common.runtimes.constants.RunStates.error_and_abortion_states()
|
|
@@ -416,6 +417,21 @@ class BaseLauncher(abc.ABC):
|
|
|
416
417
|
|
|
417
418
|
return None
|
|
418
419
|
|
|
420
|
+
@staticmethod
|
|
421
|
+
def _update_end_time_if_terminal_state(
|
|
422
|
+
runtime: "mlrun.runtimes.BaseRuntime", run: "mlrun.run.RunObject"
|
|
423
|
+
):
|
|
424
|
+
if (
|
|
425
|
+
run.status.state
|
|
426
|
+
in mlrun.common.runtimes.constants.RunStates.terminal_states()
|
|
427
|
+
and not run.status.end_time
|
|
428
|
+
):
|
|
429
|
+
end_time = mlrun.utils.now_date().isoformat()
|
|
430
|
+
updates = {"status.end_time": end_time}
|
|
431
|
+
runtime._get_db().update_run(
|
|
432
|
+
updates, run.metadata.uid, run.metadata.project
|
|
433
|
+
)
|
|
434
|
+
|
|
419
435
|
@staticmethod
|
|
420
436
|
def _refresh_function_metadata(runtime: "mlrun.runtimes.BaseRuntime"):
|
|
421
437
|
pass
|
mlrun/model_monitoring/api.py
CHANGED
|
@@ -619,8 +619,8 @@ def _create_model_monitoring_function_base(
|
|
|
619
619
|
app_step.__class__ = mlrun.serving.MonitoringApplicationStep
|
|
620
620
|
|
|
621
621
|
app_step.error_handler(
|
|
622
|
-
name="ApplicationErrorHandler",
|
|
623
622
|
class_name="mlrun.model_monitoring.applications._application_steps._ApplicationErrorHandler",
|
|
623
|
+
name="ApplicationErrorHandler",
|
|
624
624
|
full_event=True,
|
|
625
625
|
project=project,
|
|
626
626
|
)
|
|
@@ -629,7 +629,6 @@ def _create_model_monitoring_function_base(
|
|
|
629
629
|
class_name="mlrun.model_monitoring.applications._application_steps._PushToMonitoringWriter",
|
|
630
630
|
name="PushToMonitoringWriter",
|
|
631
631
|
project=project,
|
|
632
|
-
writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
633
632
|
)
|
|
634
633
|
|
|
635
634
|
def block_to_mock_server(*args, **kwargs) -> typing.NoReturn:
|
|
@@ -18,10 +18,8 @@ from typing import Any, Optional, Union
|
|
|
18
18
|
|
|
19
19
|
import mlrun.common.schemas
|
|
20
20
|
import mlrun.common.schemas.alert as alert_objects
|
|
21
|
-
import mlrun.common.schemas.model_monitoring.constants as
|
|
22
|
-
import mlrun.
|
|
23
|
-
import mlrun.model_monitoring
|
|
24
|
-
from mlrun.model_monitoring.helpers import get_stream_path
|
|
21
|
+
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
22
|
+
import mlrun.model_monitoring.helpers
|
|
25
23
|
from mlrun.serving import GraphContext
|
|
26
24
|
from mlrun.serving.utils import StepToDict
|
|
27
25
|
from mlrun.utils import logger
|
|
@@ -37,29 +35,14 @@ from .results import (
|
|
|
37
35
|
class _PushToMonitoringWriter(StepToDict):
|
|
38
36
|
kind = "monitoring_application_stream_pusher"
|
|
39
37
|
|
|
40
|
-
def __init__(
|
|
41
|
-
self,
|
|
42
|
-
project: str,
|
|
43
|
-
writer_application_name: str,
|
|
44
|
-
stream_uri: Optional[str] = None,
|
|
45
|
-
name: Optional[str] = None,
|
|
46
|
-
):
|
|
38
|
+
def __init__(self, project: str) -> None:
|
|
47
39
|
"""
|
|
48
40
|
Class for pushing application results to the monitoring writer stream.
|
|
49
41
|
|
|
50
|
-
:param project:
|
|
51
|
-
:param writer_application_name: Writer application name.
|
|
52
|
-
:param stream_uri: Stream URI for pushing results.
|
|
53
|
-
:param name: Name of the PushToMonitoringWriter
|
|
54
|
-
instance default to PushToMonitoringWriter.
|
|
42
|
+
:param project: Project name.
|
|
55
43
|
"""
|
|
56
44
|
self.project = project
|
|
57
|
-
self.application_name_to_push = writer_application_name
|
|
58
|
-
self.stream_uri = stream_uri or get_stream_path(
|
|
59
|
-
project=self.project, function_name=self.application_name_to_push
|
|
60
|
-
)
|
|
61
45
|
self.output_stream = None
|
|
62
|
-
self.name = name or "PushToMonitoringWriter"
|
|
63
46
|
|
|
64
47
|
def do(
|
|
65
48
|
self,
|
|
@@ -82,40 +65,43 @@ class _PushToMonitoringWriter(StepToDict):
|
|
|
82
65
|
self._lazy_init()
|
|
83
66
|
application_results, application_context = event
|
|
84
67
|
writer_event = {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
mm_constants.WriterEvent.ENDPOINT_NAME: application_context.endpoint_name,
|
|
69
|
+
mm_constants.WriterEvent.APPLICATION_NAME: application_context.application_name,
|
|
70
|
+
mm_constants.WriterEvent.ENDPOINT_ID: application_context.endpoint_id,
|
|
71
|
+
mm_constants.WriterEvent.START_INFER_TIME: application_context.start_infer_time.isoformat(
|
|
89
72
|
sep=" ", timespec="microseconds"
|
|
90
73
|
),
|
|
91
|
-
|
|
74
|
+
mm_constants.WriterEvent.END_INFER_TIME: application_context.end_infer_time.isoformat(
|
|
92
75
|
sep=" ", timespec="microseconds"
|
|
93
76
|
),
|
|
94
77
|
}
|
|
95
78
|
for result in application_results:
|
|
96
79
|
data = result.to_dict()
|
|
97
80
|
if isinstance(result, ModelMonitoringApplicationResult):
|
|
98
|
-
writer_event[
|
|
99
|
-
|
|
81
|
+
writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
|
|
82
|
+
mm_constants.WriterEventKind.RESULT
|
|
100
83
|
)
|
|
101
84
|
elif isinstance(result, _ModelMonitoringApplicationStats):
|
|
102
|
-
writer_event[
|
|
103
|
-
|
|
85
|
+
writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
|
|
86
|
+
mm_constants.WriterEventKind.STATS
|
|
104
87
|
)
|
|
105
88
|
else:
|
|
106
|
-
writer_event[
|
|
107
|
-
|
|
89
|
+
writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
|
|
90
|
+
mm_constants.WriterEventKind.METRIC
|
|
108
91
|
)
|
|
109
|
-
writer_event[
|
|
110
|
-
logger.
|
|
111
|
-
|
|
92
|
+
writer_event[mm_constants.WriterEvent.DATA] = json.dumps(data)
|
|
93
|
+
logger.debug(
|
|
94
|
+
"Pushing data to output stream", writer_event=str(writer_event)
|
|
112
95
|
)
|
|
113
96
|
self.output_stream.push([writer_event])
|
|
114
|
-
logger.
|
|
97
|
+
logger.debug("Pushed data to output stream successfully")
|
|
115
98
|
|
|
116
99
|
def _lazy_init(self):
|
|
117
100
|
if self.output_stream is None:
|
|
118
|
-
self.output_stream = mlrun.
|
|
101
|
+
self.output_stream = mlrun.model_monitoring.helpers.get_output_stream(
|
|
102
|
+
project=self.project,
|
|
103
|
+
function_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
104
|
+
)
|
|
119
105
|
|
|
120
106
|
|
|
121
107
|
class _PrepareMonitoringEvent(StepToDict):
|