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() )
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", )
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"
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)
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
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)
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
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()
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()
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"
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"
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"
def test_non_deployments(self): with pytest.raises(TypeError): Application([self.f, 5, "hello"])
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")])
# 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(
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