Esempio n. 1
0
    def test_deploy_from_yaml(self, serve_instance):
        config_file_name = os.path.join(
            os.path.dirname(__file__), "test_config_files", "two_deployments.yaml"
        )

        # Check if yaml string and yaml file both produce the same Application
        with open(config_file_name, "r") as f:
            app1 = Application.from_yaml(f)
        with open(config_file_name, "r") as f:
            yaml_str = f.read()
        app2 = Application.from_yaml(yaml_str)
        compare_specified_options(app1.to_dict(), app2.to_dict())

        # Check that deployment works
        serve.run(app1)
        assert (
            requests.get("http://localhost:8000/shallow").text == "Hello shallow world!"
        )
        assert requests.get("http://localhost:8000/one").text == "2"

        # Check if yaml string output is same as the Application
        recreated_app = Application.from_yaml(app1.to_yaml())
        compare_specified_options(recreated_app.to_dict(), app1.to_dict())

        # Check if yaml file output is same as the Application
        with tempfile.TemporaryFile(mode="w+") as tmp:
            app1.to_yaml(tmp)
            tmp.seek(0)
            compare_specified_options(
                Application.from_yaml(tmp).to_dict(), app1.to_dict()
            )
Esempio n. 2
0
    async def get_all_deployments(self, req: Request) -> Response:
        from ray.serve.api import Application, list_deployments

        app = Application(list(list_deployments().values()))
        return Response(
            text=json.dumps(app.to_dict()),
            content_type="application/json",
        )
Esempio n. 3
0
    def test_convert_to_import_path(self, serve_instance):
        f = decorated_func.options(name="f")
        C = DecoratedClass.options(name="C")
        app = Application([f, C])

        reconstructed_app = Application.from_yaml(app.to_yaml())

        serve.run(reconstructed_app)
        assert requests.get("http://localhost:8000/f").text == "got decorated func"
        assert requests.get("http://localhost:8000/C").text == "got decorated class"
Esempio n. 4
0
    def deploy_and_check_responses(
        self, deployments, responses, blocking=True, client=None
    ):
        """
        Helper function that deploys the list of deployments, calls them with
        their handles, and checks whether they return the objects in responses.
        If blocking is False, this function uses a non-blocking deploy and uses
        the client to wait until the deployments finish deploying.
        """

        serve.run(Application(deployments), _blocking=blocking)

        def check_all_deployed():
            try:
                for deployment, response in zip(deployments, responses):
                    if ray.get(deployment.get_handle().remote()) != response:
                        return False
            except Exception:
                return False

            return True

        if blocking:
            # If blocking, this should be guaranteed to pass immediately.
            assert check_all_deployed()
        else:
            # If non-blocking, this should pass eventually.
            wait_for_condition(check_all_deployed)
Esempio n. 5
0
    def test_valid_deployments(self):
        app = Application([self.f, self.C])

        assert len(app.deployments) == 2
        app_deployment_names = {d.name for d in app.deployments.values()}
        assert "f" in app_deployment_names
        assert "C" in app_deployment_names
Esempio n. 6
0
    def test_invalid_input(self, serve_instance):
        """
        Checks Application's deploy behavior when deployment group contains
        non-Deployment objects.
        """

        with pytest.raises(TypeError):
            Application([self.f, self.C,
                         "not a Deployment object"]).deploy(blocking=True)
Esempio n. 7
0
def test_run_get_ingress_app(serve_instance):
    """Check that serve.run() with an app returns the ingress."""
    @serve.deployment(route_prefix=None)
    def f():
        return "got f"

    @serve.deployment(route_prefix="/g")
    def g():
        return "got g"

    app = Application([f, g])
    ingress_handle = serve.run(app)

    assert ray.get(ingress_handle.remote()) == "got g"
    serve_instance.delete_deployments(["f", "g"])

    no_ingress_app = Application([f.options(route_prefix="/f"), g])
    ingress_handle = serve.run(no_ingress_app)
    assert ingress_handle is None
Esempio n. 8
0
    async def put_all_deployments(self, req: Request) -> Response:
        app = Application.from_dict(await req.json())
        serve.run(app, _blocking=False)

        new_names = set()
        for deployment in app.deployments.values():
            new_names.add(deployment.name)

        all_deployments = serve.list_deployments()
        all_names = set(all_deployments.keys())
        names_to_delete = all_names.difference(new_names)
        internal_get_global_client().delete_deployments(names_to_delete)

        return Response()
Esempio n. 9
0
def run(
    config_or_import_path: str,
    runtime_env: str,
    runtime_env_json: str,
    working_dir: str,
    app_dir: str,
    address: str,
    host: str,
    port: int,
    blocking: bool,
):
    sys.path.insert(0, app_dir)

    final_runtime_env = parse_runtime_env_args(
        runtime_env=runtime_env,
        runtime_env_json=runtime_env_json,
        working_dir=working_dir,
    )

    app_or_node = None
    if pathlib.Path(config_or_import_path).is_file():
        config_path = config_or_import_path
        cli_logger.print(f"Loading app from config file: '{config_path}'.")
        with open(config_path, "r") as config_file:
            app_or_node = Application.from_yaml(config_file)
    else:
        import_path = config_or_import_path
        cli_logger.print(f"Loading app from import path: '{import_path}'.")
        app_or_node = import_attr(import_path)

    # Setting the runtime_env here will set defaults for the deployments.
    ray.init(address=address, namespace="serve", runtime_env=final_runtime_env)

    try:
        serve.run(app_or_node, host=host, port=port)
        cli_logger.success("Deployed successfully!\n")

        if blocking:
            while True:
                statuses = serve_application_status_to_schema(
                    get_deployment_statuses()
                ).json(indent=4)
                cli_logger.info(f"{statuses}")
                time.sleep(10)

    except KeyboardInterrupt:
        cli_logger.info("Got KeyboardInterrupt, shutting down...")
        serve.shutdown()
        sys.exit()
Esempio n. 10
0
    def test_deploy_from_dict(self, serve_instance):
        config_file_name = os.path.join(os.path.dirname(__file__),
                                        "test_config_files",
                                        "two_deployments.yaml")

        with open(config_file_name, "r") as config_file:
            config_dict = yaml.safe_load(config_file)

        app = Application.from_dict(config_dict)
        app_dict = app.to_dict()

        compare_specified_options(config_dict, app_dict)

        serve.run(app.from_dict(app_dict))

        assert (requests.get("http://localhost:8000/shallow").text ==
                "Hello shallow world!")
        assert requests.get("http://localhost:8000/one").text == "2"
Esempio n. 11
0
def test_immutable_deployment_list(serve_instance):
    config_file_name = os.path.join(
        os.path.dirname(__file__), "test_config_files", "two_deployments.yaml"
    )

    with open(config_file_name, "r") as f:
        app = Application.from_yaml(f)

    assert len(app.deployments.values()) == 2

    for name in app.deployments.keys():
        with pytest.raises(RuntimeError):
            app.deployments[name] = app.deployments[name].options(name="sneaky")

    for deployment in app.deployments.values():
        deployment.deploy()

    assert requests.get("http://localhost:8000/shallow").text == "Hello shallow world!"
    assert requests.get("http://localhost:8000/one").text == "2"
Esempio n. 12
0
    def test_mutual_handles(self, serve_instance):
        """
        Atomically deploys a group of deployments that get handles to other
        deployments in the group inside their __init__ functions. The handle
        references should fail in a non-atomic deployment. Checks whether the
        deployments deploy correctly.
        """

        @serve.deployment
        class MutualHandles:
            async def __init__(self, handle_name):
                self.handle = serve.get_deployment(handle_name).get_handle()

            async def __call__(self, echo: str):
                return await self.handle.request_echo.remote(echo)

            async def request_echo(self, echo: str):
                return echo

        names = []
        for i in range(10):
            names.append("a" * i)

        deployments = []
        for idx in range(len(names)):
            # Each deployment will hold a ServeHandle with the next name in
            # the list
            deployment_name = names[idx]
            handle_name = names[(idx + 1) % len(names)]

            deployments.append(
                MutualHandles.options(name=deployment_name, init_args=(handle_name,))
            )

        serve.run(Application(deployments), _blocking=True)

        for deployment in deployments:
            assert (ray.get(deployment.get_handle().remote("hello"))) == "hello"
Esempio n. 13
0
 def test_non_deployments(self):
     with pytest.raises(TypeError):
         Application([self.f, 5, "hello"])
Esempio n. 14
0
    def test_repeated_deployment_names(self):
        with pytest.raises(ValueError):
            Application([self.f, self.C.options(name="f")])

        with pytest.raises(ValueError):
            Application([self.C, self.f.options(name="C")])
Esempio n. 15
0
    # Check idempotence
    for _ in range(2):
        subprocess.check_output(["serve", "deploy", config_file_name])
        wait_for_condition(lambda: get_num_deployments() == 2, timeout=35)

        subprocess.check_output(["serve", "delete", "-y"])
        wait_for_condition(lambda: get_num_deployments() == 0, timeout=35)


@serve.deployment
def parrot(request):
    return request.query_params["sound"]


parrot_app = Application([parrot])


@pytest.mark.skipif(sys.platform == "win32",
                    reason="File path incorrect on Windows.")
def test_run_application(ray_start_stop):
    # Deploys valid config file and import path via serve run

    # Deploy via config file
    config_file_name = os.path.join(os.path.dirname(__file__),
                                    "test_config_files",
                                    "two_deployments.yaml")

    p = subprocess.Popen(["serve", "run", "--address=auto", config_file_name])
    wait_for_condition(lambda: ping_endpoint("one") == "2", timeout=10)
    wait_for_condition(
Esempio n. 16
0
def maybe_build(node: DeploymentNode,
                use_build: bool) -> Union[Application, DeploymentNode]:
    if use_build:
        return Application.from_dict(build_app(node).to_dict())
    else:
        return node