mlrun 1.7.0rc7__py3-none-any.whl → 1.7.0rc9__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/__main__.py +2 -0
- mlrun/common/schemas/__init__.py +3 -0
- mlrun/common/schemas/api_gateway.py +8 -1
- mlrun/common/schemas/hub.py +7 -9
- mlrun/common/schemas/model_monitoring/constants.py +1 -1
- mlrun/common/schemas/pagination.py +26 -0
- mlrun/common/schemas/project.py +15 -10
- mlrun/config.py +28 -10
- mlrun/datastore/__init__.py +3 -7
- mlrun/datastore/datastore_profile.py +19 -1
- mlrun/datastore/snowflake_utils.py +43 -0
- mlrun/datastore/sources.py +9 -26
- mlrun/datastore/targets.py +131 -11
- mlrun/datastore/utils.py +10 -5
- mlrun/db/base.py +44 -0
- mlrun/db/httpdb.py +122 -21
- mlrun/db/nopdb.py +107 -0
- mlrun/feature_store/api.py +3 -2
- mlrun/feature_store/retrieval/spark_merger.py +27 -23
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
- mlrun/kfpops.py +2 -5
- mlrun/launcher/base.py +1 -1
- mlrun/launcher/client.py +2 -2
- mlrun/model_monitoring/helpers.py +3 -1
- mlrun/projects/pipelines.py +1 -1
- mlrun/projects/project.py +32 -21
- mlrun/run.py +5 -1
- mlrun/runtimes/__init__.py +16 -0
- mlrun/runtimes/base.py +4 -1
- mlrun/runtimes/kubejob.py +26 -121
- mlrun/runtimes/nuclio/api_gateway.py +58 -8
- mlrun/runtimes/nuclio/application/application.py +79 -1
- mlrun/runtimes/nuclio/application/reverse_proxy.go +9 -1
- mlrun/runtimes/nuclio/function.py +11 -8
- mlrun/runtimes/nuclio/serving.py +2 -2
- mlrun/runtimes/pod.py +145 -0
- mlrun/runtimes/utils.py +0 -28
- mlrun/serving/remote.py +2 -3
- mlrun/serving/routers.py +4 -3
- mlrun/serving/server.py +1 -1
- mlrun/serving/states.py +6 -9
- mlrun/serving/v2_serving.py +4 -3
- mlrun/utils/http.py +1 -1
- mlrun/utils/retryer.py +1 -0
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/METADATA +15 -15
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/RECORD +52 -50
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc7.dist-info → mlrun-1.7.0rc9.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py
CHANGED
|
@@ -32,7 +32,7 @@ import dotenv
|
|
|
32
32
|
import git
|
|
33
33
|
import git.exc
|
|
34
34
|
import kfp
|
|
35
|
-
import nuclio
|
|
35
|
+
import nuclio.utils
|
|
36
36
|
import requests
|
|
37
37
|
import yaml
|
|
38
38
|
|
|
@@ -129,6 +129,7 @@ def new_project(
|
|
|
129
129
|
save: bool = True,
|
|
130
130
|
overwrite: bool = False,
|
|
131
131
|
parameters: dict = None,
|
|
132
|
+
default_function_node_selector: dict = None,
|
|
132
133
|
) -> "MlrunProject":
|
|
133
134
|
"""Create a new MLRun project, optionally load it from a yaml/zip/git template
|
|
134
135
|
|
|
@@ -182,6 +183,7 @@ def new_project(
|
|
|
182
183
|
:param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
|
|
183
184
|
if project with name exists
|
|
184
185
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
186
|
+
:param default_function_node_selector: defines the default node selector for scheduling functions within the project
|
|
185
187
|
|
|
186
188
|
:returns: project object
|
|
187
189
|
"""
|
|
@@ -228,6 +230,11 @@ def new_project(
|
|
|
228
230
|
project.spec.origin_url = url
|
|
229
231
|
if description:
|
|
230
232
|
project.spec.description = description
|
|
233
|
+
|
|
234
|
+
if default_function_node_selector:
|
|
235
|
+
for key, val in default_function_node_selector.items():
|
|
236
|
+
project.spec.default_function_node_selector[key] = val
|
|
237
|
+
|
|
231
238
|
if parameters:
|
|
232
239
|
# Enable setting project parameters at load time, can be used to customize the project_setup
|
|
233
240
|
for key, val in parameters.items():
|
|
@@ -2086,7 +2093,7 @@ class MlrunProject(ModelObj):
|
|
|
2086
2093
|
self,
|
|
2087
2094
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
2088
2095
|
name: str = "",
|
|
2089
|
-
kind: str = "",
|
|
2096
|
+
kind: str = "job",
|
|
2090
2097
|
image: str = None,
|
|
2091
2098
|
handler: str = None,
|
|
2092
2099
|
with_repo: bool = None,
|
|
@@ -2173,16 +2180,13 @@ class MlrunProject(ModelObj):
|
|
|
2173
2180
|
if func is None and not _has_module(handler, kind):
|
|
2174
2181
|
# if function path is not provided and it is not a module (no ".")
|
|
2175
2182
|
# use the current notebook as default
|
|
2176
|
-
if
|
|
2177
|
-
|
|
2178
|
-
"Function path or module must be specified (when not running inside a Notebook)"
|
|
2179
|
-
)
|
|
2180
|
-
from IPython import get_ipython
|
|
2183
|
+
if is_ipython:
|
|
2184
|
+
from IPython import get_ipython
|
|
2181
2185
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
+
kernel = get_ipython()
|
|
2187
|
+
func = nuclio.utils.notebook_file_name(kernel)
|
|
2188
|
+
if func.startswith(path.abspath(self.spec.context)):
|
|
2189
|
+
func = path.relpath(func, self.spec.context)
|
|
2186
2190
|
|
|
2187
2191
|
func = func or ""
|
|
2188
2192
|
|
|
@@ -3340,7 +3344,7 @@ class MlrunProject(ModelObj):
|
|
|
3340
3344
|
logger.warning(
|
|
3341
3345
|
f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
|
|
3342
3346
|
" To remove the function, attempt to manually delete it.",
|
|
3343
|
-
exc=
|
|
3347
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
3344
3348
|
)
|
|
3345
3349
|
|
|
3346
3350
|
return result
|
|
@@ -3688,7 +3692,10 @@ class MlrunProject(ModelObj):
|
|
|
3688
3692
|
self.spec.remove_custom_packager(packager=packager)
|
|
3689
3693
|
|
|
3690
3694
|
def store_api_gateway(
|
|
3691
|
-
self,
|
|
3695
|
+
self,
|
|
3696
|
+
api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
|
|
3697
|
+
wait_for_readiness=True,
|
|
3698
|
+
max_wait_time=90,
|
|
3692
3699
|
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
3693
3700
|
"""
|
|
3694
3701
|
Creates or updates a Nuclio API Gateway using the provided APIGateway object.
|
|
@@ -3699,7 +3706,11 @@ class MlrunProject(ModelObj):
|
|
|
3699
3706
|
Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
|
|
3700
3707
|
|
|
3701
3708
|
:param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the configuration
|
|
3702
|
-
of the API Gateway to be created
|
|
3709
|
+
of the API Gateway to be created or updated.
|
|
3710
|
+
:param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become ready
|
|
3711
|
+
after creation or update (default is True)
|
|
3712
|
+
:param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
|
|
3713
|
+
|
|
3703
3714
|
|
|
3704
3715
|
@return: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
|
|
3705
3716
|
information retrieved from the Nuclio API
|
|
@@ -3715,6 +3726,9 @@ class MlrunProject(ModelObj):
|
|
|
3715
3726
|
api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
|
|
3716
3727
|
api_gateway_json
|
|
3717
3728
|
)
|
|
3729
|
+
if wait_for_readiness:
|
|
3730
|
+
api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
|
|
3731
|
+
|
|
3718
3732
|
return api_gateway
|
|
3719
3733
|
|
|
3720
3734
|
def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
|
|
@@ -3743,7 +3757,8 @@ class MlrunProject(ModelObj):
|
|
|
3743
3757
|
mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
|
|
3744
3758
|
"""
|
|
3745
3759
|
|
|
3746
|
-
|
|
3760
|
+
gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
|
|
3761
|
+
return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
|
|
3747
3762
|
|
|
3748
3763
|
def delete_api_gateway(
|
|
3749
3764
|
self,
|
|
@@ -3933,10 +3948,6 @@ def _init_function_from_dict(
|
|
|
3933
3948
|
tag = f.get("tag", None)
|
|
3934
3949
|
|
|
3935
3950
|
has_module = _has_module(handler, kind)
|
|
3936
|
-
if not url and "spec" not in f and not has_module:
|
|
3937
|
-
# function must point to a file or a module or have a spec
|
|
3938
|
-
raise ValueError("Function missing a url or a spec or a module")
|
|
3939
|
-
|
|
3940
3951
|
relative_url = url
|
|
3941
3952
|
url, in_context = project.get_item_absolute_path(url)
|
|
3942
3953
|
|
|
@@ -3996,7 +4007,7 @@ def _init_function_from_dict(
|
|
|
3996
4007
|
tag=tag,
|
|
3997
4008
|
)
|
|
3998
4009
|
|
|
3999
|
-
elif
|
|
4010
|
+
elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
4000
4011
|
func = new_function(
|
|
4001
4012
|
name,
|
|
4002
4013
|
command=relative_url,
|
|
@@ -4005,7 +4016,7 @@ def _init_function_from_dict(
|
|
|
4005
4016
|
handler=handler,
|
|
4006
4017
|
tag=tag,
|
|
4007
4018
|
)
|
|
4008
|
-
if kind != mlrun.runtimes.RuntimeKinds.application:
|
|
4019
|
+
if image and kind != mlrun.runtimes.RuntimeKinds.application:
|
|
4009
4020
|
logger.info("Function code not specified, setting entry point to image")
|
|
4010
4021
|
func.from_image(image)
|
|
4011
4022
|
else:
|
mlrun/run.py
CHANGED
|
@@ -389,6 +389,8 @@ def import_function_to_dict(url, secrets=None):
|
|
|
389
389
|
code = get_in(runtime, "spec.build.functionSourceCode")
|
|
390
390
|
update_in(runtime, "metadata.build.code_origin", url)
|
|
391
391
|
cmd = code_file = get_in(runtime, "spec.command", "")
|
|
392
|
+
# use kind = "job" by default if not specified
|
|
393
|
+
runtime.setdefault("kind", "job")
|
|
392
394
|
if " " in cmd:
|
|
393
395
|
code_file = cmd[: cmd.find(" ")]
|
|
394
396
|
if runtime["kind"] in ["", "local"]:
|
|
@@ -535,7 +537,7 @@ def new_function(
|
|
|
535
537
|
if source:
|
|
536
538
|
runner.spec.build.source = source
|
|
537
539
|
if handler:
|
|
538
|
-
if kind in
|
|
540
|
+
if kind in RuntimeKinds.handlerless_runtimes():
|
|
539
541
|
raise MLRunInvalidArgumentError(
|
|
540
542
|
f"Handler is not supported for {kind} runtime"
|
|
541
543
|
)
|
|
@@ -628,6 +630,8 @@ def code_to_function(
|
|
|
628
630
|
- mpijob: run distributed Horovod jobs over the MPI job operator
|
|
629
631
|
- spark: run distributed Spark job using Spark Kubernetes Operator
|
|
630
632
|
- remote-spark: run distributed Spark job on remote Spark service
|
|
633
|
+
- databricks: run code on Databricks cluster (python scripts, Spark etc.)
|
|
634
|
+
- application: run a long living application (e.g. a web server, UI, etc.)
|
|
631
635
|
|
|
632
636
|
Learn more about [Kinds of function (runtimes)](../concepts/functions-overview.html).
|
|
633
637
|
|
mlrun/runtimes/__init__.py
CHANGED
|
@@ -154,6 +154,22 @@ class RuntimeKinds:
|
|
|
154
154
|
RuntimeKinds.application,
|
|
155
155
|
]
|
|
156
156
|
|
|
157
|
+
@staticmethod
|
|
158
|
+
def pure_nuclio_deployed_runtimes():
|
|
159
|
+
return [
|
|
160
|
+
RuntimeKinds.remote,
|
|
161
|
+
RuntimeKinds.nuclio,
|
|
162
|
+
RuntimeKinds.serving,
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def handlerless_runtimes():
|
|
167
|
+
return [
|
|
168
|
+
RuntimeKinds.serving,
|
|
169
|
+
# Application runtime handler is internal reverse proxy
|
|
170
|
+
RuntimeKinds.application,
|
|
171
|
+
]
|
|
172
|
+
|
|
157
173
|
@staticmethod
|
|
158
174
|
def local_runtimes():
|
|
159
175
|
return [
|
mlrun/runtimes/base.py
CHANGED
|
@@ -23,6 +23,7 @@ from typing import Callable, Optional, Union
|
|
|
23
23
|
import requests.exceptions
|
|
24
24
|
from nuclio.build import mlrun_footer
|
|
25
25
|
|
|
26
|
+
import mlrun.common.constants
|
|
26
27
|
import mlrun.common.schemas
|
|
27
28
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
28
29
|
import mlrun.db
|
|
@@ -634,7 +635,9 @@ class BaseRuntime(ModelObj):
|
|
|
634
635
|
image = image or self.spec.image or ""
|
|
635
636
|
|
|
636
637
|
image = enrich_image_url(image, client_version, client_python_version)
|
|
637
|
-
if not image.startswith(
|
|
638
|
+
if not image.startswith(
|
|
639
|
+
mlrun.common.constants.IMAGE_NAME_ENRICH_REGISTRY_PREFIX
|
|
640
|
+
):
|
|
638
641
|
return image
|
|
639
642
|
registry, repository = get_parsed_docker_registry()
|
|
640
643
|
if registry:
|
mlrun/runtimes/kubejob.py
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import time
|
|
16
15
|
import warnings
|
|
17
16
|
|
|
18
17
|
import mlrun.common.schemas
|
|
@@ -21,7 +20,6 @@ import mlrun.errors
|
|
|
21
20
|
|
|
22
21
|
from ..kfpops import build_op
|
|
23
22
|
from ..model import RunObject
|
|
24
|
-
from ..utils import get_in, logger
|
|
25
23
|
from .pod import KubeResource
|
|
26
24
|
|
|
27
25
|
|
|
@@ -65,29 +63,13 @@ class KubejobRuntime(KubeResource):
|
|
|
65
63
|
:param pull_at_runtime: load the archive into the container at job runtime vs on build/deploy
|
|
66
64
|
:param target_dir: target dir on runtime pod or repo clone / archive extraction
|
|
67
65
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if target_dir:
|
|
76
|
-
self.spec.build.source_code_target_dir = target_dir
|
|
77
|
-
|
|
78
|
-
self.spec.build.load_source_on_run = pull_at_runtime
|
|
79
|
-
if (
|
|
80
|
-
self.spec.build.base_image
|
|
81
|
-
and not self.spec.build.commands
|
|
82
|
-
and pull_at_runtime
|
|
83
|
-
and not self.spec.image
|
|
84
|
-
):
|
|
85
|
-
# if we load source from repo and don't need a full build use the base_image as the image
|
|
86
|
-
self.spec.image = self.spec.build.base_image
|
|
87
|
-
elif not pull_at_runtime:
|
|
88
|
-
# clear the image so build will not be skipped
|
|
89
|
-
self.spec.build.base_image = self.spec.build.base_image or self.spec.image
|
|
90
|
-
self.spec.image = ""
|
|
66
|
+
self._configure_mlrun_build_with_source(
|
|
67
|
+
source=source,
|
|
68
|
+
workdir=workdir,
|
|
69
|
+
handler=handler,
|
|
70
|
+
pull_at_runtime=pull_at_runtime,
|
|
71
|
+
target_dir=target_dir,
|
|
72
|
+
)
|
|
91
73
|
|
|
92
74
|
def build_config(
|
|
93
75
|
self,
|
|
@@ -169,116 +151,39 @@ class KubejobRuntime(KubeResource):
|
|
|
169
151
|
show_on_failure: bool = False,
|
|
170
152
|
force_build: bool = False,
|
|
171
153
|
) -> bool:
|
|
172
|
-
"""
|
|
154
|
+
"""Deploy function, build container with dependencies
|
|
173
155
|
|
|
174
|
-
:param watch:
|
|
175
|
-
:param with_mlrun:
|
|
176
|
-
:param skip_deployed:
|
|
177
|
-
:param is_kfp:
|
|
178
|
-
:param mlrun_version_specifier:
|
|
156
|
+
:param watch: Wait for the deploy to complete (and print build logs)
|
|
157
|
+
:param with_mlrun: Add the current mlrun package to the container build
|
|
158
|
+
:param skip_deployed: Skip the build if we already have an image for the function
|
|
159
|
+
:param is_kfp: Deploy as part of a kfp pipeline
|
|
160
|
+
:param mlrun_version_specifier: Which mlrun package version to include (if not current)
|
|
179
161
|
:param builder_env: Kaniko builder pod env vars dict (for config/credentials)
|
|
180
162
|
e.g. builder_env={"GIT_TOKEN": token}
|
|
181
|
-
:param show_on_failure:
|
|
182
|
-
:param force_build:
|
|
163
|
+
:param show_on_failure: Show logs only in case of build failure
|
|
164
|
+
:param force_build: Set True for force building the image, even when no changes were made
|
|
183
165
|
|
|
184
166
|
:return: True if the function is ready (deployed)
|
|
185
167
|
"""
|
|
186
168
|
|
|
187
169
|
build = self.spec.build
|
|
170
|
+
with_mlrun = self._resolve_build_with_mlrun(with_mlrun)
|
|
188
171
|
|
|
189
|
-
if with_mlrun is None:
|
|
190
|
-
if build.with_mlrun is not None:
|
|
191
|
-
with_mlrun = build.with_mlrun
|
|
192
|
-
else:
|
|
193
|
-
with_mlrun = build.base_image and not (
|
|
194
|
-
build.base_image.startswith("mlrun/")
|
|
195
|
-
or "/mlrun/" in build.base_image
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
not build.source
|
|
200
|
-
and not build.commands
|
|
201
|
-
and not build.requirements
|
|
202
|
-
and not build.extra
|
|
203
|
-
and with_mlrun
|
|
204
|
-
):
|
|
205
|
-
logger.info(
|
|
206
|
-
"Running build to add mlrun package, set "
|
|
207
|
-
"with_mlrun=False to skip if its already in the image"
|
|
208
|
-
)
|
|
209
172
|
self.status.state = ""
|
|
210
173
|
if build.base_image:
|
|
211
174
|
# clear the image so build will not be skipped
|
|
212
175
|
self.spec.image = ""
|
|
213
176
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
with_mlrun,
|
|
225
|
-
mlrun_version_specifier,
|
|
226
|
-
skip_deployed,
|
|
227
|
-
builder_env=builder_env,
|
|
228
|
-
force_build=force_build,
|
|
229
|
-
)
|
|
230
|
-
self.status = data["data"].get("status", None)
|
|
231
|
-
self.spec.image = get_in(data, "data.spec.image")
|
|
232
|
-
self.spec.build.base_image = self.spec.build.base_image or get_in(
|
|
233
|
-
data, "data.spec.build.base_image"
|
|
234
|
-
)
|
|
235
|
-
# Get the source target dir in case it was enriched due to loading source
|
|
236
|
-
self.spec.build.source_code_target_dir = get_in(
|
|
237
|
-
data, "data.spec.build.source_code_target_dir"
|
|
238
|
-
) or get_in(data, "data.spec.clone_target_dir")
|
|
239
|
-
ready = data.get("ready", False)
|
|
240
|
-
if not ready:
|
|
241
|
-
logger.info(
|
|
242
|
-
f"Started building image: {data.get('data', {}).get('spec', {}).get('build', {}).get('image')}"
|
|
243
|
-
)
|
|
244
|
-
if watch and not ready:
|
|
245
|
-
state = self._build_watch(watch, show_on_failure=show_on_failure)
|
|
246
|
-
ready = state == "ready"
|
|
247
|
-
self.status.state = state
|
|
248
|
-
|
|
249
|
-
if watch and not ready:
|
|
250
|
-
raise mlrun.errors.MLRunRuntimeError("Deploy failed")
|
|
251
|
-
return ready
|
|
252
|
-
|
|
253
|
-
def _build_watch(self, watch=True, logs=True, show_on_failure=False):
|
|
254
|
-
db = self._get_db()
|
|
255
|
-
offset = 0
|
|
256
|
-
try:
|
|
257
|
-
text, _ = db.get_builder_status(self, 0, logs=logs)
|
|
258
|
-
except mlrun.db.RunDBError:
|
|
259
|
-
raise ValueError("function or build process not found")
|
|
260
|
-
|
|
261
|
-
def print_log(text):
|
|
262
|
-
if text and (not show_on_failure or self.status.state == "error"):
|
|
263
|
-
print(text, end="")
|
|
264
|
-
|
|
265
|
-
print_log(text)
|
|
266
|
-
offset += len(text)
|
|
267
|
-
if watch:
|
|
268
|
-
while self.status.state in ["pending", "running"]:
|
|
269
|
-
time.sleep(2)
|
|
270
|
-
if show_on_failure:
|
|
271
|
-
text = ""
|
|
272
|
-
db.get_builder_status(self, 0, logs=False)
|
|
273
|
-
if self.status.state == "error":
|
|
274
|
-
# re-read the full log on failure
|
|
275
|
-
text, _ = db.get_builder_status(self, offset, logs=logs)
|
|
276
|
-
else:
|
|
277
|
-
text, _ = db.get_builder_status(self, offset, logs=logs)
|
|
278
|
-
print_log(text)
|
|
279
|
-
offset += len(text)
|
|
280
|
-
|
|
281
|
-
return self.status.state
|
|
177
|
+
return self._build_image(
|
|
178
|
+
builder_env=builder_env,
|
|
179
|
+
force_build=force_build,
|
|
180
|
+
mlrun_version_specifier=mlrun_version_specifier,
|
|
181
|
+
show_on_failure=show_on_failure,
|
|
182
|
+
skip_deployed=skip_deployed,
|
|
183
|
+
watch=watch,
|
|
184
|
+
is_kfp=is_kfp,
|
|
185
|
+
with_mlrun=with_mlrun,
|
|
186
|
+
)
|
|
282
187
|
|
|
283
188
|
def deploy_step(
|
|
284
189
|
self,
|
|
@@ -22,7 +22,8 @@ from requests.auth import HTTPBasicAuth
|
|
|
22
22
|
import mlrun
|
|
23
23
|
import mlrun.common.schemas
|
|
24
24
|
|
|
25
|
-
from
|
|
25
|
+
from ..utils import logger
|
|
26
|
+
from .function import RemoteRuntime, get_fullname, min_nuclio_versions
|
|
26
27
|
from .serving import ServingRuntime
|
|
27
28
|
|
|
28
29
|
NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
|
|
@@ -85,13 +86,14 @@ class BasicAuth(APIGatewayAuthenticator):
|
|
|
85
86
|
self,
|
|
86
87
|
) -> Optional[dict[str, Optional[mlrun.common.schemas.APIGatewayBasicAuth]]]:
|
|
87
88
|
return {
|
|
88
|
-
"
|
|
89
|
+
"basicAuth": mlrun.common.schemas.APIGatewayBasicAuth(
|
|
89
90
|
username=self._username, password=self._password
|
|
90
91
|
)
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
|
|
94
95
|
class APIGateway:
|
|
96
|
+
@min_nuclio_versions("1.13.1")
|
|
95
97
|
def __init__(
|
|
96
98
|
self,
|
|
97
99
|
project,
|
|
@@ -147,6 +149,7 @@ class APIGateway:
|
|
|
147
149
|
self.description = description
|
|
148
150
|
self.canary = canary
|
|
149
151
|
self.authentication = authentication
|
|
152
|
+
self.state = ""
|
|
150
153
|
|
|
151
154
|
def invoke(
|
|
152
155
|
self,
|
|
@@ -172,6 +175,11 @@ class APIGateway:
|
|
|
172
175
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
173
176
|
"Invocation url is not set. Set up gateway's `invoke_url` attribute."
|
|
174
177
|
)
|
|
178
|
+
if not self.is_ready():
|
|
179
|
+
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
180
|
+
f"API gateway is not ready. " f"Current state: {self.state}"
|
|
181
|
+
)
|
|
182
|
+
|
|
175
183
|
if (
|
|
176
184
|
self.authentication.authentication_mode
|
|
177
185
|
== NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
@@ -188,6 +196,33 @@ class APIGateway:
|
|
|
188
196
|
auth=HTTPBasicAuth(*auth) if auth else None,
|
|
189
197
|
)
|
|
190
198
|
|
|
199
|
+
def wait_for_readiness(self, max_wait_time=90):
|
|
200
|
+
"""
|
|
201
|
+
Wait for the API gateway to become ready within the maximum wait time.
|
|
202
|
+
|
|
203
|
+
Parameters:
|
|
204
|
+
max_wait_time: int - Maximum time to wait in seconds (default is 90 seconds).
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
bool: True if the entity becomes ready within the maximum wait time, False otherwise
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def _ensure_ready():
|
|
211
|
+
if not self.is_ready():
|
|
212
|
+
raise AssertionError(
|
|
213
|
+
f"Waiting for gateway readiness is taking more than {max_wait_time} seconds"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return mlrun.utils.helpers.retry_until_successful(
|
|
217
|
+
3, max_wait_time, logger, False, _ensure_ready
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def is_ready(self):
|
|
221
|
+
if self.state is not mlrun.common.schemas.api_gateway.APIGatewayState.ready:
|
|
222
|
+
# try to sync the state
|
|
223
|
+
self.sync()
|
|
224
|
+
return self.state == mlrun.common.schemas.api_gateway.APIGatewayState.ready
|
|
225
|
+
|
|
191
226
|
def sync(self):
|
|
192
227
|
"""
|
|
193
228
|
Synchronize the API gateway from the server.
|
|
@@ -201,6 +236,7 @@ class APIGateway:
|
|
|
201
236
|
self.functions = synced_gateway.functions
|
|
202
237
|
self.canary = synced_gateway.canary
|
|
203
238
|
self.description = synced_gateway.description
|
|
239
|
+
self.state = synced_gateway.state
|
|
204
240
|
|
|
205
241
|
def with_basic_auth(self, username: str, password: str):
|
|
206
242
|
"""
|
|
@@ -247,7 +283,12 @@ class APIGateway:
|
|
|
247
283
|
def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
|
|
248
284
|
project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
|
|
249
285
|
functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
|
|
250
|
-
|
|
286
|
+
state = (
|
|
287
|
+
api_gateway.status.state
|
|
288
|
+
if api_gateway.status
|
|
289
|
+
else mlrun.common.schemas.APIGatewayState.none
|
|
290
|
+
)
|
|
291
|
+
api_gateway = cls(
|
|
251
292
|
project=project,
|
|
252
293
|
description=api_gateway.spec.description,
|
|
253
294
|
name=api_gateway.spec.name,
|
|
@@ -257,15 +298,21 @@ class APIGateway:
|
|
|
257
298
|
functions=functions,
|
|
258
299
|
canary=canary,
|
|
259
300
|
)
|
|
301
|
+
api_gateway.state = state
|
|
302
|
+
return api_gateway
|
|
260
303
|
|
|
261
304
|
def to_scheme(self) -> mlrun.common.schemas.APIGateway:
|
|
262
305
|
upstreams = (
|
|
263
306
|
[
|
|
264
307
|
mlrun.common.schemas.APIGatewayUpstream(
|
|
265
|
-
nucliofunction={"name":
|
|
266
|
-
percentage=
|
|
267
|
-
)
|
|
268
|
-
|
|
308
|
+
nucliofunction={"name": self.functions[0]},
|
|
309
|
+
percentage=self.canary[0],
|
|
310
|
+
),
|
|
311
|
+
mlrun.common.schemas.APIGatewayUpstream(
|
|
312
|
+
# do not set percent for the second function,
|
|
313
|
+
# so we can define which function to display as a primary one in UI
|
|
314
|
+
nucliofunction={"name": self.functions[1]},
|
|
315
|
+
),
|
|
269
316
|
]
|
|
270
317
|
if self.canary
|
|
271
318
|
else [
|
|
@@ -300,7 +347,10 @@ class APIGateway:
|
|
|
300
347
|
|
|
301
348
|
:return: (str) The invoke URL.
|
|
302
349
|
"""
|
|
303
|
-
|
|
350
|
+
host = self.host
|
|
351
|
+
if not self.host.startswith("http"):
|
|
352
|
+
host = f"https://{self.host}"
|
|
353
|
+
return urljoin(host, self.path)
|
|
304
354
|
|
|
305
355
|
def _validate(
|
|
306
356
|
self,
|
|
@@ -223,7 +223,42 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
223
223
|
auth_info: AuthInfo = None,
|
|
224
224
|
builder_env: dict = None,
|
|
225
225
|
force_build: bool = False,
|
|
226
|
+
with_mlrun=None,
|
|
227
|
+
skip_deployed=False,
|
|
228
|
+
is_kfp=False,
|
|
229
|
+
mlrun_version_specifier=None,
|
|
230
|
+
show_on_failure: bool = False,
|
|
226
231
|
):
|
|
232
|
+
"""
|
|
233
|
+
Deploy function, builds the application image if required (self.requires_build()) or force_build is True,
|
|
234
|
+
Once the image is built, the function is deployed.
|
|
235
|
+
:param project: Project name
|
|
236
|
+
:param tag: Function tag
|
|
237
|
+
:param verbose: Set True for verbose logging
|
|
238
|
+
:param auth_info: Service AuthInfo (deprecated and ignored)
|
|
239
|
+
:param builder_env: Env vars dict for source archive config/credentials
|
|
240
|
+
e.g. builder_env={"GIT_TOKEN": token}
|
|
241
|
+
:param force_build: Set True for force building the application image
|
|
242
|
+
:param with_mlrun: Add the current mlrun package to the container build
|
|
243
|
+
:param skip_deployed: Skip the build if we already have an image for the function
|
|
244
|
+
:param is_kfp: Deploy as part of a kfp pipeline
|
|
245
|
+
:param mlrun_version_specifier: Which mlrun package version to include (if not current)
|
|
246
|
+
:param show_on_failure: Show logs only in case of build failure
|
|
247
|
+
:return: True if the function is ready (deployed)
|
|
248
|
+
"""
|
|
249
|
+
if self.requires_build() or force_build:
|
|
250
|
+
self._fill_credentials()
|
|
251
|
+
self._build_application_image(
|
|
252
|
+
builder_env=builder_env,
|
|
253
|
+
force_build=force_build,
|
|
254
|
+
watch=True,
|
|
255
|
+
with_mlrun=with_mlrun,
|
|
256
|
+
skip_deployed=skip_deployed,
|
|
257
|
+
is_kfp=is_kfp,
|
|
258
|
+
mlrun_version_specifier=mlrun_version_specifier,
|
|
259
|
+
show_on_failure=show_on_failure,
|
|
260
|
+
)
|
|
261
|
+
|
|
227
262
|
self._ensure_reverse_proxy_configurations()
|
|
228
263
|
self._configure_application_sidecar()
|
|
229
264
|
super().deploy(
|
|
@@ -232,7 +267,50 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
232
267
|
verbose,
|
|
233
268
|
auth_info,
|
|
234
269
|
builder_env,
|
|
235
|
-
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def with_source_archive(
|
|
273
|
+
self, source, workdir=None, pull_at_runtime=True, target_dir=None
|
|
274
|
+
):
|
|
275
|
+
"""load the code from git/tar/zip archive at runtime or build
|
|
276
|
+
|
|
277
|
+
:param source: valid absolute path or URL to git, zip, or tar file, e.g.
|
|
278
|
+
git://github.com/mlrun/something.git
|
|
279
|
+
http://some/url/file.zip
|
|
280
|
+
note path source must exist on the image or exist locally when run is local
|
|
281
|
+
(it is recommended to use 'workdir' when source is a filepath instead)
|
|
282
|
+
:param workdir: working dir relative to the archive root (e.g. './subdir') or absolute to the image root
|
|
283
|
+
:param pull_at_runtime: load the archive into the container at job runtime vs on build/deploy
|
|
284
|
+
:param target_dir: target dir on runtime pod or repo clone / archive extraction
|
|
285
|
+
"""
|
|
286
|
+
self._configure_mlrun_build_with_source(
|
|
287
|
+
source=source,
|
|
288
|
+
workdir=workdir,
|
|
289
|
+
pull_at_runtime=pull_at_runtime,
|
|
290
|
+
target_dir=target_dir,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _build_application_image(
|
|
294
|
+
self,
|
|
295
|
+
builder_env: dict = None,
|
|
296
|
+
force_build: bool = False,
|
|
297
|
+
watch=True,
|
|
298
|
+
with_mlrun=None,
|
|
299
|
+
skip_deployed=False,
|
|
300
|
+
is_kfp=False,
|
|
301
|
+
mlrun_version_specifier=None,
|
|
302
|
+
show_on_failure: bool = False,
|
|
303
|
+
):
|
|
304
|
+
with_mlrun = self._resolve_build_with_mlrun(with_mlrun)
|
|
305
|
+
return self._build_image(
|
|
306
|
+
builder_env=builder_env,
|
|
307
|
+
force_build=force_build,
|
|
308
|
+
mlrun_version_specifier=mlrun_version_specifier,
|
|
309
|
+
show_on_failure=show_on_failure,
|
|
310
|
+
skip_deployed=skip_deployed,
|
|
311
|
+
watch=watch,
|
|
312
|
+
is_kfp=is_kfp,
|
|
313
|
+
with_mlrun=with_mlrun,
|
|
236
314
|
)
|
|
237
315
|
|
|
238
316
|
def _ensure_reverse_proxy_configurations(self):
|
|
@@ -39,11 +39,19 @@ func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
|
|
|
39
39
|
for k, v := range event.GetHeaders() {
|
|
40
40
|
httpRequest.Header[k] = []string{v.(string)}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
// populate query params
|
|
44
|
+
query := httpRequest.URL.Query()
|
|
45
|
+
for k, v := range event.GetFields() {
|
|
46
|
+
query.Set(k, v.(string))
|
|
47
|
+
}
|
|
48
|
+
httpRequest.URL.RawQuery = query.Encode()
|
|
49
|
+
|
|
42
50
|
recorder := httptest.NewRecorder()
|
|
43
51
|
reverseProxy.ServeHTTP(recorder, httpRequest)
|
|
44
52
|
|
|
45
53
|
// send request to sidecar
|
|
46
|
-
context.Logger.
|
|
54
|
+
context.Logger.DebugWith("Forwarding request to sidecar", "sidecarUrl", sidecarUrl, "query", httpRequest.URL.Query())
|
|
47
55
|
response := recorder.Result()
|
|
48
56
|
|
|
49
57
|
headers := make(map[string]interface{})
|