mlrun 1.7.0rc4__py3-none-any.whl → 1.7.0rc6__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/artifacts/base.py +2 -1
- mlrun/artifacts/plots.py +9 -5
- mlrun/common/constants.py +1 -0
- mlrun/common/schemas/__init__.py +10 -0
- mlrun/common/schemas/api_gateway.py +85 -0
- mlrun/common/schemas/auth.py +2 -2
- mlrun/config.py +19 -4
- mlrun/datastore/sources.py +5 -4
- mlrun/datastore/targets.py +16 -20
- mlrun/db/base.py +16 -0
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +50 -8
- mlrun/db/nopdb.py +13 -0
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +1 -1
- mlrun/launcher/client.py +1 -1
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +1 -1
- mlrun/launcher/remote.py +1 -1
- mlrun/model_monitoring/api.py +6 -12
- mlrun/model_monitoring/application.py +21 -21
- mlrun/model_monitoring/applications/histogram_data_drift.py +130 -40
- mlrun/model_monitoring/batch.py +1 -42
- mlrun/model_monitoring/controller.py +1 -8
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +45 -4
- mlrun/model_monitoring/stream_processing.py +2 -0
- mlrun/projects/project.py +229 -16
- mlrun/run.py +70 -74
- mlrun/runtimes/__init__.py +35 -0
- mlrun/runtimes/base.py +15 -11
- mlrun/runtimes/nuclio/__init__.py +1 -0
- mlrun/runtimes/nuclio/api_gateway.py +300 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +283 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +87 -0
- mlrun/runtimes/nuclio/function.py +50 -1
- mlrun/runtimes/pod.py +1 -1
- mlrun/serving/states.py +7 -19
- mlrun/utils/logger.py +2 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/METADATA +1 -1
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/RECORD +47 -42
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/top_level.txt +0 -0
mlrun/runtimes/base.py
CHANGED
|
@@ -138,20 +138,24 @@ class FunctionSpec(ModelObj):
|
|
|
138
138
|
|
|
139
139
|
@property
|
|
140
140
|
def clone_target_dir(self):
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
# TODO: remove this property in 1.9.0
|
|
142
|
+
if self.build.source_code_target_dir:
|
|
143
|
+
warnings.warn(
|
|
144
|
+
"The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.9.0. "
|
|
145
|
+
"Use spec.build.source_code_target_dir instead.",
|
|
146
|
+
FutureWarning,
|
|
147
|
+
)
|
|
146
148
|
return self.build.source_code_target_dir
|
|
147
149
|
|
|
148
150
|
@clone_target_dir.setter
|
|
149
151
|
def clone_target_dir(self, clone_target_dir):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
# TODO: remove this property in 1.9.0
|
|
153
|
+
if clone_target_dir:
|
|
154
|
+
warnings.warn(
|
|
155
|
+
"The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.9.0. "
|
|
156
|
+
"Use spec.build.source_code_target_dir instead.",
|
|
157
|
+
FutureWarning,
|
|
158
|
+
)
|
|
155
159
|
self.build.source_code_target_dir = clone_target_dir
|
|
156
160
|
|
|
157
161
|
def enrich_function_preemption_spec(self):
|
|
@@ -782,7 +786,7 @@ class BaseRuntime(ModelObj):
|
|
|
782
786
|
requirements: Optional[list[str]] = None,
|
|
783
787
|
overwrite: bool = False,
|
|
784
788
|
prepare_image_for_deploy: bool = True,
|
|
785
|
-
requirements_file: str = "",
|
|
789
|
+
requirements_file: Optional[str] = "",
|
|
786
790
|
):
|
|
787
791
|
"""add package requirements from file or list to build spec.
|
|
788
792
|
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import base64
|
|
15
|
+
from typing import Optional, Union
|
|
16
|
+
from urllib.parse import urljoin
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
import mlrun
|
|
21
|
+
import mlrun.common.schemas
|
|
22
|
+
|
|
23
|
+
from .function import RemoteRuntime
|
|
24
|
+
from .serving import ServingRuntime
|
|
25
|
+
|
|
26
|
+
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
|
|
27
|
+
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE = "none"
|
|
28
|
+
PROJECT_NAME_LABEL = "nuclio.io/project-name"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class APIGateway:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
project,
|
|
35
|
+
name: str,
|
|
36
|
+
functions: Union[
|
|
37
|
+
list[str],
|
|
38
|
+
Union[
|
|
39
|
+
list[
|
|
40
|
+
Union[
|
|
41
|
+
RemoteRuntime,
|
|
42
|
+
ServingRuntime,
|
|
43
|
+
]
|
|
44
|
+
],
|
|
45
|
+
Union[RemoteRuntime, ServingRuntime],
|
|
46
|
+
],
|
|
47
|
+
],
|
|
48
|
+
description: str = "",
|
|
49
|
+
path: str = "/",
|
|
50
|
+
authentication_mode: Optional[
|
|
51
|
+
str
|
|
52
|
+
] = NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE,
|
|
53
|
+
host: Optional[str] = None,
|
|
54
|
+
canary: Optional[list[int]] = None,
|
|
55
|
+
username: Optional[str] = None,
|
|
56
|
+
password: Optional[str] = None,
|
|
57
|
+
):
|
|
58
|
+
self.functions = None
|
|
59
|
+
self._validate(
|
|
60
|
+
project=project,
|
|
61
|
+
functions=functions,
|
|
62
|
+
name=name,
|
|
63
|
+
canary=canary,
|
|
64
|
+
username=username,
|
|
65
|
+
password=password,
|
|
66
|
+
)
|
|
67
|
+
self.project = project
|
|
68
|
+
self.name = name
|
|
69
|
+
self.host = host
|
|
70
|
+
|
|
71
|
+
self.path = path
|
|
72
|
+
self.description = description
|
|
73
|
+
self.authentication_mode = (
|
|
74
|
+
authentication_mode
|
|
75
|
+
if authentication_mode
|
|
76
|
+
else self._enrich_authentication_mode(username=username, password=password)
|
|
77
|
+
)
|
|
78
|
+
self.canary = canary
|
|
79
|
+
self._username = username
|
|
80
|
+
self._password = password
|
|
81
|
+
|
|
82
|
+
def invoke(
|
|
83
|
+
self,
|
|
84
|
+
method="POST",
|
|
85
|
+
headers: dict = {},
|
|
86
|
+
auth: Optional[tuple[str, str]] = None,
|
|
87
|
+
**kwargs,
|
|
88
|
+
):
|
|
89
|
+
if not self.invoke_url:
|
|
90
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
91
|
+
"Invocation url is not set. Set up gateway's `invoke_url` attribute."
|
|
92
|
+
)
|
|
93
|
+
if (
|
|
94
|
+
self.authentication_mode
|
|
95
|
+
== NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
96
|
+
and not auth
|
|
97
|
+
):
|
|
98
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
99
|
+
"API Gateway invocation requires authentication. Please pass credentials"
|
|
100
|
+
)
|
|
101
|
+
if auth:
|
|
102
|
+
headers["Authorization"] = self._generate_basic_auth(*auth)
|
|
103
|
+
return requests.request(
|
|
104
|
+
method=method, url=self.invoke_url, headers=headers, **kwargs
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
|
|
109
|
+
project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
|
|
110
|
+
functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
|
|
111
|
+
return cls(
|
|
112
|
+
project=project,
|
|
113
|
+
description=api_gateway.spec.description,
|
|
114
|
+
name=api_gateway.spec.name,
|
|
115
|
+
host=api_gateway.spec.host,
|
|
116
|
+
path=api_gateway.spec.path,
|
|
117
|
+
authentication_mode=str(api_gateway.spec.authenticationMode),
|
|
118
|
+
functions=functions,
|
|
119
|
+
canary=canary,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def to_scheme(self) -> mlrun.common.schemas.APIGateway:
|
|
123
|
+
upstreams = (
|
|
124
|
+
[
|
|
125
|
+
mlrun.common.schemas.APIGatewayUpstream(
|
|
126
|
+
nucliofunction={"name": function_name},
|
|
127
|
+
percentage=percentage,
|
|
128
|
+
)
|
|
129
|
+
for function_name, percentage in zip(self.functions, self.canary)
|
|
130
|
+
]
|
|
131
|
+
if self.canary
|
|
132
|
+
else [
|
|
133
|
+
mlrun.common.schemas.APIGatewayUpstream(
|
|
134
|
+
nucliofunction={"name": function_name},
|
|
135
|
+
)
|
|
136
|
+
for function_name in self.functions
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
api_gateway = mlrun.common.schemas.APIGateway(
|
|
140
|
+
metadata=mlrun.common.schemas.APIGatewayMetadata(name=self.name, labels={}),
|
|
141
|
+
spec=mlrun.common.schemas.APIGatewaySpec(
|
|
142
|
+
name=self.name,
|
|
143
|
+
description=self.description,
|
|
144
|
+
path=self.path,
|
|
145
|
+
authentication_mode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
|
|
146
|
+
self.authentication_mode
|
|
147
|
+
),
|
|
148
|
+
upstreams=upstreams,
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
if (
|
|
152
|
+
self.authentication_mode
|
|
153
|
+
is NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
154
|
+
):
|
|
155
|
+
api_gateway.spec.authentication = mlrun.common.schemas.APIGatewayBasicAuth(
|
|
156
|
+
username=self._username, password=self._password
|
|
157
|
+
)
|
|
158
|
+
return api_gateway
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def invoke_url(
|
|
162
|
+
self,
|
|
163
|
+
):
|
|
164
|
+
return urljoin(self.host, self.path)
|
|
165
|
+
|
|
166
|
+
def _validate(
|
|
167
|
+
self,
|
|
168
|
+
name: str,
|
|
169
|
+
project: str,
|
|
170
|
+
functions: Union[
|
|
171
|
+
list[str],
|
|
172
|
+
Union[
|
|
173
|
+
list[
|
|
174
|
+
Union[
|
|
175
|
+
RemoteRuntime,
|
|
176
|
+
ServingRuntime,
|
|
177
|
+
]
|
|
178
|
+
],
|
|
179
|
+
Union[RemoteRuntime, ServingRuntime],
|
|
180
|
+
],
|
|
181
|
+
],
|
|
182
|
+
canary: Optional[list[int]] = None,
|
|
183
|
+
username: Optional[str] = None,
|
|
184
|
+
password: Optional[str] = None,
|
|
185
|
+
):
|
|
186
|
+
if not name:
|
|
187
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
188
|
+
"API Gateway name cannot be empty"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.functions = self._validate_functions(project=project, functions=functions)
|
|
192
|
+
|
|
193
|
+
# validating canary
|
|
194
|
+
if canary:
|
|
195
|
+
if len(self.functions) != len(canary):
|
|
196
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
197
|
+
"Function and canary lists lengths do not match"
|
|
198
|
+
)
|
|
199
|
+
for canary_percent in canary:
|
|
200
|
+
if canary_percent < 0 or canary_percent > 100:
|
|
201
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
202
|
+
"The percentage value must be in the range from 0 to 100"
|
|
203
|
+
)
|
|
204
|
+
if sum(canary) != 100:
|
|
205
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
206
|
+
"The sum of canary function percents should be equal to 100"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# validating auth
|
|
210
|
+
if username and not password:
|
|
211
|
+
raise mlrun.errors.MLRunInvalidArgumentError("Password is not specified")
|
|
212
|
+
|
|
213
|
+
if password and not username:
|
|
214
|
+
raise mlrun.errors.MLRunInvalidArgumentError("Username is not specified")
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _validate_functions(
|
|
218
|
+
project: str,
|
|
219
|
+
functions: Union[
|
|
220
|
+
list[str],
|
|
221
|
+
Union[
|
|
222
|
+
list[
|
|
223
|
+
Union[
|
|
224
|
+
RemoteRuntime,
|
|
225
|
+
ServingRuntime,
|
|
226
|
+
]
|
|
227
|
+
],
|
|
228
|
+
Union[RemoteRuntime, ServingRuntime],
|
|
229
|
+
],
|
|
230
|
+
],
|
|
231
|
+
):
|
|
232
|
+
if not isinstance(functions, list):
|
|
233
|
+
functions = [functions]
|
|
234
|
+
|
|
235
|
+
# validating functions
|
|
236
|
+
if not 1 <= len(functions) <= 2:
|
|
237
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
238
|
+
f"Gateway can be created from one or two functions, "
|
|
239
|
+
f"the number of functions passed is {len(functions)}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
function_names = []
|
|
243
|
+
for func in functions:
|
|
244
|
+
if isinstance(func, str):
|
|
245
|
+
function_names.append(func)
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
function_name = (
|
|
249
|
+
func.metadata.name if hasattr(func, "metadata") else func.name
|
|
250
|
+
)
|
|
251
|
+
if func.kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
252
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
253
|
+
f"Input function {function_name} is not a Nuclio function"
|
|
254
|
+
)
|
|
255
|
+
if func.metadata.project != project:
|
|
256
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
257
|
+
f"input function {function_name} "
|
|
258
|
+
f"does not belong to this project"
|
|
259
|
+
)
|
|
260
|
+
function_names.append(func.uri)
|
|
261
|
+
return function_names
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _enrich_authentication_mode(username, password):
|
|
265
|
+
return (
|
|
266
|
+
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE
|
|
267
|
+
if username is not None and password is not None
|
|
268
|
+
else NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def _generate_basic_auth(username: str, password: str):
|
|
273
|
+
token = base64.b64encode(f"{username}:{password}".encode()).decode()
|
|
274
|
+
return f"Basic {token}"
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def _resolve_canary(
|
|
278
|
+
upstreams: list[mlrun.common.schemas.APIGatewayUpstream],
|
|
279
|
+
) -> tuple[Union[list[str], None], Union[list[int], None]]:
|
|
280
|
+
if len(upstreams) == 1:
|
|
281
|
+
return [upstreams[0].nucliofunction.get("name")], None
|
|
282
|
+
elif len(upstreams) == 2:
|
|
283
|
+
canary = [0, 0]
|
|
284
|
+
functions = [
|
|
285
|
+
upstreams[0].nucliofunction.get("name"),
|
|
286
|
+
upstreams[1].nucliofunction.get("name"),
|
|
287
|
+
]
|
|
288
|
+
percentage_1 = upstreams[0].percentage
|
|
289
|
+
percentage_2 = upstreams[1].percentage
|
|
290
|
+
|
|
291
|
+
if not percentage_1 and percentage_2:
|
|
292
|
+
percentage_1 = 100 - percentage_2
|
|
293
|
+
if not percentage_2 and percentage_1:
|
|
294
|
+
percentage_2 = 100 - percentage_1
|
|
295
|
+
if percentage_1 and percentage_2:
|
|
296
|
+
canary = [percentage_1, percentage_2]
|
|
297
|
+
return functions, canary
|
|
298
|
+
else:
|
|
299
|
+
# Nuclio only supports 1 or 2 upstream functions
|
|
300
|
+
return None, None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from .application import ApplicationRuntime
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import pathlib
|
|
15
|
+
|
|
16
|
+
import nuclio
|
|
17
|
+
|
|
18
|
+
import mlrun.errors
|
|
19
|
+
from mlrun.common.schemas import AuthInfo
|
|
20
|
+
from mlrun.runtimes import RemoteRuntime
|
|
21
|
+
from mlrun.runtimes.nuclio import min_nuclio_versions
|
|
22
|
+
from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApplicationSpec(NuclioSpec):
|
|
26
|
+
_dict_fields = NuclioSpec._dict_fields + [
|
|
27
|
+
"internal_application_port",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
command=None,
|
|
33
|
+
args=None,
|
|
34
|
+
image=None,
|
|
35
|
+
mode=None,
|
|
36
|
+
entry_points=None,
|
|
37
|
+
description=None,
|
|
38
|
+
replicas=None,
|
|
39
|
+
min_replicas=None,
|
|
40
|
+
max_replicas=None,
|
|
41
|
+
volumes=None,
|
|
42
|
+
volume_mounts=None,
|
|
43
|
+
env=None,
|
|
44
|
+
resources=None,
|
|
45
|
+
config=None,
|
|
46
|
+
base_spec=None,
|
|
47
|
+
no_cache=None,
|
|
48
|
+
source=None,
|
|
49
|
+
image_pull_policy=None,
|
|
50
|
+
function_kind=None,
|
|
51
|
+
build=None,
|
|
52
|
+
service_account=None,
|
|
53
|
+
readiness_timeout=None,
|
|
54
|
+
readiness_timeout_before_failure=None,
|
|
55
|
+
default_handler=None,
|
|
56
|
+
node_name=None,
|
|
57
|
+
node_selector=None,
|
|
58
|
+
affinity=None,
|
|
59
|
+
disable_auto_mount=False,
|
|
60
|
+
priority_class_name=None,
|
|
61
|
+
pythonpath=None,
|
|
62
|
+
workdir=None,
|
|
63
|
+
image_pull_secret=None,
|
|
64
|
+
tolerations=None,
|
|
65
|
+
preemption_mode=None,
|
|
66
|
+
security_context=None,
|
|
67
|
+
service_type=None,
|
|
68
|
+
add_templated_ingress_host_mode=None,
|
|
69
|
+
clone_target_dir=None,
|
|
70
|
+
state_thresholds=None,
|
|
71
|
+
disable_default_http_trigger=None,
|
|
72
|
+
internal_application_port=None,
|
|
73
|
+
):
|
|
74
|
+
super().__init__(
|
|
75
|
+
command=command,
|
|
76
|
+
args=args,
|
|
77
|
+
image=image,
|
|
78
|
+
mode=mode,
|
|
79
|
+
entry_points=entry_points,
|
|
80
|
+
description=description,
|
|
81
|
+
replicas=replicas,
|
|
82
|
+
min_replicas=min_replicas,
|
|
83
|
+
max_replicas=max_replicas,
|
|
84
|
+
volumes=volumes,
|
|
85
|
+
volume_mounts=volume_mounts,
|
|
86
|
+
env=env,
|
|
87
|
+
resources=resources,
|
|
88
|
+
config=config,
|
|
89
|
+
base_spec=base_spec,
|
|
90
|
+
no_cache=no_cache,
|
|
91
|
+
source=source,
|
|
92
|
+
image_pull_policy=image_pull_policy,
|
|
93
|
+
function_kind=function_kind,
|
|
94
|
+
build=build,
|
|
95
|
+
service_account=service_account,
|
|
96
|
+
readiness_timeout=readiness_timeout,
|
|
97
|
+
readiness_timeout_before_failure=readiness_timeout_before_failure,
|
|
98
|
+
default_handler=default_handler,
|
|
99
|
+
node_name=node_name,
|
|
100
|
+
node_selector=node_selector,
|
|
101
|
+
affinity=affinity,
|
|
102
|
+
disable_auto_mount=disable_auto_mount,
|
|
103
|
+
priority_class_name=priority_class_name,
|
|
104
|
+
pythonpath=pythonpath,
|
|
105
|
+
workdir=workdir,
|
|
106
|
+
image_pull_secret=image_pull_secret,
|
|
107
|
+
tolerations=tolerations,
|
|
108
|
+
preemption_mode=preemption_mode,
|
|
109
|
+
security_context=security_context,
|
|
110
|
+
service_type=service_type,
|
|
111
|
+
add_templated_ingress_host_mode=add_templated_ingress_host_mode,
|
|
112
|
+
clone_target_dir=clone_target_dir,
|
|
113
|
+
state_thresholds=state_thresholds,
|
|
114
|
+
disable_default_http_trigger=disable_default_http_trigger,
|
|
115
|
+
)
|
|
116
|
+
self.internal_application_port = internal_application_port or 8080
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def internal_application_port(self):
|
|
120
|
+
return self._internal_application_port
|
|
121
|
+
|
|
122
|
+
@internal_application_port.setter
|
|
123
|
+
def internal_application_port(self, port):
|
|
124
|
+
port = int(port)
|
|
125
|
+
if port < 0 or port > 65535:
|
|
126
|
+
raise ValueError("Port must be in the range 0-65535")
|
|
127
|
+
self._internal_application_port = port
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ApplicationStatus(NuclioStatus):
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
state=None,
|
|
134
|
+
nuclio_name=None,
|
|
135
|
+
address=None,
|
|
136
|
+
internal_invocation_urls=None,
|
|
137
|
+
external_invocation_urls=None,
|
|
138
|
+
build_pod=None,
|
|
139
|
+
container_image=None,
|
|
140
|
+
application_image=None,
|
|
141
|
+
sidecar_name=None,
|
|
142
|
+
):
|
|
143
|
+
super().__init__(
|
|
144
|
+
state=state,
|
|
145
|
+
nuclio_name=nuclio_name,
|
|
146
|
+
address=address,
|
|
147
|
+
internal_invocation_urls=internal_invocation_urls,
|
|
148
|
+
external_invocation_urls=external_invocation_urls,
|
|
149
|
+
build_pod=build_pod,
|
|
150
|
+
container_image=container_image,
|
|
151
|
+
)
|
|
152
|
+
self.application_image = application_image or None
|
|
153
|
+
self.sidecar_name = sidecar_name or None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class ApplicationRuntime(RemoteRuntime):
|
|
157
|
+
kind = "application"
|
|
158
|
+
|
|
159
|
+
@min_nuclio_versions("1.12.7")
|
|
160
|
+
def __init__(self, spec=None, metadata=None):
|
|
161
|
+
super().__init__(spec=spec, metadata=metadata)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def spec(self) -> ApplicationSpec:
|
|
165
|
+
return self._spec
|
|
166
|
+
|
|
167
|
+
@spec.setter
|
|
168
|
+
def spec(self, spec):
|
|
169
|
+
self._spec = self._verify_dict(spec, "spec", ApplicationSpec)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def status(self) -> ApplicationStatus:
|
|
173
|
+
return self._status
|
|
174
|
+
|
|
175
|
+
@status.setter
|
|
176
|
+
def status(self, status):
|
|
177
|
+
self._status = self._verify_dict(status, "status", ApplicationStatus)
|
|
178
|
+
|
|
179
|
+
def set_internal_application_port(self, port: int):
|
|
180
|
+
self.spec.internal_application_port = port
|
|
181
|
+
|
|
182
|
+
def pre_deploy_validation(self):
|
|
183
|
+
super().pre_deploy_validation()
|
|
184
|
+
if not self.spec.config.get("spec.sidecars"):
|
|
185
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
186
|
+
"Application spec must include a sidecar configuration"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
sidecars = self.spec.config["spec.sidecars"]
|
|
190
|
+
for sidecar in sidecars:
|
|
191
|
+
if not sidecar.get("image"):
|
|
192
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
193
|
+
"Application sidecar spec must include an image"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if not sidecar.get("ports"):
|
|
197
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
198
|
+
"Application sidecar spec must include at least one port"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
ports = sidecar["ports"]
|
|
202
|
+
for port in ports:
|
|
203
|
+
if not port.get("containerPort"):
|
|
204
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
205
|
+
"Application sidecar port spec must include a containerPort"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not port.get("name"):
|
|
209
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
210
|
+
"Application sidecar port spec must include a name"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if not sidecar.get("command") and sidecar.get("args"):
|
|
214
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
215
|
+
"Application sidecar spec must include a command if args are provided"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def deploy(
|
|
219
|
+
self,
|
|
220
|
+
project="",
|
|
221
|
+
tag="",
|
|
222
|
+
verbose=False,
|
|
223
|
+
auth_info: AuthInfo = None,
|
|
224
|
+
builder_env: dict = None,
|
|
225
|
+
force_build: bool = False,
|
|
226
|
+
):
|
|
227
|
+
self._ensure_reverse_proxy_configurations()
|
|
228
|
+
self._configure_application_sidecar()
|
|
229
|
+
super().deploy(
|
|
230
|
+
project,
|
|
231
|
+
tag,
|
|
232
|
+
verbose,
|
|
233
|
+
auth_info,
|
|
234
|
+
builder_env,
|
|
235
|
+
force_build,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _ensure_reverse_proxy_configurations(self):
|
|
239
|
+
if self.spec.build.functionSourceCode or self.status.container_image:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
filename, handler = ApplicationRuntime.get_filename_and_handler()
|
|
243
|
+
name, spec, code = nuclio.build_file(
|
|
244
|
+
filename,
|
|
245
|
+
name=self.metadata.name,
|
|
246
|
+
handler=handler,
|
|
247
|
+
)
|
|
248
|
+
self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
|
|
249
|
+
self.spec.build.functionSourceCode = mlrun.utils.get_in(
|
|
250
|
+
spec, "spec.build.functionSourceCode"
|
|
251
|
+
)
|
|
252
|
+
self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
|
|
253
|
+
|
|
254
|
+
def _configure_application_sidecar(self):
|
|
255
|
+
# Save the application image in the status to allow overriding it with the reverse proxy entry point
|
|
256
|
+
if self.spec.image and (
|
|
257
|
+
not self.status.application_image
|
|
258
|
+
or self.spec.image != self.status.container_image
|
|
259
|
+
):
|
|
260
|
+
self.status.application_image = self.spec.image
|
|
261
|
+
self.spec.image = ""
|
|
262
|
+
|
|
263
|
+
if self.status.container_image:
|
|
264
|
+
self.from_image(self.status.container_image)
|
|
265
|
+
# nuclio implementation detail - when providing the image and emptying out the source code,
|
|
266
|
+
# nuclio skips rebuilding the image and simply takes the prebuilt image
|
|
267
|
+
self.spec.build.functionSourceCode = ""
|
|
268
|
+
|
|
269
|
+
self.status.sidecar_name = f"{self.metadata.name}-sidecar"
|
|
270
|
+
self.with_sidecar(
|
|
271
|
+
name=self.status.sidecar_name,
|
|
272
|
+
image=self.status.application_image,
|
|
273
|
+
ports=self.spec.internal_application_port,
|
|
274
|
+
command=self.spec.command,
|
|
275
|
+
args=self.spec.args,
|
|
276
|
+
)
|
|
277
|
+
self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
|
|
278
|
+
self.set_env("SIDECAR_HOST", "http://localhost")
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def get_filename_and_handler(cls) -> (str, str):
|
|
282
|
+
reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
|
|
283
|
+
return str(reverse_proxy_file_path), "Handler"
|