chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
{auto_gen_header}
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>io.ray</groupId>
|
||||
<artifactId>ray-superpom</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ray-serve</artifactId>
|
||||
<name>ray serve</name>
|
||||
<description>java for ray serve</description>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.ray</groupId>
|
||||
<artifactId>ray-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.ray</groupId>
|
||||
<artifactId>ray-runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
{generated_bzl_deps}
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,420 @@
|
||||
package io.ray.serve.api;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.exception.RayActorException;
|
||||
import io.ray.api.exception.RayTimeoutException;
|
||||
import io.ray.api.function.PyActorClass;
|
||||
import io.ray.api.function.PyActorMethod;
|
||||
import io.ray.api.options.ActorLifetime;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.dag.Graph;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.deployment.DeploymentCreator;
|
||||
import io.ray.serve.deployment.DeploymentRoute;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.ActorNameList;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import io.ray.serve.poll.LongPollClientFactory;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import io.ray.serve.util.CollectionUtil;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Ray Serve global API. TODO: will be riched in the Java SDK/API PR. */
|
||||
public class Serve {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Serve.class);
|
||||
|
||||
private static ReplicaContext INTERNAL_REPLICA_CONTEXT;
|
||||
|
||||
private static ServeControllerClient GLOBAL_CLIENT;
|
||||
|
||||
/**
|
||||
* Initialize a serve instance.
|
||||
*
|
||||
* @param config Configuration options for Serve.
|
||||
* @return
|
||||
*/
|
||||
public static synchronized ServeControllerClient start(Map<String, String> config) {
|
||||
return serveStart(config);
|
||||
}
|
||||
|
||||
private static synchronized ServeControllerClient serveStart(Map<String, String> config) {
|
||||
|
||||
try {
|
||||
ServeControllerClient client = getGlobalClient(true);
|
||||
LOGGER.info("Connecting to existing Serve app in namespace {}", Constants.SERVE_NAMESPACE);
|
||||
return client;
|
||||
} catch (RayServeException | IllegalStateException e) {
|
||||
LOGGER.info(
|
||||
"There is no Serve instance running on this Ray cluster. A new one will be started.");
|
||||
}
|
||||
|
||||
// Initialize ray if needed.
|
||||
if (!Ray.isInitialized()) {
|
||||
init();
|
||||
}
|
||||
|
||||
int httpPort =
|
||||
Optional.ofNullable(config)
|
||||
.map(m -> m.get(RayServeConfig.PROXY_HTTP_PORT))
|
||||
.map(Integer::parseInt)
|
||||
.orElse(Integer.valueOf(System.getProperty(RayServeConfig.PROXY_HTTP_PORT, "8000")));
|
||||
PyActorHandle controllerAvatar =
|
||||
Ray.actor(
|
||||
PyActorClass.of("ray.serve._private.controller_avatar", "ServeControllerAvatar"),
|
||||
httpPort)
|
||||
.setName(Constants.SERVE_CONTROLLER_NAME + "_AVATAR")
|
||||
.setLifetime(ActorLifetime.DETACHED)
|
||||
.setMaxRestarts(-1)
|
||||
.setMaxConcurrency(1)
|
||||
.remote();
|
||||
|
||||
controllerAvatar.task(PyActorMethod.of("check_alive")).remote().get();
|
||||
|
||||
PyActorHandle controller =
|
||||
(PyActorHandle)
|
||||
Ray.getActor(Constants.SERVE_CONTROLLER_NAME, Constants.SERVE_NAMESPACE).get();
|
||||
|
||||
ActorNameList actorNameList =
|
||||
ServeProtoUtil.bytesToProto(
|
||||
(byte[]) controller.task(PyActorMethod.of("get_proxy_names")).remote().get(),
|
||||
ActorNameList::parseFrom);
|
||||
if (actorNameList != null && !CollectionUtil.isEmpty(actorNameList.getNamesList())) {
|
||||
try {
|
||||
for (String name : actorNameList.getNamesList()) {
|
||||
PyActorHandle proxyActorHandle =
|
||||
(PyActorHandle) Ray.getActor(name, Constants.SERVE_NAMESPACE).get();
|
||||
proxyActorHandle
|
||||
.task(PyActorMethod.of("ready"))
|
||||
.remote()
|
||||
.get(Constants.PROXY_TIMEOUT_S * 1000);
|
||||
}
|
||||
} catch (RayTimeoutException e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"HTTP proxies not available after {}s.", Constants.PROXY_TIMEOUT_S);
|
||||
LOGGER.error(errMsg, e);
|
||||
throw new RayServeException(errMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
ServeControllerClient client = new ServeControllerClient(controller);
|
||||
setGlobalClient(client);
|
||||
LOGGER.info("Started Serve in namespace {}", Constants.SERVE_NAMESPACE);
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely shut down the connected Serve instance.
|
||||
*
|
||||
* <p>Shuts down all processes and deletes all state associated with the instance.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
ServeControllerClient client = null;
|
||||
try {
|
||||
client = getGlobalClient();
|
||||
} catch (RayServeException | IllegalStateException e) {
|
||||
LOGGER.info(
|
||||
"Nothing to shut down. There's no Serve application running on this Ray cluster.");
|
||||
return;
|
||||
}
|
||||
|
||||
LongPollClientFactory.stop();
|
||||
client.shutdown(null);
|
||||
clearContext();
|
||||
}
|
||||
|
||||
public static void clearContext() {
|
||||
setGlobalClient(null);
|
||||
setInternalReplicaContext(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a Serve deployment.
|
||||
*
|
||||
* @return DeploymentCreator
|
||||
*/
|
||||
public static DeploymentCreator deployment() {
|
||||
return new DeploymentCreator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set replica information to global context.
|
||||
*
|
||||
* @param deploymentName deployment name
|
||||
* @param replicaTag replica tag
|
||||
* @param servableObject the servable object of the specified replica.
|
||||
* @param config
|
||||
*/
|
||||
public static void setInternalReplicaContext(
|
||||
String deploymentName,
|
||||
String replicaTag,
|
||||
Object servableObject,
|
||||
Map<String, String> config,
|
||||
String appName) {
|
||||
INTERNAL_REPLICA_CONTEXT =
|
||||
new ReplicaContext(deploymentName, replicaTag, servableObject, config, appName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set replica information to global context.
|
||||
*
|
||||
* @param replicaContext
|
||||
*/
|
||||
public static void setInternalReplicaContext(ReplicaContext replicaContext) {
|
||||
INTERNAL_REPLICA_CONTEXT = replicaContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* If called from a deployment, returns the deployment and replica tag.
|
||||
*
|
||||
* <p>A replica tag uniquely identifies a single replica for a Ray Serve deployment at runtime.
|
||||
* Replica tags are of the form `<deployment_name>#<random letters>`.
|
||||
*
|
||||
* @return the replica context if it exists, or throw RayServeException.
|
||||
*/
|
||||
public static ReplicaContext getReplicaContext() {
|
||||
if (INTERNAL_REPLICA_CONTEXT == null) {
|
||||
throw new RayServeException(
|
||||
"`Serve.getReplicaContext()` may only be called from within a Ray Serve deployment.");
|
||||
}
|
||||
return INTERNAL_REPLICA_CONTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global client, which stores the controller's handle.
|
||||
*
|
||||
* @param healthCheckController If True, run a health check on the cached controller if it exists.
|
||||
* If the check fails, try reconnecting to the controller.
|
||||
* @return ServeControllerClient to the running Serve controller. If there is no running
|
||||
* controller and raise_if_no_controller_running is set to False, returns None.
|
||||
*/
|
||||
public static ServeControllerClient getGlobalClient(boolean healthCheckController) {
|
||||
try {
|
||||
if (GLOBAL_CLIENT != null) {
|
||||
if (healthCheckController) {
|
||||
((PyActorHandle) GLOBAL_CLIENT.getController())
|
||||
.task(PyActorMethod.of("check_alive"))
|
||||
.remote();
|
||||
}
|
||||
return GLOBAL_CLIENT;
|
||||
}
|
||||
} catch (RayActorException e) {
|
||||
LOGGER.info("The cached controller has died. Reconnecting.");
|
||||
setGlobalClient(null);
|
||||
}
|
||||
return connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global client, which stores the controller's handle.
|
||||
*
|
||||
* @return ServeControllerClient to the running Serve controller. If there is no running
|
||||
* controller and raise_if_no_controller_running is set to False, returns None.
|
||||
*/
|
||||
public static ServeControllerClient getGlobalClient() {
|
||||
return getGlobalClient(false);
|
||||
}
|
||||
|
||||
private static void setGlobalClient(ServeControllerClient client) {
|
||||
GLOBAL_CLIENT = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to an existing Serve instance on this Ray cluster.
|
||||
*
|
||||
* <p>If calling from the driver program, the Serve instance on this Ray cluster must first have
|
||||
* been initialized using `Serve.start`.
|
||||
*
|
||||
* <p>If called from within a replica, this will connect to the same Serve instance that the
|
||||
* replica is running in.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static synchronized ServeControllerClient connect() {
|
||||
|
||||
if (GLOBAL_CLIENT != null) {
|
||||
return GLOBAL_CLIENT;
|
||||
}
|
||||
|
||||
// Initialize ray if needed.
|
||||
if (!Ray.isInitialized()) {
|
||||
init();
|
||||
}
|
||||
|
||||
Optional<BaseActorHandle> optional =
|
||||
Ray.getActor(Constants.SERVE_CONTROLLER_NAME, Constants.SERVE_NAMESPACE);
|
||||
Preconditions.checkState(
|
||||
optional.isPresent(),
|
||||
MessageFormatter.format(
|
||||
"There is no instance running on this Ray cluster. "
|
||||
+ "Please call `serve.start() to start one."));
|
||||
LOGGER.info(
|
||||
"Got controller handle with name `{}` in namespace `{}`.",
|
||||
Constants.SERVE_CONTROLLER_NAME,
|
||||
Constants.SERVE_NAMESPACE);
|
||||
|
||||
ServeControllerClient client = new ServeControllerClient(optional.get());
|
||||
|
||||
setGlobalClient(client);
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically fetch a handle to a Deployment object.
|
||||
*
|
||||
* <p>This can be used to update and redeploy a deployment without access to the original
|
||||
* definition.
|
||||
*
|
||||
* @param name name of the deployment. This must have already been deployed.
|
||||
* @return Deployment
|
||||
* @deprecated {@value Constants#MIGRATION_MESSAGE}
|
||||
*/
|
||||
@Deprecated
|
||||
public static Deployment getDeployment(String name) {
|
||||
LOGGER.warn(Constants.MIGRATION_MESSAGE);
|
||||
DeploymentRoute deploymentRoute = getGlobalClient().getDeploymentInfo(name);
|
||||
if (deploymentRoute == null) {
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Deployment {} was not found. Did you call Deployment.deploy?", name));
|
||||
}
|
||||
|
||||
// TODO use DeploymentCreator
|
||||
return new Deployment(
|
||||
name,
|
||||
deploymentRoute.getDeploymentInfo().getDeploymentConfig(),
|
||||
deploymentRoute.getDeploymentInfo().getReplicaConfig(),
|
||||
deploymentRoute.getDeploymentInfo().getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an application and return a handle to its ingress deployment.
|
||||
*
|
||||
* @param target A Serve application returned by `Deployment.bind()`.
|
||||
* @return A handle that can be used to call the application.
|
||||
*/
|
||||
public static DeploymentHandle run(Application target) {
|
||||
return run(target, true, Constants.SERVE_DEFAULT_APP_NAME, null, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an application and return a handle to its ingress deployment.
|
||||
*
|
||||
* @param target A Serve application returned by `Deployment.bind()`.
|
||||
* @param blocking
|
||||
* @param name Application name. If not provided, this will be the only application running on the
|
||||
* cluster (it will delete all others).
|
||||
* @param routePrefix Route prefix for HTTP requests. Defaults to '/'.
|
||||
* @param config
|
||||
* @param externalScalerEnabled If true, indicates that an external autoscaler will manage replica
|
||||
* scaling for this application. Defaults to false.
|
||||
* @return A handle that can be used to call the application.
|
||||
*/
|
||||
public static DeploymentHandle run(
|
||||
Application target,
|
||||
boolean blocking,
|
||||
String name,
|
||||
String routePrefix,
|
||||
Map<String, String> config,
|
||||
boolean externalScalerEnabled) {
|
||||
|
||||
if (StringUtils.isBlank(name)) {
|
||||
throw new RayServeException("Application name must a non-empty string.");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(routePrefix)) {
|
||||
Preconditions.checkArgument(
|
||||
routePrefix.startsWith("/"), "The route_prefix must start with a forward slash ('/')");
|
||||
} else {
|
||||
routePrefix = "/";
|
||||
}
|
||||
|
||||
ServeControllerClient client = serveStart(config);
|
||||
|
||||
List<Deployment> deployments = Graph.build(target.getInternalDagNode(), name);
|
||||
Deployment ingressDeployment = deployments.get(deployments.size() - 1);
|
||||
|
||||
for (Deployment deployment : deployments) {
|
||||
// Overwrite route prefix
|
||||
deployment
|
||||
.getDeploymentConfig()
|
||||
.setVersion(
|
||||
StringUtils.isNotBlank(deployment.getVersion())
|
||||
? deployment.getVersion()
|
||||
: RandomStringUtils.randomAlphabetic(6));
|
||||
}
|
||||
|
||||
client.deployApplication(
|
||||
name,
|
||||
routePrefix,
|
||||
deployments,
|
||||
ingressDeployment.getName(),
|
||||
blocking,
|
||||
externalScalerEnabled);
|
||||
return client.getDeploymentHandle(ingressDeployment.getName(), name, true);
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
System.setProperty("ray.job.namespace", Constants.SERVE_NAMESPACE);
|
||||
Ray.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handle to the application's ingress deployment by name.
|
||||
*
|
||||
* @param name application name
|
||||
* @return
|
||||
*/
|
||||
public static DeploymentHandle getAppHandle(String name) {
|
||||
ServeControllerClient client = getGlobalClient();
|
||||
String ingress =
|
||||
(String)
|
||||
((PyActorHandle) client.getController())
|
||||
.task(PyActorMethod.of("get_ingress_deployment_name"), name)
|
||||
.remote()
|
||||
.get();
|
||||
|
||||
if (StringUtils.isBlank(ingress)) {
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format("Application '{}' does not exist.", ingress));
|
||||
}
|
||||
return client.getDeploymentHandle(ingress, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an application by its name.
|
||||
*
|
||||
* <p>Deletes the app with all corresponding deployments.
|
||||
*
|
||||
* @param name application name
|
||||
*/
|
||||
public static void delete(String name) {
|
||||
delete(name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an application by its name.
|
||||
*
|
||||
* <p>Deletes the app with all corresponding deployments.
|
||||
*
|
||||
* @param name application name
|
||||
* @param blocking Wait for the application to be deleted or not.
|
||||
*/
|
||||
public static void delete(String name, boolean blocking) {
|
||||
getGlobalClient().deleteApps(Arrays.asList(name), blocking);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
package io.ray.serve.api;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.exception.RayActorException;
|
||||
import io.ray.api.exception.RayTimeoutException;
|
||||
import io.ray.api.function.PyActorMethod;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.controller.ServeController;
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.deployment.DeploymentRoute;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.ApplicationArgs;
|
||||
import io.ray.serve.generated.ApplicationStatus;
|
||||
import io.ray.serve.generated.DeploymentArgs;
|
||||
import io.ray.serve.generated.EndpointInfo;
|
||||
import io.ray.serve.generated.StatusOverview;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import io.ray.serve.util.CollectionUtil;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ServeControllerClient {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServeControllerClient.class);
|
||||
|
||||
private static long CLIENT_POLLING_INTERVAL_S = 1;
|
||||
|
||||
private BaseActorHandle controller; // TODO change to PyActorHandle
|
||||
|
||||
private boolean shutdown;
|
||||
|
||||
private Map<String, DeploymentHandle> handleCache = new ConcurrentHashMap<>();
|
||||
|
||||
private String rootUrl;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ServeControllerClient(BaseActorHandle controller) {
|
||||
this.controller = controller;
|
||||
this.rootUrl =
|
||||
controller instanceof PyActorHandle
|
||||
? (String)
|
||||
((PyActorHandle) controller).task(PyActorMethod.of("get_root_url")).remote().get()
|
||||
: ((ActorHandle<ServeController>) controller)
|
||||
.task(ServeController::getRootUrl)
|
||||
.remote()
|
||||
.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve DeploymentHandle for service deployment to invoke it from Java.
|
||||
*
|
||||
* @param deploymentName A registered service deployment.
|
||||
* @param appName application name
|
||||
* @param missingOk If true, then Serve won't check the deployment is registered.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DeploymentHandle getDeploymentHandle(
|
||||
String deploymentName, String appName, boolean missingOk) {
|
||||
String cacheKey =
|
||||
StringUtils.join(
|
||||
new Object[] {deploymentName, appName, missingOk}, Constants.SEPARATOR_HASH);
|
||||
if (handleCache.containsKey(cacheKey)) {
|
||||
return handleCache.get(cacheKey);
|
||||
}
|
||||
|
||||
Map<String, EndpointInfo> endpoints = null;
|
||||
if (controller instanceof PyActorHandle) {
|
||||
endpoints =
|
||||
ServeProtoUtil.parseEndpointSet(
|
||||
(byte[])
|
||||
((PyActorHandle) controller)
|
||||
.task(PyActorMethod.of(Constants.CONTROLLER_GET_ALL_ENDPOINTS_METHOD))
|
||||
.remote()
|
||||
.get());
|
||||
} else {
|
||||
LOGGER.warn("Client currently only supports the Python controller.");
|
||||
endpoints =
|
||||
ServeProtoUtil.parseEndpointSet(
|
||||
((ActorHandle<? extends ServeController>) controller)
|
||||
.task(ServeController::getAllEndpoints)
|
||||
.remote()
|
||||
.get());
|
||||
}
|
||||
|
||||
if (!missingOk && (endpoints == null || !endpoints.containsKey(deploymentName))) {
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format("Deployment {} does not exist.", deploymentName));
|
||||
}
|
||||
|
||||
DeploymentHandle handle = new DeploymentHandle(deploymentName, appName);
|
||||
handleCache.put(cacheKey, handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely shut down the connected Serve instance.
|
||||
*
|
||||
* <p>Shuts down all processes and deletes all state associated with the instance.
|
||||
*
|
||||
* @param timeoutS The unit is second.
|
||||
*/
|
||||
public synchronized void shutdown(Long timeoutS) {
|
||||
if (Ray.isInitialized() && !shutdown) {
|
||||
|
||||
if (timeoutS == null) {
|
||||
timeoutS = 30L;
|
||||
}
|
||||
|
||||
try {
|
||||
((PyActorHandle) controller)
|
||||
.task(PyActorMethod.of("graceful_shutdown"))
|
||||
.remote()
|
||||
.get(timeoutS * 1000);
|
||||
} catch (RayActorException e) {
|
||||
// Controller has been shut down.
|
||||
return;
|
||||
} catch (RayTimeoutException e) {
|
||||
LOGGER.warn(
|
||||
"Controller failed to shut down within {}s. Check controller logs for more details.",
|
||||
timeoutS);
|
||||
}
|
||||
shutdown = true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated {@value Constants#MIGRATION_MESSAGE}
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public DeploymentRoute getDeploymentInfo(String name) {
|
||||
return DeploymentRoute.fromProtoBytes(
|
||||
(byte[])
|
||||
((PyActorHandle) controller)
|
||||
.task(PyActorMethod.of("get_deployment_info"), name)
|
||||
.remote()
|
||||
.get());
|
||||
}
|
||||
|
||||
public BaseActorHandle getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment an application with deployment list.
|
||||
*
|
||||
* @param name application name.
|
||||
* @param routePrefix route prefix for the application.
|
||||
* @param deployments deployment list.
|
||||
* @param ingressDeploymentName name of the ingress deployment (the one that is exposed over
|
||||
* HTTP).
|
||||
* @param blocking Wait for the applications to be deployed or not.
|
||||
* @param externalScalerEnabled If true, indicates that an external autoscaler will manage replica
|
||||
* scaling for this application.
|
||||
*/
|
||||
public void deployApplication(
|
||||
String name,
|
||||
String routePrefix,
|
||||
List<Deployment> deployments,
|
||||
String ingressDeploymentName,
|
||||
boolean blocking,
|
||||
boolean externalScalerEnabled) {
|
||||
|
||||
Object[] deploymentArgsArray = new Object[deployments.size()];
|
||||
|
||||
for (int i = 0; i < deployments.size(); i++) {
|
||||
Deployment deployment = deployments.get(i);
|
||||
DeploymentArgs.Builder deploymentArgs =
|
||||
DeploymentArgs.newBuilder()
|
||||
.setDeploymentName(deployment.getName())
|
||||
.setReplicaConfig(ByteString.copyFrom(deployment.getReplicaConfig().toProtoBytes()))
|
||||
.setDeploymentConfig(
|
||||
ByteString.copyFrom(deployment.getDeploymentConfig().toProtoBytes()))
|
||||
.setIngress(deployment.isIngress())
|
||||
.setDeployerJobId(Ray.getRuntimeContext().getCurrentJobId().toString());
|
||||
if (deployment.getName() == ingressDeploymentName) {
|
||||
deploymentArgs.setRoutePrefix(routePrefix);
|
||||
}
|
||||
deploymentArgsArray[i] = deploymentArgs.build().toByteArray();
|
||||
}
|
||||
|
||||
ApplicationArgs.Builder applicationArgs =
|
||||
ApplicationArgs.newBuilder().setExternalScalerEnabled(externalScalerEnabled);
|
||||
byte[] applicationArgsBytes = applicationArgs.build().toByteArray();
|
||||
|
||||
((PyActorHandle) controller)
|
||||
.task(
|
||||
PyActorMethod.of("deploy_application"), name, deploymentArgsArray, applicationArgsBytes)
|
||||
.remote()
|
||||
.get();
|
||||
|
||||
if (blocking) {
|
||||
waitForApplicationRunning(name, null);
|
||||
for (Deployment deployment : deployments) {
|
||||
logDeploymentReady(
|
||||
deployment.getName(),
|
||||
deployment.getVersion(),
|
||||
"component=serve deployment=" + deployment.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the named application to enter "RUNNING" status.
|
||||
*
|
||||
* @param name application name
|
||||
* @param timeoutS unit: second
|
||||
*/
|
||||
private void waitForApplicationRunning(String name, Long timeoutS) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (timeoutS == null || System.currentTimeMillis() - start < timeoutS * 1000) {
|
||||
|
||||
StatusOverview status = getServeStatus(name);
|
||||
if (status.getAppStatus().getStatus() == ApplicationStatus.APPLICATION_STATUS_RUNNING) {
|
||||
return;
|
||||
} else if (status.getAppStatus().getStatus()
|
||||
== ApplicationStatus.APPLICATION_STATUS_DEPLOY_FAILED) {
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Deploying application {} is failed: {}",
|
||||
name,
|
||||
status.getAppStatus().getMessage()));
|
||||
}
|
||||
|
||||
LOGGER.debug(
|
||||
"Waiting for {} to be RUNNING, current status: {}.",
|
||||
name,
|
||||
status.getAppStatus().getStatus());
|
||||
try {
|
||||
Thread.sleep(CLIENT_POLLING_INTERVAL_S * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Application {} did not become RUNNING after {}s.", name, timeoutS));
|
||||
}
|
||||
|
||||
private void logDeploymentReady(String name, String version, String tag) {
|
||||
LOGGER.info(
|
||||
"Deployment '{}{}' is ready. {}",
|
||||
name,
|
||||
StringUtils.isNotBlank(version) ? "':'" + version : "",
|
||||
tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified applications.
|
||||
*
|
||||
* @param names application names
|
||||
* @param blocking Wait for the applications to be deleted or not.
|
||||
*/
|
||||
public void deleteApps(List<String> names, boolean blocking) {
|
||||
if (CollectionUtil.isEmpty(names)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Deleting app {}", names);
|
||||
|
||||
((PyActorHandle) controller)
|
||||
.task(PyActorMethod.of("delete_apps"), names.toArray())
|
||||
.remote()
|
||||
.get();
|
||||
|
||||
if (blocking) {
|
||||
long start = System.currentTimeMillis();
|
||||
List<String> undeleted = new ArrayList<>(names);
|
||||
|
||||
while (System.currentTimeMillis() - start < 60 * 1000) {
|
||||
|
||||
Iterator<String> iterator = undeleted.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
String name = iterator.next();
|
||||
StatusOverview status = getServeStatus(name);
|
||||
if (status.getAppStatus().getStatus()
|
||||
== ApplicationStatus.APPLICATION_STATUS_NOT_STARTED) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (undeleted.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(CLIENT_POLLING_INTERVAL_S * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Some of these applications weren't deleted after 60s: {}", names));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status of the specified application.
|
||||
*
|
||||
* @param name application name
|
||||
* @return
|
||||
*/
|
||||
public StatusOverview getServeStatus(String name) {
|
||||
byte[] statusBytes =
|
||||
(byte[])
|
||||
((PyActorHandle) controller)
|
||||
.task(PyActorMethod.of("get_serve_status"), name)
|
||||
.remote()
|
||||
.get();
|
||||
return ServeProtoUtil.bytesToProto(statusBytes, StatusOverview::parseFrom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ray.serve.common;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.List;
|
||||
|
||||
/** Ray Serve common constants. */
|
||||
public class Constants {
|
||||
|
||||
/** Name of deployment reconfiguration method implemented by user. */
|
||||
public static final String RECONFIGURE_METHOD = "reconfigure";
|
||||
|
||||
/** Default histogram buckets for latency tracker. */
|
||||
public static final List<Double> DEFAULT_LATENCY_BUCKET_MS =
|
||||
Lists.newArrayList(
|
||||
1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0);
|
||||
|
||||
/** Name of controller listen_for_change method. */
|
||||
public static final String CONTROLLER_LISTEN_FOR_CHANGE_METHOD = "listen_for_change_java";
|
||||
|
||||
/** Name of controller get_all_endpoints method. */
|
||||
public static final String CONTROLLER_GET_ALL_ENDPOINTS_METHOD = "get_all_endpoints_java";
|
||||
|
||||
public static final String SERVE_CONTROLLER_NAME = "SERVE_CONTROLLER_ACTOR";
|
||||
|
||||
public static final String SERVE_NAMESPACE = "serve";
|
||||
|
||||
public static final String CALL_METHOD = "call";
|
||||
|
||||
public static final String UTF8 = "UTF-8";
|
||||
|
||||
public static final String CHECK_HEALTH_METHOD = "checkHealth";
|
||||
|
||||
/** Controller checkpoint path */
|
||||
public static final String DEFAULT_CHECKPOINT_PATH = "ray://";
|
||||
|
||||
/**
|
||||
* Because ServeController will accept one long poll request per handle, its concurrency needs to
|
||||
* scale as O(num_handles)
|
||||
*/
|
||||
public static final int CONTROLLER_MAX_CONCURRENCY = 15000;
|
||||
|
||||
/** Max time to wait for proxy in `Serve.start`. Unit: second */
|
||||
public static final int PROXY_TIMEOUT_S = 60;
|
||||
|
||||
public static final Double DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_S = 20.0;
|
||||
|
||||
public static final Double DEFAULT_GRACEFUL_SHUTDOWN_WAIT_LOOP_S = 2.0;
|
||||
|
||||
public static final Double DEFAULT_HEALTH_CHECK_PERIOD_S = 10.0;
|
||||
|
||||
public static final Double DEFAULT_HEALTH_CHECK_TIMEOUT_S = 30.0;
|
||||
|
||||
public static final Double DEFAULT_REQUEST_ROUTING_STATS_PERIOD_S = 10.0;
|
||||
|
||||
public static final Double DEFAULT_REQUEST_ROUTING_STATS_TIMEOUT_S = 30.0;
|
||||
|
||||
/** Default Serve application name */
|
||||
public static final String SERVE_DEFAULT_APP_NAME = "default";
|
||||
|
||||
public static final String MIGRATION_MESSAGE =
|
||||
"This API is deprecated and may be removed in future Ray releases. "
|
||||
+ "Please see https://docs.ray.io/en/latest/serve/index.html for more information.";
|
||||
|
||||
public static final String SEPARATOR_HASH = "#";
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.ray.serve.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class AutoscalingConfig implements Serializable {
|
||||
private static final long serialVersionUID = 9135422781025005216L;
|
||||
private int minReplicas = 1;
|
||||
private int maxReplicas = 1;
|
||||
private int targetOngoingRequests = 1;
|
||||
/** How often to scrape for metrics */
|
||||
private double metricsIntervalS = 10.0;
|
||||
/** Time window to average over for metrics. */
|
||||
private double lookBackPeriodS = 30.0;
|
||||
/** Multiplicative "gain" factor to limit scaling decisions */
|
||||
private double smoothingFactor = 1.0;
|
||||
/** How long to wait before scaling down replicas */
|
||||
private double downscaleDelayS = 600.0;
|
||||
/** How long to wait before scaling up replicas */
|
||||
private double upscaleDelayS = 30.0;
|
||||
|
||||
public int getMinReplicas() {
|
||||
return minReplicas;
|
||||
}
|
||||
|
||||
public void setMinReplicas(int minReplicas) {
|
||||
this.minReplicas = minReplicas;
|
||||
}
|
||||
|
||||
public int getMaxReplicas() {
|
||||
return maxReplicas;
|
||||
}
|
||||
|
||||
public void setMaxReplicas(int maxReplicas) {
|
||||
this.maxReplicas = maxReplicas;
|
||||
}
|
||||
|
||||
public int getTargetOngoingRequests() {
|
||||
return targetOngoingRequests;
|
||||
}
|
||||
|
||||
public void setTargetOngoingRequests(int targetOngoingRequests) {
|
||||
this.targetOngoingRequests = targetOngoingRequests;
|
||||
}
|
||||
|
||||
public double getMetricsIntervalS() {
|
||||
return metricsIntervalS;
|
||||
}
|
||||
|
||||
public void setMetricsIntervalS(double metricsIntervalS) {
|
||||
this.metricsIntervalS = metricsIntervalS;
|
||||
}
|
||||
|
||||
public double getLookBackPeriodS() {
|
||||
return lookBackPeriodS;
|
||||
}
|
||||
|
||||
public void setLookBackPeriodS(double lookBackPeriodS) {
|
||||
this.lookBackPeriodS = lookBackPeriodS;
|
||||
}
|
||||
|
||||
public double getSmoothingFactor() {
|
||||
return smoothingFactor;
|
||||
}
|
||||
|
||||
public void setSmoothingFactor(double smoothingFactor) {
|
||||
this.smoothingFactor = smoothingFactor;
|
||||
}
|
||||
|
||||
public double getDownscaleDelayS() {
|
||||
return downscaleDelayS;
|
||||
}
|
||||
|
||||
public void setDownscaleDelayS(double downscaleDelayS) {
|
||||
this.downscaleDelayS = downscaleDelayS;
|
||||
}
|
||||
|
||||
public double getUpscaleDelayS() {
|
||||
return upscaleDelayS;
|
||||
}
|
||||
|
||||
public void setUpscaleDelayS(double upscaleDelayS) {
|
||||
this.upscaleDelayS = upscaleDelayS;
|
||||
}
|
||||
|
||||
public io.ray.serve.generated.AutoscalingConfig toProto() {
|
||||
return io.ray.serve.generated.AutoscalingConfig.newBuilder()
|
||||
.setMinReplicas(minReplicas)
|
||||
.setMaxReplicas(maxReplicas)
|
||||
.setTargetOngoingRequests(targetOngoingRequests)
|
||||
.setMetricsIntervalS(metricsIntervalS)
|
||||
.setLookBackPeriodS(lookBackPeriodS)
|
||||
.setSmoothingFactor(smoothingFactor)
|
||||
.setDownscaleDelayS(downscaleDelayS)
|
||||
.setUpscaleDelayS(upscaleDelayS)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package io.ray.serve.config;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.runtime.serializer.MessagePackSerializer;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import java.io.Serializable;
|
||||
|
||||
/** Configuration options for a deployment, to be set by the user. */
|
||||
public class DeploymentConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5965977837248820843L;
|
||||
|
||||
/**
|
||||
* The number of processes to start up that will handle requests to this deployment. Defaults to
|
||||
* 1.
|
||||
*/
|
||||
private Integer numReplicas = 1;
|
||||
|
||||
/**
|
||||
* The maximum number of requests that can be sent to a replica of this deployment without
|
||||
* receiving a response. Defaults to 100.
|
||||
*/
|
||||
private Integer maxOngoingRequests = 100;
|
||||
|
||||
/**
|
||||
* Arguments to pass to the reconfigure method of the deployment. The reconfigure method is called
|
||||
* if user_config is not None.
|
||||
*/
|
||||
private Object userConfig;
|
||||
|
||||
/**
|
||||
* Duration that deployment replicas will wait until there is no more work to be done before
|
||||
* shutting down.
|
||||
*/
|
||||
private Double gracefulShutdownWaitLoopS = Constants.DEFAULT_GRACEFUL_SHUTDOWN_WAIT_LOOP_S;
|
||||
|
||||
/** Controller waits for this duration to forcefully kill the replica for shutdown. */
|
||||
private Double gracefulShutdownTimeoutS = Constants.DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_S;
|
||||
|
||||
/** Frequency at which the controller will health check replicas. */
|
||||
private Double healthCheckPeriodS = Constants.DEFAULT_HEALTH_CHECK_PERIOD_S;
|
||||
|
||||
/**
|
||||
* Timeout that the controller will wait for a response from the replica's health check before
|
||||
* marking it unhealthy.
|
||||
*/
|
||||
private Double healthCheckTimeoutS = Constants.DEFAULT_HEALTH_CHECK_TIMEOUT_S;
|
||||
|
||||
private AutoscalingConfig autoscalingConfig;
|
||||
|
||||
private RequestRouterConfig routerConfig;
|
||||
|
||||
/** This flag is used to let replica know they are deplyed from a different language. */
|
||||
private Boolean isCrossLanguage = false;
|
||||
|
||||
/** This property tells the controller the deployment's language. */
|
||||
private DeploymentLanguage deploymentLanguage = DeploymentLanguage.JAVA;
|
||||
|
||||
private String version;
|
||||
|
||||
private String prevVersion;
|
||||
|
||||
private Integer maxConstructorRetryCount = 20;
|
||||
|
||||
public Integer getNumReplicas() {
|
||||
return numReplicas;
|
||||
}
|
||||
|
||||
public DeploymentConfig setNumReplicas(Integer numReplicas) {
|
||||
if (numReplicas != null) {
|
||||
this.numReplicas = numReplicas;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMaxOngoingRequests() {
|
||||
return maxOngoingRequests;
|
||||
}
|
||||
|
||||
public DeploymentConfig setMaxOngoingRequests(Integer maxOngoingRequests) {
|
||||
if (maxOngoingRequests != null) {
|
||||
Preconditions.checkArgument(maxOngoingRequests > 0, "max_ongoing_requests must be > 0");
|
||||
this.maxOngoingRequests = maxOngoingRequests;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMaxConstructorRetryCount() {
|
||||
return maxConstructorRetryCount;
|
||||
}
|
||||
|
||||
public DeploymentConfig setMaxConstructorRetryCount(Integer maxConstructorRetryCount) {
|
||||
if (maxConstructorRetryCount != null) {
|
||||
Preconditions.checkArgument(
|
||||
maxConstructorRetryCount > 0, "max constructor retry count must be > 0");
|
||||
this.maxConstructorRetryCount = maxConstructorRetryCount;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getUserConfig() {
|
||||
return userConfig;
|
||||
}
|
||||
|
||||
public DeploymentConfig setUserConfig(Object userConfig) {
|
||||
this.userConfig = userConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getGracefulShutdownWaitLoopS() {
|
||||
return gracefulShutdownWaitLoopS;
|
||||
}
|
||||
|
||||
public DeploymentConfig setGracefulShutdownWaitLoopS(Double gracefulShutdownWaitLoopS) {
|
||||
if (gracefulShutdownWaitLoopS != null) {
|
||||
this.gracefulShutdownWaitLoopS = gracefulShutdownWaitLoopS;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getGracefulShutdownTimeoutS() {
|
||||
return gracefulShutdownTimeoutS;
|
||||
}
|
||||
|
||||
public DeploymentConfig setGracefulShutdownTimeoutS(Double gracefulShutdownTimeoutS) {
|
||||
if (gracefulShutdownTimeoutS != null) {
|
||||
this.gracefulShutdownTimeoutS = gracefulShutdownTimeoutS;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getHealthCheckPeriodS() {
|
||||
return healthCheckPeriodS;
|
||||
}
|
||||
|
||||
public DeploymentConfig setHealthCheckPeriodS(Double healthCheckPeriodS) {
|
||||
if (healthCheckPeriodS != null) {
|
||||
this.healthCheckPeriodS = healthCheckPeriodS;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getHealthCheckTimeoutS() {
|
||||
return healthCheckTimeoutS;
|
||||
}
|
||||
|
||||
public DeploymentConfig setHealthCheckTimeoutS(Double healthCheckTimeoutS) {
|
||||
if (healthCheckTimeoutS != null) {
|
||||
this.healthCheckTimeoutS = healthCheckTimeoutS;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRequestRoutingStatsPeriodS() {
|
||||
return routerConfig.getRequestRoutingStatsPeriodS();
|
||||
}
|
||||
|
||||
public DeploymentConfig setRequestRoutingStatsPeriodS(Double requestRoutingStatsPeriodS) {
|
||||
if (requestRoutingStatsPeriodS != null) {
|
||||
routerConfig.setRequestRoutingStatsPeriodS(requestRoutingStatsPeriodS);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRequestRoutingStatsTimeoutS() {
|
||||
return routerConfig.getRequestRoutingStatsTimeoutS();
|
||||
}
|
||||
|
||||
public DeploymentConfig setRequestRoutingStatsTimeoutS(Double requestRoutingStatsTimeoutS) {
|
||||
if (requestRoutingStatsTimeoutS != null) {
|
||||
routerConfig.setRequestRoutingStatsTimeoutS(requestRoutingStatsTimeoutS);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoscalingConfig getAutoscalingConfig() {
|
||||
return autoscalingConfig;
|
||||
}
|
||||
|
||||
public DeploymentConfig setAutoscalingConfig(AutoscalingConfig autoscalingConfig) {
|
||||
this.autoscalingConfig = autoscalingConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestRouterConfig getRequestRouterConfig() {
|
||||
return routerConfig;
|
||||
}
|
||||
|
||||
public DeploymentConfig setRequestRouterConfig(RequestRouterConfig routerConfig) {
|
||||
this.routerConfig = routerConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isCrossLanguage() {
|
||||
return isCrossLanguage;
|
||||
}
|
||||
|
||||
public DeploymentConfig setCrossLanguage(Boolean isCrossLanguage) {
|
||||
if (isCrossLanguage != null) {
|
||||
this.isCrossLanguage = isCrossLanguage;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeploymentLanguage getDeploymentLanguage() {
|
||||
return deploymentLanguage;
|
||||
}
|
||||
|
||||
public DeploymentConfig setDeploymentLanguage(DeploymentLanguage deploymentLanguage) {
|
||||
if (deploymentLanguage != null) {
|
||||
this.deploymentLanguage = deploymentLanguage;
|
||||
this.isCrossLanguage = deploymentLanguage != DeploymentLanguage.JAVA;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public DeploymentConfig setVersion(String version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPrevVersion() {
|
||||
return prevVersion;
|
||||
}
|
||||
|
||||
public void setPrevVersion(String prevVersion) {
|
||||
this.prevVersion = prevVersion;
|
||||
}
|
||||
|
||||
public byte[] toProtoBytes() {
|
||||
io.ray.serve.generated.DeploymentConfig.Builder builder =
|
||||
io.ray.serve.generated.DeploymentConfig.newBuilder()
|
||||
.setNumReplicas(numReplicas)
|
||||
.setMaxOngoingRequests(maxOngoingRequests)
|
||||
.setMaxQueuedRequests(-1)
|
||||
.setGracefulShutdownWaitLoopS(gracefulShutdownWaitLoopS)
|
||||
.setGracefulShutdownTimeoutS(gracefulShutdownTimeoutS)
|
||||
.setHealthCheckPeriodS(healthCheckPeriodS)
|
||||
.setHealthCheckTimeoutS(healthCheckTimeoutS)
|
||||
.setIsCrossLanguage(isCrossLanguage)
|
||||
.setDeploymentLanguage(deploymentLanguage)
|
||||
.setVersion(version)
|
||||
.setMaxConstructorRetryCount(maxConstructorRetryCount);
|
||||
if (null != userConfig) {
|
||||
builder.setUserConfig(ByteString.copyFrom(MessagePackSerializer.encode(userConfig).getKey()));
|
||||
}
|
||||
if (null != autoscalingConfig) {
|
||||
builder.setAutoscalingConfig(autoscalingConfig.toProto());
|
||||
}
|
||||
if (null != routerConfig) {
|
||||
builder.setRequestRouterConfig(routerConfig.toProto());
|
||||
}
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
public io.ray.serve.generated.DeploymentConfig toProto() {
|
||||
io.ray.serve.generated.DeploymentConfig.Builder builder =
|
||||
io.ray.serve.generated.DeploymentConfig.newBuilder()
|
||||
.setNumReplicas(numReplicas)
|
||||
.setMaxOngoingRequests(maxOngoingRequests)
|
||||
.setGracefulShutdownWaitLoopS(gracefulShutdownWaitLoopS)
|
||||
.setGracefulShutdownTimeoutS(gracefulShutdownTimeoutS)
|
||||
.setHealthCheckPeriodS(healthCheckPeriodS)
|
||||
.setHealthCheckTimeoutS(healthCheckTimeoutS)
|
||||
.setIsCrossLanguage(isCrossLanguage)
|
||||
.setDeploymentLanguage(deploymentLanguage)
|
||||
.setMaxConstructorRetryCount(maxConstructorRetryCount);
|
||||
if (null != userConfig) {
|
||||
builder.setUserConfig(ByteString.copyFrom(MessagePackSerializer.encode(userConfig).getKey()));
|
||||
}
|
||||
if (null != autoscalingConfig) {
|
||||
builder.setAutoscalingConfig(autoscalingConfig.toProto());
|
||||
}
|
||||
if (null != routerConfig) {
|
||||
builder.setRequestRouterConfig(routerConfig.toProto());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static DeploymentConfig fromProto(io.ray.serve.generated.DeploymentConfig proto) {
|
||||
|
||||
DeploymentConfig deploymentConfig = new DeploymentConfig();
|
||||
if (proto == null) {
|
||||
return deploymentConfig;
|
||||
}
|
||||
deploymentConfig.setNumReplicas(proto.getNumReplicas());
|
||||
deploymentConfig.setMaxOngoingRequests(proto.getMaxOngoingRequests());
|
||||
deploymentConfig.setGracefulShutdownWaitLoopS(proto.getGracefulShutdownWaitLoopS());
|
||||
deploymentConfig.setGracefulShutdownTimeoutS(proto.getGracefulShutdownTimeoutS());
|
||||
deploymentConfig.setCrossLanguage(proto.getIsCrossLanguage());
|
||||
if (proto.getDeploymentLanguage() == DeploymentLanguage.UNRECOGNIZED) {
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Unrecognized deployment language {}. Deployment language must be in {}.",
|
||||
proto.getDeploymentLanguage(),
|
||||
Lists.newArrayList(DeploymentLanguage.values())));
|
||||
}
|
||||
deploymentConfig.setDeploymentLanguage(proto.getDeploymentLanguage());
|
||||
if (proto.getUserConfig() != null && proto.getUserConfig().size() != 0) {
|
||||
deploymentConfig.setUserConfig(
|
||||
MessagePackSerializer.decode(
|
||||
proto.getUserConfig().toByteArray(), Object.class)); // TODO-xlang
|
||||
}
|
||||
if (proto.getMaxConstructorRetryCount() > 0) {
|
||||
deploymentConfig.setMaxConstructorRetryCount(proto.getMaxConstructorRetryCount());
|
||||
}
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
public static DeploymentConfig fromProtoBytes(byte[] bytes) {
|
||||
|
||||
DeploymentConfig deploymentConfig = new DeploymentConfig();
|
||||
if (bytes == null) {
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
io.ray.serve.generated.DeploymentConfig proto = null;
|
||||
try {
|
||||
proto = io.ray.serve.generated.DeploymentConfig.parseFrom(bytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse DeploymentConfig from protobuf bytes.", e);
|
||||
}
|
||||
|
||||
return fromProto(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ray.serve.config;
|
||||
|
||||
public class RayServeConfig {
|
||||
|
||||
public static final String PROXY_CLASS = "ray.serve.proxy.class";
|
||||
|
||||
public static final String PROXY_HTTP_PORT = "ray.serve.proxy.http.port";
|
||||
|
||||
public static final String METRICS_ENABLED = "ray.serve.metrics.enabled";
|
||||
|
||||
public static final String LONG_POOL_CLIENT_ENABLED = "ray.serve.long.poll.client.enabled";
|
||||
|
||||
/** The polling interval of long poll thread. Unit: second. */
|
||||
public static final String LONG_POOL_CLIENT_INTERVAL = "ray.serve.long.poll.client.interval_s";
|
||||
|
||||
/** The polling timeout of each long poll invoke. Unit: second. */
|
||||
public static final String LONG_POOL_CLIENT_TIMEOUT_S = "ray.serve.long.poll.client.timeout_s";
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package io.ray.serve.config;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.ray.runtime.serializer.MessagePackSerializer;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** Configuration options for a replica. */
|
||||
public class ReplicaConfig {
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
private String deploymentDef;
|
||||
|
||||
private Object[] initArgs;
|
||||
|
||||
private Map<String, Object> rayActorOptions;
|
||||
|
||||
private Map<String, Object> resources;
|
||||
|
||||
private static final Set<String> allowedRayActorOptions =
|
||||
Sets.newHashSet(
|
||||
// resource options
|
||||
"accelerator_type",
|
||||
"memory",
|
||||
"num_cpus",
|
||||
"num_gpus",
|
||||
"object_store_memory",
|
||||
"resources",
|
||||
// other options
|
||||
"runtime_env");
|
||||
|
||||
public ReplicaConfig(
|
||||
String deploymentDef, Object[] initArgs, Map<String, Object> rayActorOptions) {
|
||||
this.deploymentDef = deploymentDef;
|
||||
this.initArgs = initArgs != null ? initArgs : new Object[0];
|
||||
this.rayActorOptions = rayActorOptions != null ? rayActorOptions : new HashMap<>();
|
||||
for (String option : this.rayActorOptions.keySet()) {
|
||||
Preconditions.checkArgument(
|
||||
allowedRayActorOptions.contains(option),
|
||||
MessageFormatter.format(
|
||||
"Specifying '{}' in ray_actor_options is not allowed. Allowed options: {}",
|
||||
option,
|
||||
allowedRayActorOptions));
|
||||
}
|
||||
// TODO validate_actor_options
|
||||
if (!this.rayActorOptions.containsKey("num_cpus")) {
|
||||
this.rayActorOptions.put("num_cpus", 1.0);
|
||||
}
|
||||
this.resources = resourcesFromRayOptions(this.rayActorOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a task's resource requirements.
|
||||
*
|
||||
* @param rayActorOptions The map that contains resources requirements.
|
||||
* @return A map of the resource requirements for the task.
|
||||
*/
|
||||
private Map<String, Object> resourcesFromRayOptions(Map<String, Object> rayActorOptions) {
|
||||
|
||||
Object numCpus = rayActorOptions.get("num_cpus");
|
||||
Object numGpus = rayActorOptions.get("num_gpus");
|
||||
Object memory = rayActorOptions.get("memory");
|
||||
Object objectStoreMemory = rayActorOptions.get("object_store_memory");
|
||||
Object acceleratorType = rayActorOptions.get("accelerator_type");
|
||||
|
||||
Map<String, Object> resources = new HashMap<>();
|
||||
if (numCpus != null) {
|
||||
resources.put("CPU", numCpus);
|
||||
}
|
||||
if (numGpus != null) {
|
||||
resources.put("GPU", numGpus);
|
||||
}
|
||||
if (memory != null) {
|
||||
resources.put("memory", memory);
|
||||
}
|
||||
if (objectStoreMemory != null) {
|
||||
resources.put("object_store_memory", objectStoreMemory);
|
||||
}
|
||||
if (acceleratorType != null) {
|
||||
resources.put("accelerator_type:" + acceleratorType, 0.001);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
public String getDeploymentDef() {
|
||||
return deploymentDef;
|
||||
}
|
||||
|
||||
public void setDeploymentDef(String deploymentDef) {
|
||||
this.deploymentDef = deploymentDef;
|
||||
}
|
||||
|
||||
public Object[] getInitArgs() {
|
||||
return initArgs;
|
||||
}
|
||||
|
||||
public void setInitArgs(Object[] initArgs) {
|
||||
this.initArgs = initArgs;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRayActorOptions() {
|
||||
return rayActorOptions;
|
||||
}
|
||||
|
||||
public void setRayActorOptions(Map<String, Object> rayActorOptions) {
|
||||
this.rayActorOptions = rayActorOptions;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void setResources(Map<String, Object> resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public byte[] toProtoBytes() {
|
||||
io.ray.serve.generated.ReplicaConfig.Builder builder =
|
||||
io.ray.serve.generated.ReplicaConfig.newBuilder();
|
||||
if (StringUtils.isNotBlank(deploymentDef)) {
|
||||
builder.setDeploymentDefName(deploymentDef);
|
||||
builder.setDeploymentDef(ByteString.copyFromUtf8(deploymentDef)); // TODO-xlang
|
||||
}
|
||||
if (initArgs != null && initArgs.length > 0) {
|
||||
builder.setInitArgs(
|
||||
ByteString.copyFrom(MessagePackSerializer.encode(initArgs).getKey())); // TODO-xlang
|
||||
}
|
||||
if (rayActorOptions != null && !rayActorOptions.isEmpty()) {
|
||||
builder.setRayActorOptions(gson.toJson(rayActorOptions));
|
||||
}
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
public static ReplicaConfig fromProto(io.ray.serve.generated.ReplicaConfig proto) {
|
||||
if (proto == null) {
|
||||
return null;
|
||||
}
|
||||
Object[] initArgs = null;
|
||||
if (0 != proto.getInitArgs().toByteArray().length) {
|
||||
initArgs = MessagePackSerializer.decode(proto.getInitArgs().toByteArray(), null);
|
||||
}
|
||||
ReplicaConfig replicaConfig =
|
||||
new ReplicaConfig(
|
||||
proto.getDeploymentDefName(),
|
||||
initArgs,
|
||||
gson.fromJson(proto.getRayActorOptions(), Map.class));
|
||||
return replicaConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ray.serve.config;
|
||||
|
||||
import io.ray.serve.common.Constants;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class RequestRouterConfig implements Serializable {
|
||||
/** Frequency at which the controller will record request routing stats. */
|
||||
private Double requestRoutingStatsPeriodS = Constants.DEFAULT_REQUEST_ROUTING_STATS_PERIOD_S;
|
||||
|
||||
/**
|
||||
* Timeout that the controller waits for a response from the replica's request routing stats
|
||||
* before retrying.
|
||||
*/
|
||||
private Double requestRoutingStatsTimeoutS = Constants.DEFAULT_REQUEST_ROUTING_STATS_TIMEOUT_S;
|
||||
|
||||
public Double getRequestRoutingStatsPeriodS() {
|
||||
return requestRoutingStatsPeriodS;
|
||||
}
|
||||
|
||||
public Double getRequestRoutingStatsTimeoutS() {
|
||||
return requestRoutingStatsTimeoutS;
|
||||
}
|
||||
|
||||
public void setRequestRoutingStatsPeriodS(Double requestRoutingStatsPeriodS) {
|
||||
this.requestRoutingStatsPeriodS = requestRoutingStatsPeriodS;
|
||||
}
|
||||
|
||||
public void setRequestRoutingStatsTimeoutS(Double requestRoutingStatsTimeoutS) {
|
||||
this.requestRoutingStatsTimeoutS = requestRoutingStatsTimeoutS;
|
||||
}
|
||||
|
||||
public io.ray.serve.generated.RequestRouterConfig toProto() {
|
||||
return io.ray.serve.generated.RequestRouterConfig.newBuilder()
|
||||
.setRequestRoutingStatsPeriodS(requestRoutingStatsPeriodS)
|
||||
.setRequestRoutingStatsTimeoutS(requestRoutingStatsTimeoutS)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.ray.serve.context;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class RequestContext {
|
||||
|
||||
private static RequestContext DEFAULT_CONTEXT = new RequestContext("", "", "", "");
|
||||
|
||||
private static ThreadLocal<RequestContext> SERVE_REQUEST_CONTEXT =
|
||||
ThreadLocal.withInitial(() -> DEFAULT_CONTEXT);
|
||||
|
||||
private String route;
|
||||
private String requestId;
|
||||
private String appName;
|
||||
private String multiplexedModelId;
|
||||
|
||||
private RequestContext(
|
||||
String route, String requestId, String appName, String multiplexedModelId) {
|
||||
this.route = route;
|
||||
this.requestId = requestId;
|
||||
this.appName = appName;
|
||||
this.multiplexedModelId = multiplexedModelId;
|
||||
}
|
||||
|
||||
public String getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public String getMultiplexedModelId() {
|
||||
return multiplexedModelId;
|
||||
}
|
||||
|
||||
public static void set(
|
||||
String route, String requestId, String appName, String multiplexedModelId) {
|
||||
SERVE_REQUEST_CONTEXT.set(
|
||||
new RequestContext(
|
||||
Optional.ofNullable(route).orElse(DEFAULT_CONTEXT.getRoute()),
|
||||
Optional.ofNullable(requestId).orElse(DEFAULT_CONTEXT.getRequestId()),
|
||||
Optional.ofNullable(appName).orElse(DEFAULT_CONTEXT.getAppName()),
|
||||
Optional.ofNullable(multiplexedModelId)
|
||||
.orElse(DEFAULT_CONTEXT.getMultiplexedModelId())));
|
||||
}
|
||||
|
||||
public static RequestContext get() {
|
||||
return SERVE_REQUEST_CONTEXT.get();
|
||||
}
|
||||
|
||||
public static void clean() {
|
||||
SERVE_REQUEST_CONTEXT.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ray.serve.controller;
|
||||
|
||||
import io.ray.serve.poll.LongPollRequest;
|
||||
|
||||
public interface ServeController {
|
||||
byte[] getAllEndpoints();
|
||||
|
||||
byte[] listenForChange(LongPollRequest longPollRequest);
|
||||
|
||||
String getRootUrl();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ray.serve.dag;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ClassNode extends DAGNode {
|
||||
|
||||
private String className;
|
||||
|
||||
public ClassNode(
|
||||
String clsName,
|
||||
Object[] clsArgs,
|
||||
Map<String, Object> clsOptions,
|
||||
Map<String, Object> otherArgsToResolve) {
|
||||
super(clsArgs, clsOptions, otherArgsToResolve);
|
||||
this.className = clsName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DAGNode copyImpl(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve) {
|
||||
return new ClassNode(className, newArgs, newOptions, newOtherArgsToResolve);
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package io.ray.serve.dag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class DAGNode implements DAGNodeBase {
|
||||
|
||||
private final Object[] boundArgs;
|
||||
|
||||
private final Map<String, Object> boundOptions;
|
||||
|
||||
private final Map<String, Object> boundOtherArgsToResolve;
|
||||
|
||||
private String stableUuid = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
public DAGNode(
|
||||
Object[] args, Map<String, Object> options, Map<String, Object> otherArgsToResolve) {
|
||||
this.boundArgs = args != null ? args : new Object[0];
|
||||
this.boundOptions = options != null ? options : new HashMap<>();
|
||||
this.boundOtherArgsToResolve =
|
||||
otherArgsToResolve != null ? otherArgsToResolve : new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T applyRecursive(Function<DAGNodeBase, T> fn) {
|
||||
if (!(fn instanceof CachingFn)) {
|
||||
Function<DAGNodeBase, T> newFun = new CachingFn<>(fn);
|
||||
return newFun.apply(applyAndReplaceAllChildNodes(node -> node.applyRecursive(newFun)));
|
||||
} else {
|
||||
return fn.apply(applyAndReplaceAllChildNodes(node -> node.applyRecursive(fn)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DAGNodeBase applyAndReplaceAllChildNodes(Function<DAGNodeBase, T> fn) {
|
||||
Object[] newArgs = new Object[boundArgs.length];
|
||||
for (int i = 0; i < boundArgs.length; i++) {
|
||||
newArgs[i] =
|
||||
boundArgs[i] instanceof DAGNodeBase ? fn.apply((DAGNodeBase) boundArgs[i]) : boundArgs[i];
|
||||
}
|
||||
|
||||
return copy(newArgs, boundOptions, boundOtherArgsToResolve);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DAGNodeBase copy(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve) {
|
||||
DAGNode instance = (DAGNode) copyImpl(newArgs, newOptions, newOtherArgsToResolve);
|
||||
instance.stableUuid = stableUuid;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Object[] getBoundArgs() {
|
||||
return boundArgs;
|
||||
}
|
||||
|
||||
public Map<String, Object> getBoundOptions() {
|
||||
return boundOptions;
|
||||
}
|
||||
|
||||
public Map<String, Object> getBoundOtherArgsToResolve() {
|
||||
return boundOtherArgsToResolve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStableUuid() {
|
||||
return stableUuid;
|
||||
}
|
||||
|
||||
private static class CachingFn<T> implements Function<DAGNodeBase, T> {
|
||||
|
||||
private final Map<String, T> cache = new HashMap<>();
|
||||
|
||||
private final Function<DAGNodeBase, T> function;
|
||||
|
||||
public CachingFn(Function<DAGNodeBase, T> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T apply(DAGNodeBase t) {
|
||||
return cache.computeIfAbsent(t.getStableUuid(), key -> function.apply(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ray.serve.dag;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface DAGNodeBase {
|
||||
|
||||
<T> T applyRecursive(Function<DAGNodeBase, T> fn);
|
||||
|
||||
<T> DAGNodeBase applyAndReplaceAllChildNodes(Function<DAGNodeBase, T> fn);
|
||||
|
||||
DAGNodeBase copy(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve);
|
||||
|
||||
default DAGNodeBase copyImpl(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
String getStableUuid();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ray.serve.dag;
|
||||
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import java.util.Map;
|
||||
|
||||
public class DeploymentNode extends DAGNode {
|
||||
|
||||
private String appName;
|
||||
|
||||
private Deployment deployment;
|
||||
|
||||
private DeploymentHandle deploymentHandle;
|
||||
|
||||
public DeploymentNode(
|
||||
Deployment deployment,
|
||||
String appName,
|
||||
Object[] deploymentInitArgs,
|
||||
Map<String, Object> rayActorOptions,
|
||||
Map<String, Object> otherArgsToResolve) {
|
||||
super(deploymentInitArgs, rayActorOptions, otherArgsToResolve);
|
||||
this.appName = appName;
|
||||
this.deployment = deployment;
|
||||
this.deploymentHandle = new DeploymentHandle(deployment.getName(), appName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DAGNode copyImpl(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve) {
|
||||
return new DeploymentNode(deployment, appName, newArgs, newOptions, newOtherArgsToResolve);
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public Deployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public DeploymentHandle getDeploymentHandle() {
|
||||
return deploymentHandle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.ray.serve.dag;
|
||||
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import io.ray.serve.util.CommonUtil;
|
||||
import io.ray.serve.util.DAGUtil;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class Graph {
|
||||
|
||||
public static List<Deployment> build(DAGNode rayDagRootNode, String name) {
|
||||
DAGNodeBase serveRootDag =
|
||||
rayDagRootNode.applyRecursive(node -> transformRayDagToServeDag(node, name));
|
||||
return extractDeployments(serveRootDag);
|
||||
}
|
||||
|
||||
public static DAGNodeBase transformRayDagToServeDag(DAGNodeBase dagNode, String appName) {
|
||||
if (dagNode instanceof ClassNode) {
|
||||
ClassNode clsNode = (ClassNode) dagNode;
|
||||
Deployment deploymentShell =
|
||||
(Deployment) clsNode.getBoundOtherArgsToResolve().get("deployment_schema");
|
||||
|
||||
String deploymentName = DAGUtil.getNodeName(clsNode);
|
||||
if (!StringUtils.equals(
|
||||
deploymentShell.getName(), CommonUtil.getDeploymentName(clsNode.getClassName()))) {
|
||||
deploymentName = deploymentShell.getName();
|
||||
}
|
||||
|
||||
Object[] replacedDeploymentInitArgs = new Object[clsNode.getBoundArgs().length];
|
||||
for (int i = 0; i < clsNode.getBoundArgs().length; i++) {
|
||||
replacedDeploymentInitArgs[i] =
|
||||
clsNode.getBoundArgs()[i] instanceof DeploymentNode
|
||||
? replaceWithHandle((DeploymentNode) clsNode.getBoundArgs()[i])
|
||||
: clsNode.getBoundArgs()[i];
|
||||
}
|
||||
|
||||
Deployment deployment =
|
||||
deploymentShell
|
||||
.options()
|
||||
.setDeploymentDef(clsNode.getClassName())
|
||||
.setName(deploymentName)
|
||||
.setInitArgs(replacedDeploymentInitArgs)
|
||||
.create(false);
|
||||
|
||||
return new DeploymentNode(
|
||||
deployment,
|
||||
appName,
|
||||
clsNode.getBoundArgs(),
|
||||
clsNode.getBoundOptions(),
|
||||
clsNode.getBoundOtherArgsToResolve());
|
||||
}
|
||||
|
||||
return dagNode;
|
||||
}
|
||||
|
||||
public static List<Deployment> extractDeployments(DAGNodeBase rootNode) {
|
||||
Map<String, Deployment> deployments = new LinkedHashMap<>();
|
||||
rootNode.applyRecursive(
|
||||
node -> {
|
||||
if (node instanceof DeploymentNode) {
|
||||
Deployment deployment = ((DeploymentNode) node).getDeployment();
|
||||
deployments.put(deployment.getName(), deployment);
|
||||
}
|
||||
return node;
|
||||
});
|
||||
return deployments.values().stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static DeploymentHandle replaceWithHandle(DAGNode node) {
|
||||
if (node instanceof DeploymentNode) {
|
||||
DeploymentNode deploymentNode = (DeploymentNode) node;
|
||||
return new DeploymentHandle(
|
||||
deploymentNode.getDeployment().getName(), deploymentNode.getAppName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.serve.dag.DAGNode;
|
||||
import io.ray.serve.dag.DAGNodeBase;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Application implements DAGNodeBase {
|
||||
private DAGNode internalDagNode;
|
||||
|
||||
private Application(DAGNode internalDagNode) {
|
||||
this.internalDagNode = internalDagNode;
|
||||
}
|
||||
|
||||
public DAGNode getInternalDagNode() {
|
||||
return internalDagNode;
|
||||
}
|
||||
|
||||
public static Application fromInternalDagNode(DAGNode dagNode) {
|
||||
return new Application(dagNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T applyRecursive(Function<DAGNodeBase, T> fn) {
|
||||
return internalDagNode.applyRecursive(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DAGNodeBase applyAndReplaceAllChildNodes(Function<DAGNodeBase, T> fn) {
|
||||
return internalDagNode.applyAndReplaceAllChildNodes(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DAGNodeBase copy(
|
||||
Object[] newArgs, Map<String, Object> newOptions, Map<String, Object> newOtherArgsToResolve) {
|
||||
return internalDagNode.copy(newArgs, newOptions, newOtherArgsToResolve);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStableUuid() {
|
||||
return internalDagNode.getStableUuid();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.ReplicaConfig;
|
||||
import io.ray.serve.dag.ClassNode;
|
||||
import io.ray.serve.dag.DAGNode;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Construct a Deployment. CONSTRUCTOR SHOULDN'T BE USED DIRECTLY.
|
||||
*
|
||||
* <p>Deployments should be created, retrieved, and updated using `Serve.deployment.create`,
|
||||
* `Serve.getDeployment`, and `Deployment.options.create`, respectively.
|
||||
*/
|
||||
public class Deployment {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Deployment.class);
|
||||
|
||||
private final String name;
|
||||
|
||||
private final DeploymentConfig deploymentConfig;
|
||||
|
||||
private final ReplicaConfig replicaConfig;
|
||||
|
||||
private final String version;
|
||||
|
||||
private boolean ingress;
|
||||
|
||||
// TODO support placement group.
|
||||
|
||||
public Deployment(
|
||||
String name, DeploymentConfig deploymentConfig, ReplicaConfig replicaConfig, String version) {
|
||||
|
||||
Preconditions.checkArgument(
|
||||
version != null || deploymentConfig.getAutoscalingConfig() == null,
|
||||
"Currently autoscaling is only supported for versioned deployments. Try Serve.deployment.setVersion.");
|
||||
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.deploymentConfig = deploymentConfig;
|
||||
this.replicaConfig = replicaConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ServeHandle to this deployment to invoke it from Java.
|
||||
*
|
||||
* @return ServeHandle
|
||||
* @deprecated {@value Constants#MIGRATION_MESSAGE}
|
||||
*/
|
||||
@Deprecated
|
||||
public DeploymentHandle getHandle() {
|
||||
LOGGER.warn(Constants.MIGRATION_MESSAGE);
|
||||
return Serve.getGlobalClient().getDeploymentHandle(name, "", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this deployment with updated options.
|
||||
*
|
||||
* <p>Only those options passed in will be updated, all others will remain unchanged from the
|
||||
* existing deployment.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DeploymentCreator options() {
|
||||
|
||||
return new DeploymentCreator()
|
||||
.setDeploymentDef(this.replicaConfig.getDeploymentDef())
|
||||
.setName(this.name)
|
||||
.setVersion(this.version)
|
||||
.setNumReplicas(this.deploymentConfig.getNumReplicas())
|
||||
.setInitArgs(this.replicaConfig.getInitArgs())
|
||||
.setRayActorOptions(this.replicaConfig.getRayActorOptions())
|
||||
.setUserConfig(this.deploymentConfig.getUserConfig())
|
||||
.setMaxOngoingRequests(this.deploymentConfig.getMaxOngoingRequests())
|
||||
.setAutoscalingConfig(this.deploymentConfig.getAutoscalingConfig())
|
||||
.setGracefulShutdownWaitLoopS(this.deploymentConfig.getGracefulShutdownWaitLoopS())
|
||||
.setGracefulShutdownTimeoutS(this.deploymentConfig.getGracefulShutdownTimeoutS())
|
||||
.setHealthCheckPeriodS(this.deploymentConfig.getHealthCheckPeriodS())
|
||||
.setHealthCheckTimeoutS(this.deploymentConfig.getHealthCheckTimeoutS())
|
||||
.setLanguage(this.deploymentConfig.getDeploymentLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the arguments to the deployment and return an Application.
|
||||
*
|
||||
* <p>The returned Application can be deployed using `serve.run` (or via config file) or bound to
|
||||
* another deployment for composition.
|
||||
*
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
public Application bind(Object... args) {
|
||||
Map<String, Object> otherArgsToResolve = new HashMap<>();
|
||||
otherArgsToResolve.put("deployment_schema", this);
|
||||
otherArgsToResolve.put("is_from_serve_deployment", true);
|
||||
DAGNode dagNode =
|
||||
new ClassNode(
|
||||
replicaConfig.getDeploymentDef(),
|
||||
args,
|
||||
replicaConfig.getRayActorOptions(),
|
||||
otherArgsToResolve);
|
||||
return Application.fromInternalDagNode(dagNode);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DeploymentConfig getDeploymentConfig() {
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
public ReplicaConfig getReplicaConfig() {
|
||||
return replicaConfig;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isIngress() {
|
||||
return ingress;
|
||||
}
|
||||
|
||||
public void setIngress(boolean ingress) {
|
||||
this.ingress = ingress;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.config.AutoscalingConfig;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.ReplicaConfig;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.util.CommonUtil;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DeploymentCreator {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Serve.class);
|
||||
|
||||
private String deploymentDef;
|
||||
|
||||
/**
|
||||
* Globally-unique name identifying this deployment. If not provided, the name of the class or
|
||||
* function will be used.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Version of the deployment. This is used to indicate a code change for the deployment; when it
|
||||
* is re-deployed with a version change, a rolling update of the replicas will be performed. If
|
||||
* not provided, every deployment will be treated as a new version.
|
||||
*/
|
||||
@Deprecated private String version;
|
||||
|
||||
/**
|
||||
* The number of processes to start up that will handle requests to this deployment. Defaults to
|
||||
* 1.
|
||||
*/
|
||||
private Integer numReplicas;
|
||||
|
||||
/**
|
||||
* Positional args to be passed to the class constructor when starting up deployment replicas.
|
||||
* These can also be passed when you call `.deploy()` on the returned Deployment.
|
||||
*/
|
||||
private Object[] initArgs;
|
||||
|
||||
/** Options to be passed to the Ray actor constructor such as resource requirements. */
|
||||
private Map<String, Object> rayActorOptions;
|
||||
|
||||
/**
|
||||
* [experimental] Config to pass to the reconfigure method of the deployment. This can be updated
|
||||
* dynamically without changing the version of the deployment and restarting its replicas.
|
||||
*/
|
||||
private Object userConfig;
|
||||
|
||||
/**
|
||||
* The maximum number of queries that will be sent to a replica of this deployment without
|
||||
* receiving a response. Defaults to 100.
|
||||
*/
|
||||
private Integer maxOngoingRequests;
|
||||
|
||||
private AutoscalingConfig autoscalingConfig;
|
||||
|
||||
private Double gracefulShutdownWaitLoopS;
|
||||
|
||||
private Double gracefulShutdownTimeoutS;
|
||||
|
||||
private Double healthCheckPeriodS;
|
||||
|
||||
private Double healthCheckTimeoutS;
|
||||
|
||||
private DeploymentLanguage language;
|
||||
|
||||
// TODO is_driver_deployment\placement_group_bundles\placement_group_strategy
|
||||
|
||||
public Deployment create(boolean check) {
|
||||
|
||||
if (check) {
|
||||
Preconditions.checkArgument(
|
||||
numReplicas == null || numReplicas != 0, "num_replicas is expected to larger than 0");
|
||||
|
||||
Preconditions.checkArgument(
|
||||
numReplicas == null || autoscalingConfig == null,
|
||||
"Manually setting num_replicas is not allowed when autoscalingConfig is provided.");
|
||||
}
|
||||
|
||||
if (version != null) {
|
||||
LOGGER.warn(
|
||||
"DeprecationWarning: `version` in `@serve.deployment` has been deprecated. Explicitly specifying version will raise an error in the future!");
|
||||
}
|
||||
|
||||
DeploymentConfig deploymentConfig =
|
||||
new DeploymentConfig()
|
||||
.setNumReplicas(numReplicas != null ? numReplicas : 1)
|
||||
.setMaxOngoingRequests(maxOngoingRequests)
|
||||
.setUserConfig(userConfig)
|
||||
.setAutoscalingConfig(autoscalingConfig)
|
||||
.setGracefulShutdownWaitLoopS(gracefulShutdownWaitLoopS)
|
||||
.setGracefulShutdownTimeoutS(gracefulShutdownTimeoutS)
|
||||
.setHealthCheckPeriodS(healthCheckPeriodS)
|
||||
.setHealthCheckTimeoutS(healthCheckTimeoutS)
|
||||
.setDeploymentLanguage(language);
|
||||
|
||||
ReplicaConfig replicaConfig = new ReplicaConfig(deploymentDef, initArgs, rayActorOptions);
|
||||
|
||||
return new Deployment(
|
||||
StringUtils.isNotBlank(name) ? name : CommonUtil.getDeploymentName(deploymentDef),
|
||||
deploymentConfig,
|
||||
replicaConfig,
|
||||
version);
|
||||
}
|
||||
|
||||
public Deployment create() {
|
||||
return create(true);
|
||||
}
|
||||
|
||||
public Application bind(Object... args) {
|
||||
return create().bind(args);
|
||||
}
|
||||
|
||||
public String getDeploymentDef() {
|
||||
return deploymentDef;
|
||||
}
|
||||
|
||||
public DeploymentCreator setDeploymentDef(String deploymentDef) {
|
||||
this.deploymentDef = deploymentDef;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DeploymentCreator setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public DeploymentCreator setVersion(String version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getNumReplicas() {
|
||||
return numReplicas;
|
||||
}
|
||||
|
||||
public DeploymentCreator setNumReplicas(Integer numReplicas) {
|
||||
this.numReplicas = numReplicas;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object[] getInitArgs() {
|
||||
return initArgs;
|
||||
}
|
||||
|
||||
public DeploymentCreator setInitArgs(Object[] initArgs) {
|
||||
this.initArgs = initArgs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRayActorOptions() {
|
||||
return rayActorOptions;
|
||||
}
|
||||
|
||||
public DeploymentCreator setRayActorOptions(Map<String, Object> rayActorOptions) {
|
||||
this.rayActorOptions = rayActorOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getUserConfig() {
|
||||
return userConfig;
|
||||
}
|
||||
|
||||
public DeploymentCreator setUserConfig(Object userConfig) {
|
||||
this.userConfig = userConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMaxOngoingRequests() {
|
||||
return maxOngoingRequests;
|
||||
}
|
||||
|
||||
public DeploymentCreator setMaxOngoingRequests(Integer maxOngoingRequests) {
|
||||
this.maxOngoingRequests = maxOngoingRequests;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoscalingConfig getAutoscalingConfig() {
|
||||
return autoscalingConfig;
|
||||
}
|
||||
|
||||
public DeploymentCreator setAutoscalingConfig(AutoscalingConfig autoscalingConfig) {
|
||||
this.autoscalingConfig = autoscalingConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getGracefulShutdownWaitLoopS() {
|
||||
return gracefulShutdownWaitLoopS;
|
||||
}
|
||||
|
||||
public DeploymentCreator setGracefulShutdownWaitLoopS(Double gracefulShutdownWaitLoopS) {
|
||||
this.gracefulShutdownWaitLoopS = gracefulShutdownWaitLoopS;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getGracefulShutdownTimeoutS() {
|
||||
return gracefulShutdownTimeoutS;
|
||||
}
|
||||
|
||||
public DeploymentCreator setGracefulShutdownTimeoutS(Double gracefulShutdownTimeoutS) {
|
||||
this.gracefulShutdownTimeoutS = gracefulShutdownTimeoutS;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getHealthCheckPeriodS() {
|
||||
return healthCheckPeriodS;
|
||||
}
|
||||
|
||||
public DeploymentCreator setHealthCheckPeriodS(Double healthCheckPeriodS) {
|
||||
this.healthCheckPeriodS = healthCheckPeriodS;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getHealthCheckTimeoutS() {
|
||||
return healthCheckTimeoutS;
|
||||
}
|
||||
|
||||
public DeploymentCreator setHealthCheckTimeoutS(Double healthCheckTimeoutS) {
|
||||
this.healthCheckTimeoutS = healthCheckTimeoutS;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeploymentLanguage getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public DeploymentCreator setLanguage(DeploymentLanguage language) {
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class DeploymentId implements Serializable {
|
||||
private static final long serialVersionUID = 3423413558240304854L;
|
||||
private final String app;
|
||||
private final String name;
|
||||
|
||||
public DeploymentId(String name, String app) {
|
||||
this.name = name;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String toReplicaActorClassName() {
|
||||
if (StringUtils.isBlank(app)) {
|
||||
return "ServeReplica:" + name;
|
||||
} else {
|
||||
return "ServeReplica:" + app + ":" + name;
|
||||
}
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (StringUtils.isBlank(app)) {
|
||||
return name;
|
||||
} else {
|
||||
return app + "_" + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.ReplicaConfig;
|
||||
|
||||
public class DeploymentInfo {
|
||||
|
||||
private String name;
|
||||
|
||||
private DeploymentConfig deploymentConfig;
|
||||
|
||||
private ReplicaConfig replicaConfig;
|
||||
|
||||
private Long startTimeMs;
|
||||
|
||||
private String actorName;
|
||||
|
||||
private String version;
|
||||
|
||||
private Long endTimeMs;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DeploymentConfig getDeploymentConfig() {
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
public void setDeploymentConfig(DeploymentConfig deploymentConfig) {
|
||||
this.deploymentConfig = deploymentConfig;
|
||||
}
|
||||
|
||||
public ReplicaConfig getReplicaConfig() {
|
||||
return replicaConfig;
|
||||
}
|
||||
|
||||
public void setReplicaConfig(ReplicaConfig replicaConfig) {
|
||||
this.replicaConfig = replicaConfig;
|
||||
}
|
||||
|
||||
public Long getStartTimeMs() {
|
||||
return startTimeMs;
|
||||
}
|
||||
|
||||
public void setStartTimeMs(Long startTimeMs) {
|
||||
this.startTimeMs = startTimeMs;
|
||||
}
|
||||
|
||||
public String getActorName() {
|
||||
return actorName;
|
||||
}
|
||||
|
||||
public void setActorName(String actorName) {
|
||||
this.actorName = actorName;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Long getEndTimeMs() {
|
||||
return endTimeMs;
|
||||
}
|
||||
|
||||
public void setEndTimeMs(Long endTimeMs) {
|
||||
this.endTimeMs = endTimeMs;
|
||||
}
|
||||
|
||||
public static DeploymentInfo fromProto(io.ray.serve.generated.DeploymentInfo proto) {
|
||||
|
||||
if (proto == null) {
|
||||
return null;
|
||||
}
|
||||
DeploymentInfo deploymentInfo = new DeploymentInfo();
|
||||
deploymentInfo.setName(proto.getName());
|
||||
deploymentInfo.setDeploymentConfig(DeploymentConfig.fromProto(proto.getDeploymentConfig()));
|
||||
deploymentInfo.setReplicaConfig(ReplicaConfig.fromProto(proto.getReplicaConfig()));
|
||||
if (proto.getStartTimeMs() != 0) {
|
||||
deploymentInfo.setStartTimeMs(proto.getStartTimeMs());
|
||||
}
|
||||
deploymentInfo.setActorName(proto.getActorName());
|
||||
deploymentInfo.setVersion(proto.getVersion());
|
||||
if (proto.getEndTimeMs() != 0) {
|
||||
deploymentInfo.setEndTimeMs(proto.getEndTimeMs());
|
||||
}
|
||||
return deploymentInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
|
||||
public class DeploymentRoute {
|
||||
|
||||
private final DeploymentInfo deploymentInfo;
|
||||
|
||||
private final String route;
|
||||
|
||||
public DeploymentRoute(DeploymentInfo deploymentInfo, String route) {
|
||||
this.deploymentInfo = deploymentInfo;
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
public DeploymentInfo getDeploymentInfo() {
|
||||
return deploymentInfo;
|
||||
}
|
||||
|
||||
public String getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
public static DeploymentRoute fromProto(io.ray.serve.generated.DeploymentRoute proto) {
|
||||
if (proto == null) {
|
||||
return null;
|
||||
}
|
||||
return new DeploymentRoute(
|
||||
DeploymentInfo.fromProto(proto.getDeploymentInfo()), proto.getRoute());
|
||||
}
|
||||
|
||||
public static DeploymentRoute fromProtoBytes(byte[] bytes) {
|
||||
io.ray.serve.generated.DeploymentRoute proto =
|
||||
ServeProtoUtil.bytesToProto(bytes, io.ray.serve.generated.DeploymentRoute::parseFrom);
|
||||
return fromProto(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.serve.generated.DeploymentStatus;
|
||||
|
||||
public class DeploymentStatusInfo {
|
||||
|
||||
private String name;
|
||||
|
||||
private DeploymentStatus deploymentStatus;
|
||||
|
||||
private String message = "";
|
||||
|
||||
public DeploymentStatusInfo(String name, DeploymentStatus deploymentStatus, String message) {
|
||||
this.name = name;
|
||||
this.deploymentStatus = deploymentStatus;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static DeploymentStatusInfo fromProto(io.ray.serve.generated.DeploymentStatusInfo proto) {
|
||||
return new DeploymentStatusInfo(proto.getName(), proto.getStatus(), proto.getMessage());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DeploymentStatus getDeploymentStatus() {
|
||||
return deploymentStatus;
|
||||
}
|
||||
|
||||
public void setDeploymentStatus(DeploymentStatus deploymentStatus) {
|
||||
this.deploymentStatus = deploymentStatus;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class DeploymentVersion implements Serializable {
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
private static final long serialVersionUID = 3400261981775851058L;
|
||||
|
||||
private String codeVersion;
|
||||
|
||||
private Object userConfig;
|
||||
|
||||
private DeploymentConfig deploymentConfig;
|
||||
|
||||
private Map<String, Object> rayActorOptions;
|
||||
|
||||
private boolean unversioned;
|
||||
|
||||
public DeploymentVersion() {
|
||||
this(null, new DeploymentConfig(), null);
|
||||
}
|
||||
|
||||
public DeploymentVersion(String codeVersion) {
|
||||
this(codeVersion, new DeploymentConfig(), null);
|
||||
}
|
||||
|
||||
public DeploymentVersion(
|
||||
String codeVersion, DeploymentConfig deploymentConfig, Map<String, Object> rayActorOptions) {
|
||||
if (StringUtils.isBlank(codeVersion)) {
|
||||
this.unversioned = true;
|
||||
this.codeVersion = RandomStringUtils.randomAlphabetic(6);
|
||||
} else {
|
||||
this.codeVersion = codeVersion;
|
||||
}
|
||||
if (deploymentConfig == null) {
|
||||
deploymentConfig = new DeploymentConfig();
|
||||
}
|
||||
this.deploymentConfig = deploymentConfig;
|
||||
this.rayActorOptions = rayActorOptions;
|
||||
this.userConfig = deploymentConfig.getUserConfig();
|
||||
}
|
||||
|
||||
public String getCodeVersion() {
|
||||
return codeVersion;
|
||||
}
|
||||
|
||||
public Object getUserConfig() {
|
||||
return userConfig;
|
||||
}
|
||||
|
||||
public DeploymentConfig getDeploymentConfig() {
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRayActorOptions() {
|
||||
return rayActorOptions;
|
||||
}
|
||||
|
||||
public boolean isUnversioned() {
|
||||
return unversioned;
|
||||
}
|
||||
|
||||
public static DeploymentVersion fromProtoBytes(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return new DeploymentVersion();
|
||||
}
|
||||
|
||||
io.ray.serve.generated.DeploymentVersion proto = null;
|
||||
try {
|
||||
proto = io.ray.serve.generated.DeploymentVersion.parseFrom(bytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse DeploymentVersion from protobuf bytes.", e);
|
||||
}
|
||||
if (proto == null) {
|
||||
return new DeploymentVersion();
|
||||
}
|
||||
return new DeploymentVersion(
|
||||
proto.getCodeVersion(),
|
||||
DeploymentConfig.fromProto(proto.getDeploymentConfig()),
|
||||
gson.fromJson(proto.getRayActorOptions(), Map.class));
|
||||
}
|
||||
|
||||
public byte[] toProtoBytes() {
|
||||
io.ray.serve.generated.DeploymentVersion.Builder proto =
|
||||
io.ray.serve.generated.DeploymentVersion.newBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(codeVersion)) {
|
||||
proto.setCodeVersion(codeVersion);
|
||||
}
|
||||
proto.setDeploymentConfig(deploymentConfig.toProto());
|
||||
if (rayActorOptions != null && !rayActorOptions.isEmpty()) {
|
||||
proto.setRayActorOptions(gson.toJson(rayActorOptions));
|
||||
}
|
||||
return proto.build().toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
public class DeploymentWrapper implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5366203408463039652L;
|
||||
|
||||
private String name;
|
||||
|
||||
private String deploymentDef;
|
||||
|
||||
private DeploymentConfig deploymentConfig;
|
||||
|
||||
private Object[] initArgs;
|
||||
|
||||
private DeploymentVersion deploymentVersion;
|
||||
|
||||
private Map<String, String> config;
|
||||
|
||||
private String appName;
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDeploymentDef() {
|
||||
return deploymentDef;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setDeploymentDef(String deploymentDef) {
|
||||
this.deploymentDef = deploymentDef;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeploymentConfig getDeploymentConfig() {
|
||||
return deploymentConfig;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setDeploymentConfig(DeploymentConfig deploymentConfig) {
|
||||
this.deploymentConfig = deploymentConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object[] getInitArgs() {
|
||||
return initArgs;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setInitArgs(Object[] initArgs) {
|
||||
this.initArgs = initArgs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeploymentVersion getDeploymentVersion() {
|
||||
return deploymentVersion;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setDeploymentVersion(DeploymentVersion deploymentVersion) {
|
||||
this.deploymentVersion = deploymentVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public DeploymentWrapper setConfig(Map<String, String> config) {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.ray.serve.exception;
|
||||
|
||||
import io.ray.api.exception.RayException;
|
||||
|
||||
public class RayServeException extends RayException {
|
||||
|
||||
private static final long serialVersionUID = 4673951342965950469L;
|
||||
|
||||
public RayServeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RayServeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package io.ray.serve.handle;
|
||||
|
||||
import io.ray.runtime.metric.Count;
|
||||
import io.ray.runtime.metric.Metrics;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.context.RequestContext;
|
||||
import io.ray.serve.deployment.DeploymentId;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.metrics.RayServeMetrics;
|
||||
import io.ray.serve.router.Router;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** A handle to a service deployment. */
|
||||
public class DeploymentHandle implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 550701184372753496L;
|
||||
|
||||
private DeploymentId deploymentId;
|
||||
|
||||
private HandleOptions handleOptions;
|
||||
|
||||
private String handleTag;
|
||||
|
||||
private transient Count requestCounter;
|
||||
|
||||
private transient volatile Router router;
|
||||
|
||||
public DeploymentHandle(String deploymentName, String appName) {
|
||||
this(deploymentName, appName, null, null);
|
||||
}
|
||||
|
||||
public DeploymentHandle(
|
||||
String deploymentName, String appName, HandleOptions handleOptions, Router router) {
|
||||
this.deploymentId = new DeploymentId(deploymentName, appName);
|
||||
this.handleOptions = handleOptions != null ? handleOptions : new HandleOptions();
|
||||
this.handleTag =
|
||||
StringUtils.isBlank(appName)
|
||||
? deploymentName + Constants.SEPARATOR_HASH + RandomStringUtils.randomAlphabetic(6)
|
||||
: appName
|
||||
+ Constants.SEPARATOR_HASH
|
||||
+ deploymentName
|
||||
+ Constants.SEPARATOR_HASH
|
||||
+ RandomStringUtils.randomAlphabetic(6);
|
||||
this.router = router;
|
||||
initMetrics();
|
||||
}
|
||||
|
||||
private void initMetrics() {
|
||||
Map<String, String> metricsTags = new HashMap<>();
|
||||
metricsTags.put(RayServeMetrics.TAG_HANDLE, handleTag);
|
||||
metricsTags.put(RayServeMetrics.TAG_ENDPOINT, deploymentId.getName());
|
||||
if (StringUtils.isNotBlank(deploymentId.getApp())) {
|
||||
metricsTags.put(RayServeMetrics.TAG_APPLICATION, deploymentId.getApp());
|
||||
}
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
this.requestCounter =
|
||||
Metrics.count()
|
||||
.name(RayServeMetrics.SERVE_HANDLE_REQUEST_COUNTER.name())
|
||||
.description(RayServeMetrics.SERVE_HANDLE_REQUEST_COUNTER.getDescription())
|
||||
.unit("")
|
||||
.tags(metricsTags)
|
||||
.register());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Ray ObjectRef whose results can be waited for or retrieved using ray.wait or Ray.get
|
||||
* (or ``await object_ref``), respectively.
|
||||
*
|
||||
* @param parameters The input parameters of the specified method to be invoked in the deployment.
|
||||
* @return ObjectRef
|
||||
*/
|
||||
public DeploymentResponse remote(Object... parameters) {
|
||||
RequestContext requestContext = RequestContext.get();
|
||||
RayServeMetrics.execute(() -> requestCounter.inc(1.0));
|
||||
RequestMetadata.Builder requestMetadata = RequestMetadata.newBuilder();
|
||||
requestMetadata.setRequestId(requestContext.getRequestId());
|
||||
requestMetadata.setCallMethod(
|
||||
handleOptions != null ? handleOptions.getMethodName() : Constants.CALL_METHOD);
|
||||
requestMetadata.setRoute(requestContext.getRoute());
|
||||
requestMetadata.setMultiplexedModelId(requestContext.getMultiplexedModelId());
|
||||
return new DeploymentResponse(
|
||||
getOrCreateRouter().assignRequest(requestMetadata.build(), parameters));
|
||||
}
|
||||
|
||||
private Router getOrCreateRouter() {
|
||||
if (router != null) {
|
||||
return router;
|
||||
}
|
||||
|
||||
synchronized (DeploymentHandle.class) {
|
||||
if (router != null) {
|
||||
return router;
|
||||
}
|
||||
router = new Router(Serve.getGlobalClient().getController(), deploymentId);
|
||||
}
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor of DeploymentHandle is not invoked during deserialization, so it is necessary
|
||||
* to customize some initialization behavior during the deserialization process.
|
||||
*
|
||||
* @param in
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
initMetrics();
|
||||
}
|
||||
|
||||
public DeploymentHandle method(String methodName) {
|
||||
handleOptions.setMethodName(methodName);
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO method(String methodName, String signature)
|
||||
|
||||
public Router getRouter() {
|
||||
return getOrCreateRouter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this handle is actively polling for replica updates.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isPolling() {
|
||||
return router.getLongPollClient().isRunning();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.serve.handle;
|
||||
|
||||
import io.ray.api.ObjectRef;
|
||||
|
||||
/**
|
||||
* A future-like object wrapping the result of a unary deployment handle call.
|
||||
*
|
||||
* <p>From outside a deployment, `.result()` can be used to retrieve the output in a blocking
|
||||
* manner.
|
||||
*/
|
||||
public class DeploymentResponse {
|
||||
private ObjectRef<Object> objectRef;
|
||||
|
||||
public DeploymentResponse(ObjectRef<Object> objectRef) {
|
||||
this.objectRef = objectRef;
|
||||
}
|
||||
|
||||
public Object result() {
|
||||
return objectRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the result of the handle call synchronously.
|
||||
*
|
||||
* @param timeoutMs The unit is millisecond.
|
||||
* @return call result
|
||||
*/
|
||||
public Object result(long timeoutMs) {
|
||||
return objectRef.get(timeoutMs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ray.serve.handle;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** Options for each ServeHandle instances. These fields are immutable. */
|
||||
public class HandleOptions implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -279077795726949172L;
|
||||
private String methodName = "call";
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public void setMethodName(String methodName) {
|
||||
this.methodName = methodName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package io.ray.serve.metrics;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
|
||||
public enum RayServeMetrics {
|
||||
SERVE_HANDLE_REQUEST_COUNTER(
|
||||
"serve_handle_request_counter",
|
||||
"The number of handle.remote() calls that have been made on this handle."),
|
||||
|
||||
SERVE_NUM_ROUTER_REQUESTS(
|
||||
"serve_num_router_requests", "The number of requests processed by the router."),
|
||||
|
||||
SERVE_DEPLOYMENT_QUEUED_QUERIES(
|
||||
"serve_deployment_queued_queries",
|
||||
"The current number of queries to this deployment waiting to be assigned to a replica."),
|
||||
|
||||
SERVE_DEPLOYMENT_REQUEST_COUNTER(
|
||||
"serve_deployment_request_counter",
|
||||
"The number of queries that have been processed in this replica."),
|
||||
|
||||
SERVE_DEPLOYMENT_ERROR_COUNTER(
|
||||
"serve_deployment_error_counter",
|
||||
"The number of exceptions that have occurred in this replica."),
|
||||
|
||||
SERVE_DEPLOYMENT_REPLICA_STARTS(
|
||||
"serve_deployment_replica_starts",
|
||||
"The number of times this replica has been restarted due to failure."),
|
||||
|
||||
SERVE_DEPLOYMENT_PROCESSING_LATENCY_MS(
|
||||
"serve_deployment_processing_latency_ms", "The latency for queries to be processed."),
|
||||
|
||||
SERVE_REPLICA_PROCESSING_QUERIES(
|
||||
"serve_replica_processing_queries", "The current number of queries being processed."),
|
||||
;
|
||||
|
||||
public static final String TAG_HANDLE = "handle";
|
||||
|
||||
public static final String TAG_ENDPOINT = "endpoint";
|
||||
|
||||
public static final String TAG_DEPLOYMENT = "deployment";
|
||||
|
||||
public static final String TAG_ROUTE = "route";
|
||||
|
||||
public static final String TAG_REPLICA = "replica";
|
||||
|
||||
public static final String TAG_APPLICATION = "application";
|
||||
|
||||
private static final boolean canBeUsed =
|
||||
Ray.isInitialized() && !Ray.getRuntimeContext().isLocalMode();
|
||||
|
||||
private static volatile boolean enabled = true;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private RayServeMetrics(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static void execute(Runnable runnable) {
|
||||
if (!enabled || !canBeUsed) {
|
||||
return;
|
||||
}
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public static void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public static void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
/** Listener of long poll. It notifies changed object to the specified key. */
|
||||
@FunctionalInterface
|
||||
public interface KeyListener {
|
||||
|
||||
void notifyChanged(Object updatedObject);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** Key type of long poll. */
|
||||
public class KeyType implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8838552786234630401L;
|
||||
|
||||
private final LongPollNamespace longPollNamespace;
|
||||
|
||||
private final String key;
|
||||
|
||||
private int hashCode;
|
||||
|
||||
private String name;
|
||||
|
||||
public KeyType(LongPollNamespace longPollNamespace, String key) {
|
||||
this.longPollNamespace = longPollNamespace;
|
||||
this.key = key;
|
||||
this.hashCode = Objects.hash(this.longPollNamespace, this.key);
|
||||
this.name = parseName();
|
||||
}
|
||||
|
||||
public LongPollNamespace getLongPollNamespace() {
|
||||
return longPollNamespace;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
KeyType keyType = (KeyType) obj;
|
||||
return Objects.equals(longPollNamespace, keyType.getLongPollNamespace())
|
||||
&& Objects.equals(key, keyType.getKey());
|
||||
}
|
||||
|
||||
private String parseName() {
|
||||
if (longPollNamespace == null && StringUtils.isBlank(key)) {
|
||||
return "";
|
||||
}
|
||||
if (longPollNamespace == null) {
|
||||
return key;
|
||||
}
|
||||
if (StringUtils.isBlank(key)) {
|
||||
return longPollNamespace.name();
|
||||
}
|
||||
return "(" + longPollNamespace.name() + "," + key + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static KeyType parseFrom(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isBlank(key)) {
|
||||
return new KeyType(null, null);
|
||||
}
|
||||
if (key.startsWith("(")) {
|
||||
String[] fields = StringUtils.split(StringUtils.substring(key, 1, key.length() - 1), ",", 2);
|
||||
if (fields.length != 2) {
|
||||
throw new RayServeException("Illegal KeyType string: " + key);
|
||||
}
|
||||
return new KeyType(LongPollNamespace.parseFrom(fields[0]), fields[1].trim());
|
||||
}
|
||||
LongPollNamespace longPollNamespace = LongPollNamespace.parseFrom(key);
|
||||
if (null != longPollNamespace) {
|
||||
return new KeyType(longPollNamespace, null);
|
||||
}
|
||||
return new KeyType(null, key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import java.util.Map;
|
||||
|
||||
/** The asynchronous long polling client. */
|
||||
public class LongPollClient {
|
||||
|
||||
private Map<KeyType, KeyListener> keyListeners;
|
||||
|
||||
private boolean running;
|
||||
|
||||
public LongPollClient(BaseActorHandle hostActor, Map<KeyType, KeyListener> keyListeners) {
|
||||
Preconditions.checkArgument(keyListeners != null && keyListeners.size() != 0);
|
||||
LongPollClientFactory.register(hostActor, keyListeners);
|
||||
this.keyListeners = keyListeners;
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
public Map<KeyType, KeyListener> getKeyListeners() {
|
||||
return keyListeners;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.exception.RayActorException;
|
||||
import io.ray.api.exception.RayTaskException;
|
||||
import io.ray.api.exception.RayTimeoutException;
|
||||
import io.ray.api.function.PyActorMethod;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.controller.ServeController;
|
||||
import io.ray.serve.generated.DeploymentTargetInfo;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import io.ray.serve.util.CollectionUtil;
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** The long poll client factory that holds a asynchronous singleton thread. */
|
||||
public class LongPollClientFactory {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LongPollClientFactory.class);
|
||||
|
||||
/** Handle to actor embedding LongPollHost. */
|
||||
private static BaseActorHandle hostActor;
|
||||
|
||||
/** A set mapping keys to callbacks to be called on state update for the corresponding keys. */
|
||||
private static final Map<KeyType, KeyListener> KEY_LISTENERS = new ConcurrentHashMap<>();
|
||||
|
||||
public static final Map<KeyType, Integer> SNAPSHOT_IDS = new ConcurrentHashMap<>();
|
||||
|
||||
public static final Map<KeyType, Object> OBJECT_SNAPSHOTS = new ConcurrentHashMap<>();
|
||||
|
||||
private static ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
private static boolean inited = false;
|
||||
|
||||
private static long longPollTimoutS = 1L;
|
||||
|
||||
public static final Map<LongPollNamespace, Function<byte[], Object>> DESERIALIZERS =
|
||||
new HashMap<>();
|
||||
|
||||
static {
|
||||
DESERIALIZERS.put(LongPollNamespace.ROUTE_TABLE, ServeProtoUtil::parseEndpointSet);
|
||||
DESERIALIZERS.put(
|
||||
LongPollNamespace.DEPLOYMENT_TARGETS,
|
||||
bytes -> ServeProtoUtil.bytesToProto(bytes, DeploymentTargetInfo::parseFrom));
|
||||
}
|
||||
|
||||
public static void register(BaseActorHandle hostActor, Map<KeyType, KeyListener> keyListeners) {
|
||||
init(hostActor);
|
||||
if (!inited) {
|
||||
return;
|
||||
}
|
||||
KEY_LISTENERS.putAll(keyListeners);
|
||||
for (KeyType keyType : keyListeners.keySet()) {
|
||||
SNAPSHOT_IDS.put(keyType, -1);
|
||||
}
|
||||
LOGGER.info("LongPollClient registered keys: {}.", keyListeners.keySet());
|
||||
try {
|
||||
pollNext();
|
||||
} catch (RayTimeoutException e) {
|
||||
LOGGER.info("Register poll timeout. keys:{}", keyListeners.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void init(BaseActorHandle hostActor) {
|
||||
if (inited) {
|
||||
return;
|
||||
}
|
||||
long intervalS = 10L;
|
||||
try {
|
||||
ReplicaContext replicaContext = Serve.getReplicaContext();
|
||||
boolean enabled =
|
||||
Optional.ofNullable(replicaContext.getConfig())
|
||||
.map(config -> config.get(RayServeConfig.LONG_POOL_CLIENT_ENABLED))
|
||||
.map(Boolean::valueOf)
|
||||
.orElse(true);
|
||||
if (!enabled) {
|
||||
LOGGER.info("LongPollClient is disabled.");
|
||||
return;
|
||||
}
|
||||
if (null == hostActor) {
|
||||
hostActor = Ray.getActor(Constants.SERVE_CONTROLLER_NAME, Constants.SERVE_NAMESPACE).get();
|
||||
}
|
||||
intervalS =
|
||||
Optional.ofNullable(replicaContext.getConfig())
|
||||
.map(config -> config.get(RayServeConfig.LONG_POOL_CLIENT_INTERVAL))
|
||||
.map(Long::valueOf)
|
||||
.orElse(10L);
|
||||
longPollTimoutS =
|
||||
Optional.ofNullable(replicaContext.getConfig())
|
||||
.map(config -> config.get(RayServeConfig.LONG_POOL_CLIENT_TIMEOUT_S))
|
||||
.map(Long::valueOf)
|
||||
.orElse(10L);
|
||||
} catch (Exception e) {
|
||||
LOGGER.info(
|
||||
"Serve.getReplicaContext()` may only be called from within a Ray Serve deployment.");
|
||||
}
|
||||
|
||||
Preconditions.checkNotNull(hostActor);
|
||||
LongPollClientFactory.hostActor = hostActor;
|
||||
|
||||
scheduledExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor(
|
||||
new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread thread = new Thread(r, "ray-serve-long-poll-client-thread");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
long finalIntervalS = intervalS;
|
||||
scheduledExecutorService.scheduleWithFixedDelay(
|
||||
() -> {
|
||||
try {
|
||||
pollNext();
|
||||
} catch (RayTimeoutException e) {
|
||||
LOGGER.info(
|
||||
"long poll timeout in {} seconds, execute next poll after {} seconds.",
|
||||
longPollTimoutS,
|
||||
finalIntervalS);
|
||||
} catch (RayActorException e) {
|
||||
LOGGER.error("LongPollClient failed to connect to host. Shutting down.");
|
||||
stop();
|
||||
} catch (RayTaskException e) {
|
||||
LOGGER.error("LongPollHost errored", e);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("LongPollClient failed to update object of key {}", SNAPSHOT_IDS, e);
|
||||
}
|
||||
},
|
||||
0L,
|
||||
intervalS,
|
||||
TimeUnit.SECONDS);
|
||||
inited = true;
|
||||
LOGGER.info("LongPollClient was initialized");
|
||||
}
|
||||
|
||||
/** Poll the updates. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static synchronized void pollNext() {
|
||||
LOGGER.info("LongPollClient polls next snapshotIds {}", SNAPSHOT_IDS);
|
||||
LongPollRequest longPollRequest = new LongPollRequest(SNAPSHOT_IDS);
|
||||
LongPollResult longPollResult = null;
|
||||
if (hostActor instanceof PyActorHandle) {
|
||||
// Poll from python controller.
|
||||
ObjectRef<Object> currentRef =
|
||||
((PyActorHandle) hostActor)
|
||||
.task(
|
||||
PyActorMethod.of(Constants.CONTROLLER_LISTEN_FOR_CHANGE_METHOD),
|
||||
longPollRequest.toProtobuf().toByteArray())
|
||||
.remote();
|
||||
Object data = Ray.get(currentRef, longPollTimoutS * 1000);
|
||||
longPollResult = LongPollResult.parseFrom((byte[]) data);
|
||||
} else {
|
||||
// Poll from java controller.
|
||||
ObjectRef<byte[]> currentRef =
|
||||
((ActorHandle<ServeController>) hostActor)
|
||||
.task(ServeController::listenForChange, longPollRequest)
|
||||
.remote();
|
||||
longPollResult = LongPollResult.parseFrom(currentRef.get(longPollTimoutS * 1000));
|
||||
}
|
||||
processUpdate(longPollResult == null ? null : longPollResult.getUpdatedObjects());
|
||||
}
|
||||
|
||||
public static void processUpdate(Map<KeyType, UpdatedObject> updates) {
|
||||
if (updates == null || updates.isEmpty()) {
|
||||
LOGGER.info("LongPollClient received nothing.");
|
||||
return;
|
||||
}
|
||||
LOGGER.info("LongPollClient received updates for keys: {}", updates.keySet());
|
||||
for (Map.Entry<KeyType, UpdatedObject> entry : updates.entrySet()) {
|
||||
KeyType keyType = entry.getKey();
|
||||
UpdatedObject updatedObject = entry.getValue();
|
||||
|
||||
Object objectSnapshot = updatedObject.getObjectSnapshot();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
"The updated object for key {} is {}",
|
||||
keyType,
|
||||
ReflectionToStringBuilder.toString(objectSnapshot));
|
||||
}
|
||||
|
||||
KeyListener keyListener = KEY_LISTENERS.get(entry.getKey());
|
||||
if (keyListener == null) {
|
||||
LOGGER.warn(
|
||||
"LongPollClient has no listener for key: {}, maybe this key was garbage collected.",
|
||||
entry.getKey());
|
||||
continue;
|
||||
}
|
||||
KEY_LISTENERS.get(entry.getKey()).notifyChanged(objectSnapshot);
|
||||
OBJECT_SNAPSHOTS.put(entry.getKey(), objectSnapshot);
|
||||
SNAPSHOT_IDS.put(entry.getKey(), entry.getValue().getSnapshotId());
|
||||
}
|
||||
}
|
||||
|
||||
public static void unregister(Set<KeyType> keys) {
|
||||
if (CollectionUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
for (KeyType keyType : keys) {
|
||||
SNAPSHOT_IDS.remove(keyType);
|
||||
KEY_LISTENERS.remove(keyType);
|
||||
OBJECT_SNAPSHOTS.remove(keyType);
|
||||
}
|
||||
LOGGER.info("LongPollClient unregistered keys: {}.", keys);
|
||||
}
|
||||
|
||||
public static synchronized void stop() {
|
||||
if (!inited) {
|
||||
return;
|
||||
}
|
||||
if (scheduledExecutorService != null) {
|
||||
scheduledExecutorService.shutdown();
|
||||
try {
|
||||
scheduledExecutorService.awaitTermination(longPollTimoutS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("awaitTermination error, the exception is ", e);
|
||||
}
|
||||
}
|
||||
KEY_LISTENERS.clear();
|
||||
OBJECT_SNAPSHOTS.clear();
|
||||
SNAPSHOT_IDS.clear();
|
||||
inited = false;
|
||||
LOGGER.info("LongPollClient was stopped.");
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return inited;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** The long poll namespace enum. */
|
||||
public enum LongPollNamespace {
|
||||
DEPLOYMENT_TARGETS,
|
||||
|
||||
ROUTE_TABLE;
|
||||
|
||||
public static LongPollNamespace parseFrom(String key) {
|
||||
for (LongPollNamespace namespace : LongPollNamespace.values()) {
|
||||
if (StringUtils.equals(key, namespace.name())) {
|
||||
return namespace;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "." + this.name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
public class LongPollRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 6700308335001848617L;
|
||||
|
||||
private Map<KeyType, Integer> keysToSnapshotIds;
|
||||
|
||||
public LongPollRequest(Map<KeyType, Integer> keysToSnapshotIds) {
|
||||
this.keysToSnapshotIds = keysToSnapshotIds;
|
||||
}
|
||||
|
||||
public Map<KeyType, Integer> getKeysToSnapshotIds() {
|
||||
return keysToSnapshotIds;
|
||||
}
|
||||
|
||||
public io.ray.serve.generated.LongPollRequest toProtobuf() {
|
||||
io.ray.serve.generated.LongPollRequest.Builder builder =
|
||||
io.ray.serve.generated.LongPollRequest.newBuilder();
|
||||
if (keysToSnapshotIds != null) {
|
||||
keysToSnapshotIds.entrySet().stream()
|
||||
.forEach(
|
||||
entry -> builder.putKeysToSnapshotIds(entry.getKey().toString(), entry.getValue()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LongPollResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 6603042077360718438L;
|
||||
|
||||
Map<KeyType, UpdatedObject> updatedObjects;
|
||||
|
||||
public Map<KeyType, UpdatedObject> getUpdatedObjects() {
|
||||
return updatedObjects;
|
||||
}
|
||||
|
||||
public void setUpdatedObjects(Map<KeyType, UpdatedObject> updatedObjects) {
|
||||
this.updatedObjects = updatedObjects;
|
||||
}
|
||||
|
||||
public static LongPollResult parseFrom(byte[] longPollResultBytes) {
|
||||
if (longPollResultBytes == null) {
|
||||
return null;
|
||||
}
|
||||
io.ray.serve.generated.LongPollResult pbLongPollResult = null;
|
||||
try {
|
||||
pbLongPollResult = io.ray.serve.generated.LongPollResult.parseFrom(longPollResultBytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse LongPollResult from protobuf bytes.", e);
|
||||
}
|
||||
if (pbLongPollResult == null) {
|
||||
return null;
|
||||
}
|
||||
LongPollResult longPollResult = new LongPollResult();
|
||||
if (pbLongPollResult.getUpdatedObjectsMap() != null) {
|
||||
Map<KeyType, UpdatedObject> updatedObjects =
|
||||
new HashMap<>(pbLongPollResult.getUpdatedObjectsMap().size());
|
||||
for (Map.Entry<String, io.ray.serve.generated.UpdatedObject> entry :
|
||||
pbLongPollResult.getUpdatedObjectsMap().entrySet()) {
|
||||
KeyType keyType = KeyType.parseFrom(entry.getKey());
|
||||
updatedObjects.put(keyType, UpdatedObject.parseFrom(keyType, entry.getValue()));
|
||||
}
|
||||
longPollResult.setUpdatedObjects(updatedObjects);
|
||||
}
|
||||
return longPollResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class UpdatedObject implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 4079205733405537177L;
|
||||
|
||||
private int snapshotId;
|
||||
|
||||
private Object objectSnapshot;
|
||||
|
||||
public int getSnapshotId() {
|
||||
return snapshotId;
|
||||
}
|
||||
|
||||
public void setSnapshotId(int snapshotId) {
|
||||
this.snapshotId = snapshotId;
|
||||
}
|
||||
|
||||
public Object getObjectSnapshot() {
|
||||
return objectSnapshot;
|
||||
}
|
||||
|
||||
public void setObjectSnapshot(Object objectSnapshot) {
|
||||
this.objectSnapshot = objectSnapshot;
|
||||
}
|
||||
|
||||
public static UpdatedObject parseFrom(
|
||||
KeyType keyType, io.ray.serve.generated.UpdatedObject pbUpdatedObject) {
|
||||
if (pbUpdatedObject == null) {
|
||||
return null;
|
||||
}
|
||||
UpdatedObject updatedObject = new UpdatedObject();
|
||||
updatedObject.setSnapshotId(pbUpdatedObject.getSnapshotId());
|
||||
|
||||
Function<byte[], Object> deserializer =
|
||||
LongPollClientFactory.DESERIALIZERS.get(keyType.getLongPollNamespace());
|
||||
if (deserializer == null) {
|
||||
throw new RayServeException(
|
||||
"No deserializer for LongPollNamespace: " + keyType.getLongPollNamespace());
|
||||
}
|
||||
updatedObject.setObjectSnapshot(
|
||||
deserializer.apply(pbUpdatedObject.getObjectSnapshot().toByteArray()));
|
||||
return updatedObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
public interface RayServeReplica {
|
||||
|
||||
Object handleRequest(Object requestMetadata, Object requestArgs);
|
||||
|
||||
default Object reconfigure(byte[] deploymentConfigBytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean checkHealth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean prepareForShutdown() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default int getNumOngoingRequests() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.runtime.metric.Count;
|
||||
import io.ray.runtime.metric.Gauge;
|
||||
import io.ray.runtime.metric.Histogram;
|
||||
import io.ray.runtime.metric.Metrics;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.context.RequestContext;
|
||||
import io.ray.serve.deployment.DeploymentId;
|
||||
import io.ray.serve.deployment.DeploymentVersion;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.metrics.RayServeMetrics;
|
||||
import io.ray.serve.router.Query;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import io.ray.serve.util.ReflectUtil;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Handles requests with the provided callable. */
|
||||
public class RayServeReplicaImpl implements RayServeReplica {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RayServeReplicaImpl.class);
|
||||
|
||||
private String deploymentName;
|
||||
|
||||
private DeploymentId deploymentId;
|
||||
|
||||
private String replicaTag;
|
||||
|
||||
private DeploymentConfig config;
|
||||
|
||||
private AtomicInteger numOngoingRequests = new AtomicInteger();
|
||||
|
||||
private Object callable;
|
||||
|
||||
private Count requestCounter;
|
||||
|
||||
private Count errorCounter;
|
||||
|
||||
private Count restartCounter;
|
||||
|
||||
private Histogram processingLatencyTracker;
|
||||
|
||||
private Gauge numProcessingItems;
|
||||
|
||||
private DeploymentVersion version;
|
||||
|
||||
private boolean isDeleted = false;
|
||||
|
||||
private final Method checkHealthMethod;
|
||||
|
||||
private final Method callMethod;
|
||||
|
||||
public RayServeReplicaImpl(
|
||||
Object callable,
|
||||
DeploymentConfig deploymentConfig,
|
||||
DeploymentVersion version,
|
||||
BaseActorHandle actorHandle,
|
||||
String appName) {
|
||||
this.deploymentName = Serve.getReplicaContext().getDeploymentName();
|
||||
this.deploymentId = new DeploymentId(this.deploymentName, appName);
|
||||
this.replicaTag = Serve.getReplicaContext().getReplicaTag();
|
||||
this.callable = callable;
|
||||
this.config = deploymentConfig;
|
||||
this.version = version;
|
||||
this.checkHealthMethod = getRunnerMethod(Constants.CHECK_HEALTH_METHOD, null, true);
|
||||
this.callMethod = getRunnerMethod(Constants.CALL_METHOD, new Object[] {new Object()}, true);
|
||||
registerMetrics();
|
||||
}
|
||||
|
||||
private void registerMetrics() {
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
requestCounter =
|
||||
Metrics.count()
|
||||
.name(RayServeMetrics.SERVE_DEPLOYMENT_REQUEST_COUNTER.getName())
|
||||
.description(RayServeMetrics.SERVE_DEPLOYMENT_REQUEST_COUNTER.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentName,
|
||||
RayServeMetrics.TAG_REPLICA,
|
||||
replicaTag))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
errorCounter =
|
||||
Metrics.count()
|
||||
.name(RayServeMetrics.SERVE_DEPLOYMENT_ERROR_COUNTER.getName())
|
||||
.description(RayServeMetrics.SERVE_DEPLOYMENT_ERROR_COUNTER.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentName,
|
||||
RayServeMetrics.TAG_REPLICA,
|
||||
replicaTag))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
restartCounter =
|
||||
Metrics.count()
|
||||
.name(RayServeMetrics.SERVE_DEPLOYMENT_REPLICA_STARTS.getName())
|
||||
.description(RayServeMetrics.SERVE_DEPLOYMENT_REPLICA_STARTS.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentName,
|
||||
RayServeMetrics.TAG_REPLICA,
|
||||
replicaTag))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
processingLatencyTracker =
|
||||
Metrics.histogram()
|
||||
.name(RayServeMetrics.SERVE_DEPLOYMENT_PROCESSING_LATENCY_MS.getName())
|
||||
.description(
|
||||
RayServeMetrics.SERVE_DEPLOYMENT_PROCESSING_LATENCY_MS.getDescription())
|
||||
.unit("")
|
||||
.boundaries(Constants.DEFAULT_LATENCY_BUCKET_MS)
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentName,
|
||||
RayServeMetrics.TAG_REPLICA,
|
||||
replicaTag))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
numProcessingItems =
|
||||
Metrics.gauge()
|
||||
.name(RayServeMetrics.SERVE_REPLICA_PROCESSING_QUERIES.getName())
|
||||
.description(RayServeMetrics.SERVE_REPLICA_PROCESSING_QUERIES.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentName,
|
||||
RayServeMetrics.TAG_REPLICA,
|
||||
replicaTag))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(() -> restartCounter.inc(1.0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleRequest(Object requestMetadata, Object requestArgs) {
|
||||
// TODO 1101 wrap_user_method_call
|
||||
long startTime = System.currentTimeMillis();
|
||||
Query request = new Query((RequestMetadata) requestMetadata, requestArgs);
|
||||
LOGGER.debug(
|
||||
"Replica {} received request {}", replicaTag, request.getMetadata().getRequestId());
|
||||
|
||||
numOngoingRequests.incrementAndGet();
|
||||
RayServeMetrics.execute(() -> numProcessingItems.update(numOngoingRequests.get()));
|
||||
Object result = invokeSingle(request);
|
||||
numOngoingRequests.decrementAndGet();
|
||||
|
||||
long requestTimeMs = System.currentTimeMillis() - startTime;
|
||||
LOGGER.debug(
|
||||
"Replica {} finished request {} in {}ms",
|
||||
replicaTag,
|
||||
request.getMetadata().getRequestId(),
|
||||
requestTimeMs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object invokeSingle(Query requestItem) {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Method methodToCall = null;
|
||||
RequestMetadata requestMetadata = requestItem.getMetadata();
|
||||
try {
|
||||
RequestContext.set(
|
||||
requestMetadata.getRoute(),
|
||||
requestMetadata.getRequestId(),
|
||||
deploymentId.getApp(),
|
||||
requestMetadata.getMultiplexedModelId());
|
||||
LOGGER.info("Started executing request {}", requestMetadata.getRequestId());
|
||||
|
||||
Object[] args = parseRequestItem(requestItem);
|
||||
methodToCall =
|
||||
args.length == 1 && callMethod != null
|
||||
? callMethod
|
||||
: getRunnerMethod(requestItem.getMetadata().getCallMethod(), args, false);
|
||||
Object result = methodToCall.invoke(callable, args);
|
||||
RayServeMetrics.execute(() -> requestCounter.inc(1.0));
|
||||
return result;
|
||||
} catch (Throwable e) {
|
||||
RayServeMetrics.execute(() -> errorCounter.inc(1.0));
|
||||
throw new RayServeException(
|
||||
MessageFormatter.format(
|
||||
"Replica {} failed to invoke method {}",
|
||||
replicaTag,
|
||||
methodToCall == null ? "unknown" : methodToCall.getName()),
|
||||
e);
|
||||
} finally {
|
||||
RayServeMetrics.execute(
|
||||
() -> processingLatencyTracker.update(System.currentTimeMillis() - start));
|
||||
RequestContext.clean();
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] parseRequestItem(Query requestItem) {
|
||||
if (requestItem.getArgs() == null) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
if (requestItem.getArgs() instanceof Object[]) {
|
||||
return (Object[]) requestItem.getArgs();
|
||||
}
|
||||
|
||||
return new Object[] {requestItem.getArgs()};
|
||||
}
|
||||
|
||||
private Method getRunnerMethod(String methodName, Object[] args, boolean isNullable) {
|
||||
try {
|
||||
return ReflectUtil.getMethod(callable.getClass(), methodName, args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"Tried to call a method {} that does not exist. Available methods: {}",
|
||||
methodName,
|
||||
ReflectUtil.getMethodStrings(callable.getClass()));
|
||||
if (isNullable) {
|
||||
LOGGER.warn(errMsg);
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.error(errMsg, e);
|
||||
throw new RayServeException(errMsg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform graceful shutdown. Trigger a graceful shutdown protocol that will wait for all the
|
||||
* queued tasks to be completed and return to the controller.
|
||||
*
|
||||
* @return true if it is ready for shutdown.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean prepareForShutdown() {
|
||||
while (true) {
|
||||
// Sleep first because we want to make sure all the routers receive the notification to remove
|
||||
// this replica first.
|
||||
try {
|
||||
Thread.sleep((long) (config.getGracefulShutdownWaitLoopS() * 1000));
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(
|
||||
"Replica {} was interrupted in sheep when draining pending queries", replicaTag);
|
||||
}
|
||||
int numOngoingRequest = getNumOngoingRequests();
|
||||
if (numOngoingRequest > 0) {
|
||||
LOGGER.info(
|
||||
"Waiting for an additional {}s to shut down because there are {} ongoing requests.",
|
||||
config.getGracefulShutdownWaitLoopS(),
|
||||
numOngoingRequest);
|
||||
} else {
|
||||
LOGGER.info("Graceful shutdown complete; replica exiting.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly call the del method to trigger clean up. We set isDeleted = true after
|
||||
// succssifully calling it so the destructor is called only once.
|
||||
try {
|
||||
if (!isDeleted) {
|
||||
ReflectUtil.getMethod(callable.getClass(), "del").invoke(callable);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.warn("Deployment {} has no del method.", deploymentName);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Exception during graceful shutdown of replica.");
|
||||
} finally {
|
||||
isDeleted = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumOngoingRequests() {
|
||||
return numOngoingRequests.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeploymentVersion reconfigure(byte[] deploymentConfigBytes) {
|
||||
config = DeploymentConfig.fromProtoBytes(deploymentConfigBytes);
|
||||
Object userConfig = config.getUserConfig();
|
||||
DeploymentVersion deploymentVersion =
|
||||
new DeploymentVersion(version.getCodeVersion(), config, version.getRayActorOptions());
|
||||
version = deploymentVersion;
|
||||
if (userConfig != null) {
|
||||
updateUserConfig(userConfig);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
public void updateUserConfig(Object userConfig) {
|
||||
LOGGER.info(
|
||||
"Replica {} of deployment {} reconfigure userConfig: {}",
|
||||
replicaTag,
|
||||
deploymentName,
|
||||
userConfig);
|
||||
try {
|
||||
ReflectUtil.getMethod(callable.getClass(), Constants.RECONFIGURE_METHOD, userConfig)
|
||||
.invoke(callable, userConfig);
|
||||
} catch (NoSuchMethodException e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"userConfig specified but deployment {} missing {} method",
|
||||
deploymentId,
|
||||
Constants.RECONFIGURE_METHOD);
|
||||
LOGGER.error(errMsg);
|
||||
throw new RayServeException(errMsg, e);
|
||||
} catch (Throwable e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"Replica {} of deployment {} failed to reconfigure userConfig {}",
|
||||
replicaTag,
|
||||
deploymentId,
|
||||
userConfig);
|
||||
LOGGER.error(errMsg);
|
||||
throw new RayServeException(errMsg, e);
|
||||
} finally {
|
||||
LOGGER.info(
|
||||
"Replica {} of deployment {} finished reconfiguring userConfig: {}",
|
||||
replicaTag,
|
||||
deploymentId,
|
||||
userConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public DeploymentVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkHealth() {
|
||||
if (checkHealthMethod == null) {
|
||||
return true;
|
||||
}
|
||||
boolean result = true;
|
||||
try {
|
||||
LOGGER.info(
|
||||
"Replica {} of deployment {} check health of {}",
|
||||
replicaTag,
|
||||
deploymentName,
|
||||
callable.getClass().getName());
|
||||
Object isHealthy = checkHealthMethod.invoke(callable);
|
||||
if (!(isHealthy instanceof Boolean)) {
|
||||
LOGGER.error(
|
||||
"The health check result {} of {} in replica {} of deployment {} is illegal.",
|
||||
isHealthy == null ? "null" : isHealthy.getClass().getName() + ":" + isHealthy,
|
||||
callable.getClass().getName(),
|
||||
replicaTag,
|
||||
deploymentName);
|
||||
result = false;
|
||||
} else {
|
||||
result = (boolean) isHealthy;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(
|
||||
"Replica {} of deployment {} failed to check health of {}",
|
||||
replicaTag,
|
||||
deploymentName,
|
||||
callable.getClass().getName(),
|
||||
e);
|
||||
result = false;
|
||||
} finally {
|
||||
LOGGER.info(
|
||||
"The health check result of {} in replica {} of deployment {} is {}.",
|
||||
callable.getClass().getName(),
|
||||
replicaTag,
|
||||
deploymentName,
|
||||
result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object getCallable() {
|
||||
return callable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.runtime.serializer.MessagePackSerializer;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.deployment.DeploymentVersion;
|
||||
import io.ray.serve.deployment.DeploymentWrapper;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.metrics.RayServeMetrics;
|
||||
import io.ray.serve.util.MessageFormatter;
|
||||
import io.ray.serve.util.ReflectUtil;
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Replica class wrapping the provided class. Note that Java function is not supported now. */
|
||||
public class RayServeWrappedReplica implements RayServeReplica {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RayServeReplicaImpl.class);
|
||||
|
||||
private DeploymentWrapper deploymentInfo;
|
||||
|
||||
private RayServeReplicaImpl replica;
|
||||
|
||||
public RayServeWrappedReplica(
|
||||
String deploymentName,
|
||||
String replicaTag,
|
||||
String deploymentDef,
|
||||
byte[] initArgsbytes,
|
||||
byte[] deploymentConfigBytes,
|
||||
byte[] deploymentVersionBytes,
|
||||
String appName) {
|
||||
|
||||
// Parse DeploymentConfig.
|
||||
DeploymentConfig deploymentConfig = DeploymentConfig.fromProtoBytes(deploymentConfigBytes);
|
||||
|
||||
// Parse init args.
|
||||
Object[] initArgs = null;
|
||||
try {
|
||||
initArgs = parseInitArgs(initArgsbytes);
|
||||
} catch (IOException e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"Failed to initialize replica {} of deployment {}",
|
||||
replicaTag,
|
||||
deploymentInfo.getName());
|
||||
LOGGER.error(errMsg, e);
|
||||
throw new RayServeException(errMsg, e);
|
||||
}
|
||||
|
||||
DeploymentVersion version = DeploymentVersion.fromProtoBytes(deploymentVersionBytes);
|
||||
|
||||
DeploymentWrapper deploymentWrapper = new DeploymentWrapper();
|
||||
deploymentWrapper.setName(deploymentName);
|
||||
deploymentWrapper.setDeploymentDef(deploymentDef);
|
||||
deploymentWrapper.setDeploymentConfig(deploymentConfig);
|
||||
deploymentWrapper.setInitArgs(initArgs);
|
||||
deploymentWrapper.setDeploymentVersion(version);
|
||||
deploymentWrapper.setAppName(appName);
|
||||
|
||||
// Init replica.
|
||||
init(deploymentWrapper, replicaTag);
|
||||
}
|
||||
|
||||
public RayServeWrappedReplica(DeploymentWrapper deploymentWrapper, String replicaTag) {
|
||||
init(deploymentWrapper, replicaTag);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void init(DeploymentWrapper deploymentWrapper, String replicaTag) {
|
||||
try {
|
||||
// Set the controller name so that Serve.connect() in the user's code will connect to the
|
||||
// instance that this deployment is running in.
|
||||
Serve.setInternalReplicaContext(
|
||||
deploymentWrapper.getName(),
|
||||
replicaTag,
|
||||
null,
|
||||
deploymentWrapper.getConfig(),
|
||||
deploymentWrapper.getAppName());
|
||||
|
||||
// Instantiate the object defined by deploymentDef.
|
||||
Class deploymentClass =
|
||||
Class.forName(
|
||||
deploymentWrapper.getDeploymentDef(),
|
||||
true,
|
||||
Optional.ofNullable(Thread.currentThread().getContextClassLoader())
|
||||
.orElse(getClass().getClassLoader()));
|
||||
Object callable =
|
||||
ReflectUtil.getConstructor(deploymentClass, deploymentWrapper.getInitArgs())
|
||||
.newInstance(deploymentWrapper.getInitArgs());
|
||||
Serve.getReplicaContext().setServableObject(callable);
|
||||
|
||||
// Get the controller by name.
|
||||
Optional<BaseActorHandle> optional =
|
||||
Ray.getActor(Constants.SERVE_CONTROLLER_NAME, Constants.SERVE_NAMESPACE);
|
||||
Preconditions.checkState(optional.isPresent(), "Controller does not exist");
|
||||
|
||||
// Enable metrics.
|
||||
enableMetrics(deploymentWrapper.getConfig());
|
||||
|
||||
// Construct worker replica.
|
||||
this.replica =
|
||||
new RayServeReplicaImpl(
|
||||
callable,
|
||||
deploymentWrapper.getDeploymentConfig(),
|
||||
deploymentWrapper.getDeploymentVersion(),
|
||||
optional.get(),
|
||||
deploymentWrapper.getAppName());
|
||||
this.deploymentInfo = deploymentWrapper;
|
||||
} catch (Throwable e) {
|
||||
String errMsg =
|
||||
MessageFormatter.format(
|
||||
"Failed to initialize replica {} of deployment {}",
|
||||
replicaTag,
|
||||
deploymentWrapper.getName());
|
||||
LOGGER.error(errMsg, e);
|
||||
throw new RayServeException(errMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableMetrics(Map<String, String> config) {
|
||||
Optional.ofNullable(config)
|
||||
.map(conf -> conf.get(RayServeConfig.METRICS_ENABLED))
|
||||
.ifPresent(
|
||||
enabled -> {
|
||||
if (Boolean.valueOf(enabled)) {
|
||||
RayServeMetrics.enable();
|
||||
} else {
|
||||
RayServeMetrics.disable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object[] parseInitArgs(byte[] initArgsbytes) throws IOException {
|
||||
|
||||
if (initArgsbytes == null || initArgsbytes.length == 0) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
return MessagePackSerializer.decode(initArgsbytes, Object[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The entry method to process the request.
|
||||
*
|
||||
* @param requestMetadata the real type is byte[] if this invocation is cross-language. Otherwise,
|
||||
* the real type is {@link io.ray.serve.generated.RequestMetadata}.
|
||||
* @param requestArgs The input parameters of the specified method of the object defined by
|
||||
* deploymentDef. The real type is serialized {@link io.ray.serve.generated.RequestWrapper} if
|
||||
* this invocation is cross-language. Otherwise, the real type is Object[].
|
||||
* @return the result of request being processed
|
||||
*/
|
||||
@Override
|
||||
public Object handleRequest(Object requestMetadata, Object requestArgs) {
|
||||
return replica.handleRequest(
|
||||
ServeProtoUtil.parseRequestMetadata((byte[]) requestMetadata), requestArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the actor is healthy.
|
||||
*
|
||||
* @return true if the actor is health, or return false.
|
||||
*/
|
||||
@Override
|
||||
public boolean checkHealth() {
|
||||
return replica.checkHealth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the caller this replica is successfully launched.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAllocated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the caller this replica is successfully initialized.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object isInitialized(byte[] deploymentConfigBytes) {
|
||||
Object deploymentVersion = reconfigure(deploymentConfigBytes);
|
||||
checkHealth();
|
||||
return deploymentVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until there is no request in processing. It is used for stopping replica gracefully.
|
||||
*
|
||||
* @return true if it is ready for shutdown.
|
||||
*/
|
||||
@Override
|
||||
public boolean prepareForShutdown() {
|
||||
return replica.prepareForShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconfigure user's configuration in the callable object through its reconfigure method.
|
||||
*
|
||||
* @param userConfig new user's configuration
|
||||
* @return DeploymentVersion. If the current invocation is crossing language, the
|
||||
* DeploymentVersion is serialized to protobuf byte[].
|
||||
*/
|
||||
@Override
|
||||
public Object reconfigure(byte[] deploymentConfigBytes) {
|
||||
DeploymentVersion deploymentVersion = replica.reconfigure(deploymentConfigBytes);
|
||||
return deploymentVersion.toProtoBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deployment version of the current replica.
|
||||
*
|
||||
* @return DeploymentVersion. If the current invocation is crossing language, the
|
||||
* DeploymentVersion is serialized to protobuf byte[].
|
||||
*/
|
||||
public Object getVersion() {
|
||||
DeploymentVersion deploymentVersion = replica.getVersion();
|
||||
return deploymentVersion.toProtoBytes();
|
||||
}
|
||||
|
||||
public Object getCallable() {
|
||||
return replica.getCallable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** Stores data for Serve API calls from within deployments. */
|
||||
public class ReplicaContext {
|
||||
|
||||
private String deploymentName;
|
||||
|
||||
private String replicaTag;
|
||||
|
||||
private Object servableObject;
|
||||
|
||||
private Map<String, String> config;
|
||||
|
||||
private String appName;
|
||||
|
||||
public ReplicaContext(
|
||||
String deploymentName,
|
||||
String replicaTag,
|
||||
Object servableObject,
|
||||
Map<String, String> config,
|
||||
String appName) {
|
||||
this.deploymentName = deploymentName;
|
||||
this.replicaTag = replicaTag;
|
||||
this.servableObject = servableObject;
|
||||
this.config = config;
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
public String getDeploymentName() {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
public void setDeploymentName(String deploymentName) {
|
||||
this.deploymentName = deploymentName;
|
||||
}
|
||||
|
||||
public String getReplicaTag() {
|
||||
return replicaTag;
|
||||
}
|
||||
|
||||
public void setReplicaTag(String replicaTag) {
|
||||
this.replicaTag = replicaTag;
|
||||
}
|
||||
|
||||
public Object getServableObject() {
|
||||
return servableObject;
|
||||
}
|
||||
|
||||
public void setServableObject(Object servableObject) {
|
||||
this.servableObject = servableObject;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Map<String, String> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
public class ReplicaName {
|
||||
|
||||
private String deploymentTag;
|
||||
|
||||
private String replicaSuffix;
|
||||
|
||||
private String replicaTag = "";
|
||||
|
||||
private String delimiter = "#";
|
||||
|
||||
public ReplicaName(String deploymentTag, String replicaSuffix) {
|
||||
this.deploymentTag = deploymentTag;
|
||||
this.replicaSuffix = replicaSuffix;
|
||||
this.replicaTag = deploymentTag + this.delimiter + replicaSuffix;
|
||||
}
|
||||
|
||||
public String getDeploymentTag() {
|
||||
return deploymentTag;
|
||||
}
|
||||
|
||||
public String getReplicaSuffix() {
|
||||
return replicaSuffix;
|
||||
}
|
||||
|
||||
public String getReplicaTag() {
|
||||
return replicaTag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ray.serve.router;
|
||||
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
|
||||
/** Wrap request arguments and meta data. */
|
||||
public class Query {
|
||||
|
||||
private RequestMetadata metadata;
|
||||
|
||||
/**
|
||||
* If this query is cross-language, the args is serialized {@link
|
||||
* io.ray.serve.generated.RequestWrapper}. Otherwise, it is Object[].
|
||||
*/
|
||||
private Object args;
|
||||
|
||||
public Query(RequestMetadata requestMetadata, Object args) {
|
||||
this.metadata = requestMetadata;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public RequestMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public Object getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.ray.serve.router;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.call.PyActorTaskCaller;
|
||||
import io.ray.api.function.PyActorMethod;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.DeploymentTargetInfo;
|
||||
import io.ray.serve.replica.RayServeWrappedReplica;
|
||||
import io.ray.serve.util.CollectionUtil;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.RandomUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Data structure representing a set of replica actor handles. */
|
||||
public class ReplicaSet { // TODO ReplicaScheduler
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReplicaSet.class);
|
||||
|
||||
// The key is the name of the actor, and the value is a set of all flight queries objectrefs of
|
||||
// the actor.
|
||||
private final Map<String, Set<ObjectRef<Object>>> inFlightQueries;
|
||||
|
||||
// Map the actor name to the handle of the actor.
|
||||
private final Map<String, BaseActorHandle> allActorHandles;
|
||||
|
||||
private boolean hasPullReplica = false;
|
||||
|
||||
public ReplicaSet(String deploymentName) {
|
||||
this.inFlightQueries = new ConcurrentHashMap<>();
|
||||
this.allActorHandles = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public synchronized void updateWorkerReplicas(Object deploymentTargetInfo) {
|
||||
if (null != deploymentTargetInfo) {
|
||||
Set<String> actorNameSet =
|
||||
new HashSet<>(((DeploymentTargetInfo) deploymentTargetInfo).getReplicaNamesList());
|
||||
Set<String> added = new HashSet<>(Sets.difference(actorNameSet, inFlightQueries.keySet()));
|
||||
Set<String> removed = new HashSet<>(Sets.difference(inFlightQueries.keySet(), actorNameSet));
|
||||
added.forEach(
|
||||
name -> {
|
||||
Optional<BaseActorHandle> handleOptional =
|
||||
Ray.getActor(name, Constants.SERVE_NAMESPACE);
|
||||
if (handleOptional.isPresent()) {
|
||||
allActorHandles.put(name, handleOptional.get());
|
||||
inFlightQueries.put(name, Sets.newConcurrentHashSet());
|
||||
} else {
|
||||
LOGGER.warn("Can not get actor handle. actor name is {}", name);
|
||||
}
|
||||
});
|
||||
removed.forEach(inFlightQueries::remove);
|
||||
removed.forEach(allActorHandles::remove);
|
||||
if (added.size() > 0 || removed.size() > 0) {
|
||||
LOGGER.info("ReplicaSet: +{}, -{} replicas.", added.size(), removed.size());
|
||||
}
|
||||
}
|
||||
hasPullReplica = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a query, submit it to a replica and return the object ref. This method will keep track of
|
||||
* the in flight queries for each replicas and only send a query to available replicas (determined
|
||||
* by the max_concurrent_quries value.)
|
||||
*
|
||||
* @param query the incoming query.
|
||||
* @return ray.ObjectRef
|
||||
*/
|
||||
public ObjectRef<Object> assignReplica(Query query) {
|
||||
ObjectRef<Object> assignedRef = tryAssignReplica(query);
|
||||
return assignedRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to assign query to a replica, return the object ref if succeeded or return None if it can't
|
||||
* assign this query to any replicas.
|
||||
*
|
||||
* @param query query the incoming query.
|
||||
* @return ray.ObjectRef
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private ObjectRef<Object> tryAssignReplica(Query query) {
|
||||
int loopCount = 0;
|
||||
while (!hasPullReplica && loopCount < 50) {
|
||||
try {
|
||||
TimeUnit.MICROSECONDS.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
List<BaseActorHandle> handles = new ArrayList<>(allActorHandles.values());
|
||||
if (CollectionUtil.isEmpty(handles)) {
|
||||
throw new RayServeException("ReplicaSet found no replica.");
|
||||
}
|
||||
int randomIndex = RandomUtils.nextInt(0, handles.size());
|
||||
BaseActorHandle replica =
|
||||
handles.get(randomIndex); // TODO controll concurrency using maxOngoingRequests
|
||||
LOGGER.debug("Assigned query {} to replica {}.", query.getMetadata().getRequestId(), replica);
|
||||
if (replica instanceof PyActorHandle) {
|
||||
Object[] args =
|
||||
Stream.concat(
|
||||
Stream.of(query.getMetadata().toByteArray()),
|
||||
Arrays.stream((Object[]) query.getArgs()))
|
||||
.toArray();
|
||||
PyActorTaskCaller<Object> pyCaller =
|
||||
new PyActorTaskCaller<>(
|
||||
(PyActorHandle) replica, PyActorMethod.of("handle_request_from_java"), args);
|
||||
return pyCaller.remote();
|
||||
} else {
|
||||
return ((ActorHandle<RayServeWrappedReplica>) replica)
|
||||
.task(
|
||||
RayServeWrappedReplica::handleRequest,
|
||||
query.getMetadata().toByteArray(),
|
||||
query.getArgs())
|
||||
.remote();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Set<ObjectRef<Object>>> getInFlightQueries() {
|
||||
return inFlightQueries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package io.ray.serve.router;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.runtime.metric.Count;
|
||||
import io.ray.runtime.metric.Gauge;
|
||||
import io.ray.runtime.metric.Metrics;
|
||||
import io.ray.serve.deployment.DeploymentId;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.metrics.RayServeMetrics;
|
||||
import io.ray.serve.poll.KeyListener;
|
||||
import io.ray.serve.poll.KeyType;
|
||||
import io.ray.serve.poll.LongPollClient;
|
||||
import io.ray.serve.poll.LongPollNamespace;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/** Router process incoming queries: assign a replica. */
|
||||
public class Router {
|
||||
|
||||
private ReplicaSet replicaSet;
|
||||
private LongPollClient longPollClient;
|
||||
|
||||
private Count numRouterRequests;
|
||||
private AtomicInteger numQueuedQueries = new AtomicInteger();
|
||||
private Gauge numQueuedQueriesGauge;
|
||||
|
||||
public Router(BaseActorHandle controllerHandle, DeploymentId deploymentId) {
|
||||
this.replicaSet = new ReplicaSet(deploymentId.getName());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
this.numRouterRequests =
|
||||
Metrics.count()
|
||||
.name(RayServeMetrics.SERVE_NUM_ROUTER_REQUESTS.getName())
|
||||
.description(RayServeMetrics.SERVE_NUM_ROUTER_REQUESTS.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentId.getName(),
|
||||
RayServeMetrics.TAG_APPLICATION,
|
||||
deploymentId.getApp()))
|
||||
.register());
|
||||
|
||||
RayServeMetrics.execute(
|
||||
() ->
|
||||
this.numQueuedQueriesGauge =
|
||||
Metrics.gauge()
|
||||
.name(RayServeMetrics.SERVE_DEPLOYMENT_QUEUED_QUERIES.getName())
|
||||
.description(RayServeMetrics.SERVE_DEPLOYMENT_QUEUED_QUERIES.getDescription())
|
||||
.unit("")
|
||||
.tags(
|
||||
ImmutableMap.of(
|
||||
RayServeMetrics.TAG_DEPLOYMENT,
|
||||
deploymentId.getName(),
|
||||
RayServeMetrics.TAG_APPLICATION,
|
||||
deploymentId.getApp()))
|
||||
.register());
|
||||
|
||||
Map<KeyType, KeyListener> keyListeners = new HashMap<>();
|
||||
keyListeners.put(
|
||||
new KeyType(LongPollNamespace.DEPLOYMENT_TARGETS, deploymentId.getName()),
|
||||
deploymentTargetInfo ->
|
||||
replicaSet.updateWorkerReplicas(deploymentTargetInfo)); // cross language
|
||||
this.longPollClient = new LongPollClient(controllerHandle, keyListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a query and returns an object ref represent the result.
|
||||
*
|
||||
* @param requestMetadata the metadata of incoming queries.
|
||||
* @param requestArgs the request body of incoming queries.
|
||||
* @return ray.ObjectRef
|
||||
*/
|
||||
public ObjectRef<Object> assignRequest(RequestMetadata requestMetadata, Object[] requestArgs) {
|
||||
RayServeMetrics.execute(() -> numRouterRequests.inc(1));
|
||||
numQueuedQueries.incrementAndGet();
|
||||
RayServeMetrics.execute(() -> numQueuedQueriesGauge.update(numQueuedQueries.get()));
|
||||
|
||||
try {
|
||||
return replicaSet.assignReplica(new Query(requestMetadata, requestArgs));
|
||||
} finally {
|
||||
numQueuedQueries.decrementAndGet();
|
||||
RayServeMetrics.execute(() -> numQueuedQueriesGauge.update(numQueuedQueries.get()));
|
||||
}
|
||||
}
|
||||
|
||||
public ReplicaSet getReplicaSet() {
|
||||
return replicaSet;
|
||||
}
|
||||
|
||||
public LongPollClient getLongPollClient() {
|
||||
return longPollClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class CollectionUtil {
|
||||
|
||||
public static <E> boolean isEmpty(Collection<E> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class CommonUtil {
|
||||
|
||||
public static String getDeploymentName(String deploymentDef) {
|
||||
return StringUtils.substringAfterLast(StringUtils.substringAfterLast(deploymentDef, "."), "$");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import io.ray.serve.dag.ClassNode;
|
||||
import io.ray.serve.dag.DAGNode;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DAGUtil {
|
||||
|
||||
public static String getNodeName(DAGNode node) {
|
||||
String nodeName = null;
|
||||
if (node instanceof ClassNode) {
|
||||
nodeName =
|
||||
Optional.ofNullable(node.getBoundOptions())
|
||||
.map(options -> (String) options.get("name"))
|
||||
.orElse(CommonUtil.getDeploymentName(((ClassNode) node).getClassName()));
|
||||
}
|
||||
return nodeName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
/** Ray Serve common log tool. */
|
||||
public class MessageFormatter {
|
||||
|
||||
public static String format(String messagePattern, Object... args) {
|
||||
return org.slf4j.helpers.MessageFormatter.arrayFormat(messagePattern, args).getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ProtobufBytesParser<T> {
|
||||
|
||||
T parse(byte[] bytes) throws InvalidProtocolBufferException;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** Tool class for reflection. */
|
||||
public class ReflectUtil {
|
||||
|
||||
/**
|
||||
* Get types of the parameters in input array, and make the types into a new array.
|
||||
*
|
||||
* @param parameters The input parameter array
|
||||
* @return Type array corresponding to the input parameter array
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Class[] getParameterTypes(Object[] parameters) {
|
||||
Class[] parameterTypes = null;
|
||||
if (ArrayUtils.isEmpty(parameters)) {
|
||||
return null;
|
||||
}
|
||||
parameterTypes = new Class[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a constructor whose each parameter's type is the most closed to the corresponding input
|
||||
* parameter in terms of Java inheritance system, while {@link Class#getConstructor(Class...)}
|
||||
* returns the one based on class's equal.
|
||||
*
|
||||
* @param targetClass the constructor's class
|
||||
* @param parameters the input parameters
|
||||
* @return a matching constructor of the target class
|
||||
* @throws NoSuchMethodException if a matching method is not found
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Constructor getConstructor(Class targetClass, Object... parameters)
|
||||
throws NoSuchMethodException {
|
||||
return reflect(
|
||||
targetClass.getConstructors(), (candidate) -> true, ".<init>", targetClass, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a method whose each parameter's type is the most closed to the corresponding input
|
||||
* parameter in terms of Java inheritance system, while {@link Class#getMethod(String, Class...)}
|
||||
* returns the one based on class's equal.
|
||||
*
|
||||
* @param targetClass the constructor's class
|
||||
* @param name the specified method's name
|
||||
* @param parameters the input parameters
|
||||
* @return a matching method of the target class
|
||||
* @throws NoSuchMethodException if a matching method is not found
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Method getMethod(Class targetClass, String name, Object... parameters)
|
||||
throws NoSuchMethodException {
|
||||
return reflect(
|
||||
targetClass.getMethods(),
|
||||
(candidate) -> StringUtils.equals(name, candidate.getName()),
|
||||
"." + name,
|
||||
targetClass,
|
||||
parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object of {@link Executable} of the specified class according to initialization
|
||||
* parameters. The result's every parameter's type is the same as, or is a subclass or
|
||||
* subinterface of, the type of the corresponding input parameter. This method returns the result
|
||||
* whose each parameter's type is the most closed to the corresponding input parameter in terms of
|
||||
* Java inheritance system.
|
||||
*
|
||||
* @param <T> the type of result which extends {@link Executable}
|
||||
* @param candidates a set of candidates
|
||||
* @param filter the filter deciding whether to select the input candidate
|
||||
* @param message a message representing the target executable object
|
||||
* @param targetClass the constructor's class
|
||||
* @param parameters the input parameters
|
||||
* @return a matching executable of the target class
|
||||
* @throws NoSuchMethodException if a matching method is not found
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static <T extends Executable> T reflect(
|
||||
T[] candidates,
|
||||
Function<T, Boolean> filter,
|
||||
String message,
|
||||
Class targetClass,
|
||||
Object... parameters)
|
||||
throws NoSuchMethodException {
|
||||
Class[] parameterTypes = getParameterTypes(parameters);
|
||||
T result = null;
|
||||
for (int i = 0; i < candidates.length; i++) {
|
||||
T candidate = candidates[i];
|
||||
if (filter.apply(candidate)
|
||||
&& assignable(parameterTypes, candidate.getParameterTypes())
|
||||
&& (result == null
|
||||
|| assignable(candidate.getParameterTypes(), result.getParameterTypes()))) {
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
throw new NoSuchMethodException(
|
||||
targetClass.getName() + message + argumentTypesToString(parameterTypes));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static boolean assignable(Class[] from, Class[] to) {
|
||||
if (from == null) {
|
||||
return to == null || to.length == 0;
|
||||
}
|
||||
|
||||
if (to == null) {
|
||||
return from.length == 0;
|
||||
}
|
||||
|
||||
if (from.length != to.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < from.length; i++) {
|
||||
if (!to[i].isAssignableFrom(from[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* It is copied from {@link Class#argumentTypesToString(Class[])}.
|
||||
*
|
||||
* @param argTypes array of Class object
|
||||
* @return Formatted string of the input Class array.
|
||||
*/
|
||||
private static String argumentTypesToString(Class<?>[] argTypes) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("(");
|
||||
if (argTypes != null) {
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
if (i > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
Class<?> c = argTypes[i];
|
||||
buf.append((c == null) ? "null" : c.getName());
|
||||
}
|
||||
}
|
||||
buf.append(")");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representing the specified class's all methods.
|
||||
*
|
||||
* @param targetClass the input class
|
||||
* @return the formatted string of the specified class's all methods.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static List<String> getMethodStrings(Class targetClass) {
|
||||
if (targetClass == null) {
|
||||
return null;
|
||||
}
|
||||
Method[] methods = targetClass.getMethods();
|
||||
if (methods == null || methods.length == 0) {
|
||||
return null;
|
||||
}
|
||||
List<String> methodStrings = new ArrayList<>();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
methodStrings.add(methods[i].toString());
|
||||
}
|
||||
return methodStrings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> List<T> getInstancesByClassNames(String classNames, Class<T> cls)
|
||||
throws ClassNotFoundException, InstantiationException, IllegalAccessException,
|
||||
IllegalArgumentException, InvocationTargetException, NoSuchMethodException,
|
||||
SecurityException {
|
||||
String[] classNameArray = StringUtils.split(classNames, ";");
|
||||
List<T> isntances = new ArrayList<>();
|
||||
for (String className : classNameArray) {
|
||||
isntances.add((T) Class.forName(className).getConstructor().newInstance());
|
||||
}
|
||||
return isntances;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.generated.EndpointInfo;
|
||||
import io.ray.serve.generated.EndpointSet;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.generated.RequestWrapper;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class ServeProtoUtil {
|
||||
|
||||
public static final String SERVE_PROTO_PARSE_ERROR_MSG =
|
||||
"Failed to parse {} from protobuf bytes.";
|
||||
|
||||
public static RequestMetadata parseRequestMetadata(byte[] requestMetadataBytes) {
|
||||
|
||||
// Get a builder from RequestMetadata(bytes) or create a new one.
|
||||
RequestMetadata.Builder builder = null;
|
||||
if (requestMetadataBytes == null) {
|
||||
builder = RequestMetadata.newBuilder();
|
||||
} else {
|
||||
RequestMetadata requestMetadata = null;
|
||||
try {
|
||||
requestMetadata = RequestMetadata.parseFrom(requestMetadataBytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse RequestMetadata from protobuf bytes.", e);
|
||||
}
|
||||
if (requestMetadata == null) {
|
||||
builder = RequestMetadata.newBuilder();
|
||||
} else {
|
||||
builder = RequestMetadata.newBuilder(requestMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default values.
|
||||
if (StringUtils.isBlank(builder.getCallMethod())) {
|
||||
builder.setCallMethod(Constants.CALL_METHOD);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static RequestWrapper parseRequestWrapper(byte[] httpRequestWrapperBytes) {
|
||||
|
||||
// Get a builder from HTTPRequestWrapper(bytes) or create a new one.
|
||||
RequestWrapper.Builder builder = null;
|
||||
if (httpRequestWrapperBytes == null) {
|
||||
builder = RequestWrapper.newBuilder();
|
||||
} else {
|
||||
RequestWrapper requestWrapper = null;
|
||||
try {
|
||||
requestWrapper = RequestWrapper.parseFrom(httpRequestWrapperBytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse RequestWrapper from protobuf bytes.", e);
|
||||
}
|
||||
if (requestWrapper == null) {
|
||||
builder = RequestWrapper.newBuilder();
|
||||
} else {
|
||||
builder = RequestWrapper.newBuilder(requestWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static Map<String, EndpointInfo> parseEndpointSet(byte[] endpointSetBytes) {
|
||||
if (endpointSetBytes == null) {
|
||||
return null;
|
||||
}
|
||||
EndpointSet endpointSet = null;
|
||||
try {
|
||||
endpointSet = EndpointSet.parseFrom(endpointSetBytes);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse EndpointSet from protobuf bytes.", e);
|
||||
}
|
||||
if (endpointSet == null) {
|
||||
return null;
|
||||
}
|
||||
return endpointSet.getEndpointsMap();
|
||||
}
|
||||
|
||||
public static <T> T bytesToProto(byte[] bytes, ProtobufBytesParser<T> protobufBytesParser) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
T proto = protobufBytesParser.parse(bytes);
|
||||
return proto;
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayServeException("Failed to parse protobuf bytes.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
public class SocketUtil {
|
||||
|
||||
public static final int PORT_RANGE_MAX = 65535;
|
||||
|
||||
public static int findAvailableTcpPort(int minPort) {
|
||||
int portRange = PORT_RANGE_MAX - minPort;
|
||||
int candidatePort = minPort;
|
||||
int searchCounter = 0;
|
||||
while (!isPortAvailable(candidatePort)) {
|
||||
candidatePort++;
|
||||
if (++searchCounter > portRange) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Could not find an available tcp port in the range [%d, %d] after %d attempts.",
|
||||
minPort, PORT_RANGE_MAX, searchCounter));
|
||||
}
|
||||
}
|
||||
return candidatePort;
|
||||
}
|
||||
|
||||
public static boolean isPortAvailable(int port) {
|
||||
ServerSocket socket;
|
||||
try {
|
||||
socket = new ServerSocket();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to create ServerSocket.", e);
|
||||
}
|
||||
|
||||
try {
|
||||
InetSocketAddress sa = new InetSocketAddress(port);
|
||||
socket.bind(sa);
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore this exception for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# This file is used by CrossLanguageDeploymentTest.java to test cross-language
|
||||
# invocation.
|
||||
from ray import serve
|
||||
|
||||
|
||||
def echo_server(v):
|
||||
return v
|
||||
|
||||
|
||||
@serve.deployment
|
||||
class Counter(object):
|
||||
def __init__(self, value):
|
||||
self.value = int(value)
|
||||
|
||||
def increase(self, delta):
|
||||
self.value += int(delta)
|
||||
return str(self.value)
|
||||
|
||||
def reconfigure(self, value_str):
|
||||
self.value = int(value_str)
|
||||
@@ -0,0 +1,82 @@
|
||||
package io.ray.serve;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.api.ServeControllerClient;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.poll.LongPollClientFactory;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.collections.Maps;
|
||||
|
||||
public abstract class BaseServeTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseServeTest.class);
|
||||
protected static ServeControllerClient client = null;
|
||||
|
||||
@BeforeMethod(alwaysRun = true)
|
||||
public static void startServe() {
|
||||
Map<String, String> config = Maps.newHashMap();
|
||||
// The default port 8000 is occupied by other processes on the ci platform.
|
||||
config.put(RayServeConfig.PROXY_HTTP_PORT, "8341"); // TODO(liuyang-my) Get an available port.
|
||||
client = Serve.start(config);
|
||||
}
|
||||
|
||||
@AfterMethod(alwaysRun = true)
|
||||
public static void shutdownServe() {
|
||||
try {
|
||||
Serve.shutdown();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("serve shutdown error", e);
|
||||
}
|
||||
try {
|
||||
Ray.shutdown();
|
||||
LOGGER.info("Base serve test shutdown ray. Is initialized:{}", Ray.isInitialized());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("ray shutdown error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean previousInited;
|
||||
|
||||
private static String previousNamespace;
|
||||
|
||||
public static void initRay() {
|
||||
previousInited = Ray.isInitialized();
|
||||
LOGGER.info("Base serve test. Previous inited:{}", previousInited);
|
||||
previousNamespace = System.getProperty("ray.job.namespace");
|
||||
LOGGER.info("Base serve test. Previous namespace:{}", previousNamespace);
|
||||
|
||||
System.setProperty("ray.job.namespace", Constants.SERVE_NAMESPACE);
|
||||
Ray.init();
|
||||
}
|
||||
|
||||
public static void shutdownRay() {
|
||||
if (!previousInited) {
|
||||
Ray.shutdown();
|
||||
LOGGER.info("Base serve test shutdown ray. Is initialized:{}", Ray.isInitialized());
|
||||
}
|
||||
if (previousNamespace == null) {
|
||||
System.clearProperty("ray.job.namespace");
|
||||
} else {
|
||||
System.setProperty("ray.job.namespace", previousNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearContext() {
|
||||
Serve.clearContext();
|
||||
}
|
||||
|
||||
public static void stopLongPoll() {
|
||||
LongPollClientFactory.stop();
|
||||
}
|
||||
|
||||
public static void clearAndShutdownRay() {
|
||||
stopLongPoll();
|
||||
shutdownRay();
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ray.serve;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
public abstract class BaseServeTest2 {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BaseServeTest2.class);
|
||||
|
||||
private String previousHttpPort;
|
||||
|
||||
@BeforeMethod(alwaysRun = true)
|
||||
public void initConfig() {
|
||||
// The default port 8000 is occupied by other processes on the ci platform.
|
||||
previousHttpPort =
|
||||
System.setProperty(
|
||||
RayServeConfig.PROXY_HTTP_PORT, "8341"); // TODO(liuyang-my) Get an available port.
|
||||
}
|
||||
|
||||
@AfterMethod(alwaysRun = true)
|
||||
public void shutdownServe() {
|
||||
try {
|
||||
Serve.shutdown();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("serve shutdown error", e);
|
||||
}
|
||||
try {
|
||||
Ray.shutdown();
|
||||
LOGGER.info("Base serve test shutdown ray. Is initialized:{}", Ray.isInitialized());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("ray shutdown error", e);
|
||||
}
|
||||
if (previousHttpPort == null) {
|
||||
System.clearProperty(RayServeConfig.PROXY_HTTP_PORT);
|
||||
} else {
|
||||
System.setProperty(RayServeConfig.PROXY_HTTP_PORT, previousHttpPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.ray.serve;
|
||||
|
||||
import io.ray.serve.controller.ServeController;
|
||||
import io.ray.serve.generated.EndpointInfo;
|
||||
import io.ray.serve.generated.EndpointSet;
|
||||
import io.ray.serve.poll.LongPollRequest;
|
||||
import io.ray.serve.util.ServeProtoUtil;
|
||||
import java.util.Map;
|
||||
|
||||
public class DummyServeController implements ServeController {
|
||||
private Map<String, EndpointInfo> endpoints;
|
||||
|
||||
private byte[] longPollResult;
|
||||
|
||||
private String rootUrl;
|
||||
|
||||
public DummyServeController(String rootUrl) {
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAllEndpoints() {
|
||||
EndpointSet.Builder builder = EndpointSet.newBuilder();
|
||||
builder.putAllEndpoints(endpoints);
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
public boolean setEndpoints(byte[] endpoints) {
|
||||
this.endpoints = ServeProtoUtil.parseEndpointSet(endpoints);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setLongPollResult(byte[] longPollResult) {
|
||||
this.longPollResult = longPollResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] listenForChange(LongPollRequest longPollRequest) {
|
||||
return longPollResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
public void setRootUrl(String rootUrl) {
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ray.serve.api;
|
||||
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.DummyServeController;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.exception.RayServeException;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import io.ray.serve.replica.ReplicaName;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.collections.Maps;
|
||||
|
||||
public class ServeTest {
|
||||
|
||||
@Test
|
||||
public void getReplicaContextNormalTest() {
|
||||
try {
|
||||
String dummyName = "getReplicaContextNormalTest";
|
||||
ReplicaName replicaName = new ReplicaName(dummyName, RandomStringUtils.randomAlphabetic(6));
|
||||
Object servableObject = new Object();
|
||||
Serve.setInternalReplicaContext(
|
||||
replicaName.getDeploymentTag(), replicaName.getReplicaTag(), servableObject, null, null);
|
||||
|
||||
ReplicaContext replicaContext = Serve.getReplicaContext();
|
||||
Assert.assertNotNull(replicaContext, "no replica context");
|
||||
Assert.assertEquals(replicaContext.getDeploymentName(), replicaName.getDeploymentTag());
|
||||
Assert.assertEquals(replicaContext.getReplicaTag(), replicaName.getReplicaTag());
|
||||
} finally {
|
||||
BaseServeTest.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReplicaContextNotExistTest() {
|
||||
Assert.assertThrows(RayServeException.class, () -> Serve.getReplicaContext());
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void startTest() {
|
||||
try {
|
||||
// The default port 8000 is occupied by other processes on the ci platform.
|
||||
Map<String, String> config = Maps.newHashMap();
|
||||
config.put(RayServeConfig.PROXY_HTTP_PORT, "8341");
|
||||
Serve.start(config);
|
||||
|
||||
Optional<PyActorHandle> controller = Ray.getActor(Constants.SERVE_CONTROLLER_NAME);
|
||||
Assert.assertTrue(controller.isPresent());
|
||||
|
||||
Serve.shutdown();
|
||||
controller = Ray.getActor(Constants.SERVE_CONTROLLER_NAME);
|
||||
Assert.assertFalse(controller.isPresent());
|
||||
} finally {
|
||||
BaseServeTest.shutdownServe();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void getGlobalClientNormalTest() {
|
||||
try {
|
||||
BaseServeTest.startServe();
|
||||
|
||||
ServeControllerClient client = Serve.getGlobalClient(true);
|
||||
Assert.assertNotNull(client);
|
||||
} finally {
|
||||
BaseServeTest.shutdownServe();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGlobalClientExceptionTest() {
|
||||
Assert.assertThrows(IllegalStateException.class, () -> Serve.getGlobalClient());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGlobalClientInReplicaTest() {
|
||||
try {
|
||||
BaseServeTest.initRay();
|
||||
|
||||
// Mock controller.
|
||||
Ray.actor(DummyServeController::new, "").setName(Constants.SERVE_CONTROLLER_NAME).remote();
|
||||
|
||||
// Get client.
|
||||
ServeControllerClient client = Serve.getGlobalClient();
|
||||
Assert.assertNotNull(client);
|
||||
} finally {
|
||||
BaseServeTest.shutdownRay();
|
||||
BaseServeTest.clearContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ray.serve.context;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class RequestContextTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
RequestContext context = RequestContext.get();
|
||||
Assert.assertEquals(context.getRoute(), "");
|
||||
Assert.assertEquals(context.getRequestId(), "");
|
||||
Assert.assertEquals(context.getAppName(), "");
|
||||
Assert.assertEquals(context.getMultiplexedModelId(), "");
|
||||
|
||||
String route = "route";
|
||||
String requestId = "requestId";
|
||||
String appName = "appName";
|
||||
String multiplexedModelId = "multiplexedModelId";
|
||||
|
||||
RequestContext.set(route, requestId, appName, multiplexedModelId);
|
||||
context = RequestContext.get();
|
||||
Assert.assertEquals(context.getRoute(), route);
|
||||
Assert.assertEquals(context.getRequestId(), requestId);
|
||||
Assert.assertEquals(context.getAppName(), appName);
|
||||
Assert.assertEquals(context.getMultiplexedModelId(), multiplexedModelId);
|
||||
|
||||
RequestContext.clean();
|
||||
context = RequestContext.get();
|
||||
Assert.assertEquals(context.getRoute(), "");
|
||||
Assert.assertEquals(context.getRequestId(), "");
|
||||
Assert.assertEquals(context.getAppName(), "");
|
||||
Assert.assertEquals(context.getMultiplexedModelId(), "");
|
||||
RequestContext.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public class CrossLanguageDeploymentTest extends BaseServeTest {
|
||||
private static final String PYTHON_MODULE = "test_python_deployment";
|
||||
|
||||
@BeforeClass
|
||||
public void beforeClass() {
|
||||
// Delete and re-create the temp dir.
|
||||
File tempDir =
|
||||
new File(
|
||||
System.getProperty("java.io.tmpdir")
|
||||
+ File.separator
|
||||
+ "ray_serve_cross_language_test");
|
||||
FileUtils.deleteQuietly(tempDir);
|
||||
tempDir.mkdirs();
|
||||
tempDir.deleteOnExit();
|
||||
|
||||
// Write the test Python file to the temp dir.
|
||||
InputStream in =
|
||||
CrossLanguageDeploymentTest.class.getResourceAsStream(
|
||||
File.separator + PYTHON_MODULE + ".py");
|
||||
File pythonFile = new File(tempDir.getAbsolutePath() + File.separator + PYTHON_MODULE + ".py");
|
||||
try {
|
||||
FileUtils.copyInputStreamToFile(in, pythonFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
System.setProperty(
|
||||
"ray.job.code-search-path",
|
||||
System.getProperty("java.class.path") + File.pathSeparator + tempDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPyClassTest() {
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("createPyClassTest")
|
||||
.setDeploymentDef(PYTHON_MODULE + ".Counter")
|
||||
.setNumReplicas(1)
|
||||
.bind("28");
|
||||
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.method("increase").remote("6").result(), "34");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPyClassWithObjectRefTest() {
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("createPyClassWithObjectRefTest")
|
||||
.setDeploymentDef(PYTHON_MODULE + ".Counter")
|
||||
.setNumReplicas(1)
|
||||
.bind("28");
|
||||
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
ObjectRef<Integer> numRef = Ray.put(10);
|
||||
Assert.assertEquals(handle.method("increase").remote(numRef).result(), "38");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPyMethodTest() {
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("createPyMethodTest")
|
||||
.setDeploymentDef(PYTHON_MODULE + ".echo_server")
|
||||
.setNumReplicas(1)
|
||||
.bind();
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.method("__call__").remote("6").result(), "6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPyMethodWithObjectRefTest() {
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("createPyMethodWithObjectRefTest")
|
||||
.setDeploymentDef(PYTHON_MODULE + ".echo_server")
|
||||
.setNumReplicas(1)
|
||||
.bind();
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
ObjectRef<String> numRef = Ray.put("10");
|
||||
Assert.assertEquals(handle.method("__call__").remote(numRef).result(), "10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userConfigTest() throws InterruptedException {
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("userConfigTest")
|
||||
.setDeploymentDef(PYTHON_MODULE + ".Counter")
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("1")
|
||||
.bind("28");
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.method("increase").remote("6").result(), "7");
|
||||
// deployment.options().setUserConfig("3").create().deploy(true);
|
||||
// TimeUnit.SECONDS.sleep(20L);
|
||||
// Assert.assertEquals(handle.method("increase").remote("6").result(), "9");
|
||||
// TOOD update user config
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest2;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.generated.ApplicationStatus;
|
||||
import io.ray.serve.generated.StatusOverview;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import io.ray.serve.handle.DeploymentResponse;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class DeploymentGraphTest extends BaseServeTest2 {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DeploymentGraphTest.class);
|
||||
|
||||
private String previousHttpPort;
|
||||
|
||||
@BeforeMethod(alwaysRun = true)
|
||||
public void before() {
|
||||
// The default port 8000 is occupied by other processes on the ci platform.
|
||||
previousHttpPort = System.getProperty(RayServeConfig.PROXY_HTTP_PORT);
|
||||
System.setProperty(
|
||||
RayServeConfig.PROXY_HTTP_PORT, "8341"); // TODO(liuyang-my) Get an available port.
|
||||
}
|
||||
|
||||
@AfterMethod(alwaysRun = true)
|
||||
public void after() {
|
||||
try {
|
||||
Serve.shutdown();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("serve shutdown error", e);
|
||||
}
|
||||
try {
|
||||
Ray.shutdown();
|
||||
LOGGER.info("Base serve test shutdown ray. Is initialized:{}", Ray.isInitialized());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("ray shutdown error", e);
|
||||
}
|
||||
if (previousHttpPort == null) {
|
||||
System.clearProperty(RayServeConfig.PROXY_HTTP_PORT);
|
||||
} else {
|
||||
System.setProperty(RayServeConfig.PROXY_HTTP_PORT, previousHttpPort);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Counter {
|
||||
private AtomicInteger count;
|
||||
|
||||
public Counter(String count) {
|
||||
this.count = new AtomicInteger(Integer.parseInt(count));
|
||||
}
|
||||
|
||||
public String call(String input) {
|
||||
return String.valueOf(count.addAndGet(Integer.parseInt(input)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void bindTest() {
|
||||
Application deployment =
|
||||
Serve.deployment().setDeploymentDef(Counter.class.getName()).setNumReplicas(1).bind("2");
|
||||
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.remote("2").result(), "4");
|
||||
}
|
||||
|
||||
public static class ModelA {
|
||||
public String call(String msg) {
|
||||
return "A:" + msg;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModelB {
|
||||
public String call(String msg) {
|
||||
return "B:" + msg;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Combiner {
|
||||
private DeploymentHandle modelAHandle;
|
||||
private DeploymentHandle modelBHandle;
|
||||
|
||||
public Combiner(DeploymentHandle modelAHandle, DeploymentHandle modelBHandle) {
|
||||
LOGGER.info("Combiner.");
|
||||
this.modelAHandle = modelAHandle;
|
||||
this.modelBHandle = modelBHandle;
|
||||
}
|
||||
|
||||
public String call(String request) {
|
||||
DeploymentResponse responseA = modelAHandle.remote(request);
|
||||
DeploymentResponse responseB = modelBHandle.remote(request);
|
||||
return responseA.result() + "," + responseB.result();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void testPassHandle() {
|
||||
Application modelA = Serve.deployment().setDeploymentDef(ModelA.class.getName()).bind();
|
||||
Application modelB = Serve.deployment().setDeploymentDef(ModelB.class.getName()).bind();
|
||||
|
||||
Application driver =
|
||||
Serve.deployment().setDeploymentDef(Combiner.class.getName()).bind(modelA, modelB);
|
||||
DeploymentHandle handle = Serve.run(driver);
|
||||
Assert.assertEquals(handle.remote("test").result(), "A:test,B:test");
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void statusTest() {
|
||||
Application deployment =
|
||||
Serve.deployment().setDeploymentDef(Counter.class.getName()).setNumReplicas(1).bind("2");
|
||||
Serve.run(deployment);
|
||||
|
||||
StatusOverview status =
|
||||
Serve.getGlobalClient().getServeStatus(Constants.SERVE_DEFAULT_APP_NAME);
|
||||
Assert.assertEquals(
|
||||
status.getAppStatus().getStatus(), ApplicationStatus.APPLICATION_STATUS_RUNNING);
|
||||
|
||||
Serve.delete(Constants.SERVE_DEFAULT_APP_NAME);
|
||||
status = Serve.getGlobalClient().getServeStatus(Constants.SERVE_DEFAULT_APP_NAME);
|
||||
Assert.assertEquals(
|
||||
status.getAppStatus().getStatus(), ApplicationStatus.APPLICATION_STATUS_NOT_STARTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest2;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.config.AutoscalingConfig;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public class DeploymentTest extends BaseServeTest2 {
|
||||
|
||||
@Test
|
||||
public void deployTest() {
|
||||
// Deploy deployment.
|
||||
String deploymentName = "exampleEcho";
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.method("call").remote("6").result(), "echo_6_test");
|
||||
Assert.assertTrue((boolean) handle.method("checkHealth").remote().result());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpExposeDeploymentTest() throws IOException {
|
||||
// Deploy deployment.
|
||||
String deploymentName = "exampleEcho";
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
Serve.run(deployment);
|
||||
|
||||
HttpClient httpClient = HttpClientBuilder.create().build();
|
||||
HttpGet httpGet = new HttpGet("http://127.0.0.1:8341/" + deploymentName + "?input=testhttpget");
|
||||
try (CloseableHttpResponse httpResponse = (CloseableHttpResponse) httpClient.execute(httpGet)) {
|
||||
byte[] body = EntityUtils.toByteArray(httpResponse.getEntity());
|
||||
String response = new String(body, StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(response, "echo_testhttpget_test");
|
||||
}
|
||||
HttpPost httpPost = new HttpPost("http://127.0.0.1:8341/" + deploymentName);
|
||||
httpPost.setEntity(new StringEntity("testhttppost"));
|
||||
try (CloseableHttpResponse httpResponse =
|
||||
(CloseableHttpResponse) httpClient.execute(httpPost)) {
|
||||
byte[] body = EntityUtils.toByteArray(httpResponse.getEntity());
|
||||
String response = new String(body, StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(response, "echo_testhttppost_test");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void updateDeploymentTest() {
|
||||
String deploymentName = "exampleEcho";
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
Serve.run(deployment);
|
||||
|
||||
Deployment deployed = Serve.getDeployment(deploymentName);
|
||||
Serve.run(deployed.options().setNumReplicas(2).bind("echo_"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoScaleTest() {
|
||||
String deploymentName = "exampleEcho";
|
||||
AutoscalingConfig autoscalingConfig = new AutoscalingConfig();
|
||||
autoscalingConfig.setMinReplicas(1);
|
||||
autoscalingConfig.setMaxReplicas(3);
|
||||
autoscalingConfig.setTargetOngoingRequests(10);
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setAutoscalingConfig(autoscalingConfig)
|
||||
.setUserConfig("_test")
|
||||
.setVersion("v1")
|
||||
.bind("echo_");
|
||||
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
Assert.assertEquals(handle.method("call").remote("6").result(), "echo_6_test");
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void userConfigTest() {
|
||||
String deploymentName = "exampleEcho";
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
Serve.run(deployment);
|
||||
|
||||
Serve.run(Serve.getDeployment(deploymentName).options().setUserConfig("_new").bind());
|
||||
Assert.assertEquals(
|
||||
Serve.getAppHandle(deploymentName).method("call").remote("6").result(), "echo_6_new");
|
||||
// TOOD update user config
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalScalerEnabledTest() {
|
||||
/*
|
||||
* This test verifies that the external_scaler_enabled flag is properly passed through
|
||||
* the Java Serve API to the Python controller.
|
||||
*
|
||||
* WHY WE DON'T TEST VIA HTTP DASHBOARD API:
|
||||
* The external scaler HTTP REST API endpoint (/api/v1/applications/{app}/deployments/{dep}/scale)
|
||||
* is hosted by the Ray dashboard on port 8265. However, in the Java test framework:
|
||||
*
|
||||
* 1. Each test creates an ephemeral Ray session with a fresh actor registry
|
||||
* 2. The Serve controller is created programmatically via Java API (Serve.run)
|
||||
* 3. The dashboard runs in a separate process and discovers controllers by querying
|
||||
* Ray's actor registry for "SERVE_CONTROLLER_ACTOR" in the "serve" namespace
|
||||
* 4. Due to timing and process isolation issues in test environments, the dashboard's
|
||||
* get_serve_controller() method often fails to find the controller, returning 503
|
||||
* with error "Serve controller is not available"
|
||||
*
|
||||
* For testing purposes, we verify the flag is correctly passed to the controller by:
|
||||
* 1. Deploying an application with external_scaler_enabled=true
|
||||
* 2. Verifying the deployment succeeds and is functional
|
||||
* 3. Checking that the controller actor exists with the correct configuration
|
||||
*
|
||||
* The actual HTTP scaling functionality is tested in Python integration tests where
|
||||
* the dashboard and controller have proper lifecycle management.
|
||||
*/
|
||||
String appName = "externalScalerApp";
|
||||
String deploymentName = "exampleEcho";
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
|
||||
// Deploy with external_scaler_enabled=true - this passes the flag through
|
||||
DeploymentHandle handle = Serve.run(deployment, true, appName, "/", null, true);
|
||||
|
||||
// Verify the deployment is functional
|
||||
Assert.assertEquals(handle.method("call").remote("5").result(), "echo_5_test");
|
||||
Assert.assertTrue((boolean) handle.method("checkHealth").remote().result());
|
||||
|
||||
// Verify the controller actor exists in the correct namespace
|
||||
java.util.Optional<io.ray.api.BaseActorHandle> controllerOpt =
|
||||
Ray.getActor("SERVE_CONTROLLER_ACTOR", "serve");
|
||||
Assert.assertTrue(
|
||||
controllerOpt.isPresent(), "Serve controller actor should exist in 'serve' namespace");
|
||||
|
||||
// Verify that the external_scaler_enabled flag is actually set to TRUE in the controller
|
||||
// by calling the controller's get_external_scaler_enabled method
|
||||
io.ray.api.PyActorHandle controller = (io.ray.api.PyActorHandle) controllerOpt.get();
|
||||
try {
|
||||
// Call the Python controller's get_external_scaler_enabled method
|
||||
// This is a helper method added specifically for Java tests that returns a simple boolean
|
||||
Object result =
|
||||
controller
|
||||
.task(io.ray.api.function.PyActorMethod.of("get_external_scaler_enabled"), appName)
|
||||
.remote()
|
||||
.get();
|
||||
|
||||
// Verify the flag is set to True
|
||||
Assert.assertTrue(
|
||||
Boolean.TRUE.equals(result),
|
||||
"external_scaler_enabled should be True for app '" + appName + "', but was: " + result);
|
||||
|
||||
// Also verify application is running
|
||||
io.ray.serve.api.ServeControllerClient client = io.ray.serve.api.Serve.getGlobalClient();
|
||||
io.ray.serve.generated.StatusOverview status = client.getServeStatus(appName);
|
||||
Assert.assertEquals(
|
||||
status.getAppStatus().getStatus(),
|
||||
io.ray.serve.generated.ApplicationStatus.APPLICATION_STATUS_RUNNING,
|
||||
"Application should be in RUNNING status");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to verify external_scaler_enabled flag", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalScalerDisabledTest() {
|
||||
/*
|
||||
* This test verifies that the external_scaler_enabled flag defaults to false and
|
||||
* applications can be deployed with external scaling explicitly disabled.
|
||||
*
|
||||
* This is the complement to externalScalerEnabledTest - verifying that the flag
|
||||
* can be set to false (which is also the default behavior).
|
||||
*
|
||||
* In production, when external_scaler_enabled=false, attempts to scale via the
|
||||
* HTTP dashboard API would return 412 (Precondition Failed) with an
|
||||
* ExternalScalerDisabledError. However, as explained in externalScalerEnabledTest,
|
||||
* we cannot reliably test the HTTP API in this test environment due to dashboard
|
||||
* and controller lifecycle management issues.
|
||||
*
|
||||
* For testing purposes, we verify:
|
||||
* 1. Applications can be deployed with external_scaler_enabled=false
|
||||
* 2. The deployment succeeds and is functional
|
||||
* 3. Checking that the controller actor exists with the correct configuration
|
||||
*/
|
||||
String appName = "normalApp";
|
||||
String deploymentName = "exampleEcho";
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentDef(ExampleEchoDeployment.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.setUserConfig("_test")
|
||||
.bind("echo_");
|
||||
|
||||
// Deploy with external_scaler_enabled=false (explicit)
|
||||
DeploymentHandle handle = Serve.run(deployment, true, appName, "/", null, false);
|
||||
|
||||
// Verify the deployment is functional
|
||||
Assert.assertEquals(handle.method("call").remote("7").result(), "echo_7_test");
|
||||
Assert.assertTrue((boolean) handle.method("checkHealth").remote().result());
|
||||
|
||||
// Verify the controller actor exists in the correct namespace
|
||||
java.util.Optional<io.ray.api.BaseActorHandle> controllerOpt =
|
||||
Ray.getActor("SERVE_CONTROLLER_ACTOR", "serve");
|
||||
Assert.assertTrue(
|
||||
controllerOpt.isPresent(), "Serve controller actor should exist in 'serve' namespace");
|
||||
|
||||
// Verify that the external_scaler_enabled flag is actually set to FALSE in the controller
|
||||
// by calling the controller's get_external_scaler_enabled method
|
||||
io.ray.api.PyActorHandle controller = (io.ray.api.PyActorHandle) controllerOpt.get();
|
||||
try {
|
||||
// Call the Python controller's get_external_scaler_enabled method
|
||||
// This is a helper method added specifically for Java tests that returns a simple boolean
|
||||
Object result =
|
||||
controller
|
||||
.task(io.ray.api.function.PyActorMethod.of("get_external_scaler_enabled"), appName)
|
||||
.remote()
|
||||
.get();
|
||||
|
||||
// Verify the flag is set to False
|
||||
Assert.assertFalse(
|
||||
Boolean.TRUE.equals(result),
|
||||
"external_scaler_enabled should be False for app '" + appName + "', but was: " + result);
|
||||
|
||||
// Also verify application is running
|
||||
io.ray.serve.api.ServeControllerClient client = io.ray.serve.api.Serve.getGlobalClient();
|
||||
io.ray.serve.generated.StatusOverview status = client.getServeStatus(appName);
|
||||
Assert.assertEquals(
|
||||
status.getAppStatus().getStatus(),
|
||||
io.ray.serve.generated.ApplicationStatus.APPLICATION_STATUS_RUNNING,
|
||||
"Application should be in RUNNING status");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to verify external_scaler_enabled flag", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.ray.serve.deployment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ExampleEchoDeployment {
|
||||
String prefix;
|
||||
String suffix = "";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ExampleEchoDeployment.class);
|
||||
|
||||
public ExampleEchoDeployment(Object prefix) {
|
||||
LOGGER.info("receive init args: {}", prefix);
|
||||
this.prefix = (String) prefix;
|
||||
}
|
||||
|
||||
public String call(Object input) {
|
||||
LOGGER.info("receive call request: {}", input);
|
||||
return this.prefix + input + this.suffix;
|
||||
}
|
||||
|
||||
public boolean checkHealth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object reconfigure(Object userConfig) {
|
||||
LOGGER.info("receive userconfig: {}", userConfig);
|
||||
if (null != userConfig) {
|
||||
this.suffix = userConfig.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import org.apache.hc.client5.http.fluent.Request;
|
||||
|
||||
public class HttpStrategyCalcOnRayServe {
|
||||
|
||||
public void deploy() {
|
||||
Serve.start(null);
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName("http-strategy")
|
||||
.setDeploymentDef(HttpStrategyOnRayServe.class.getName())
|
||||
.setNumReplicas(4)
|
||||
.bind();
|
||||
Serve.run(deployment);
|
||||
}
|
||||
|
||||
// docs-http-start
|
||||
private Gson gson = new Gson();
|
||||
|
||||
public String httpCalc(Long time, String bank, String indicator) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("time", time);
|
||||
data.put("bank", bank);
|
||||
data.put("indicator", indicator);
|
||||
|
||||
String result;
|
||||
try {
|
||||
result =
|
||||
Request.post("http://127.0.0.1:8000/http-strategy")
|
||||
.bodyString(gson.toJson(data), null)
|
||||
.execute()
|
||||
.returnContent()
|
||||
.asString();
|
||||
} catch (IOException e) {
|
||||
result = "error";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
// docs-http-end
|
||||
|
||||
// docs-calc-start
|
||||
public List<String> calc(Long time, Map<String, List<List<String>>> banksAndIndicators) {
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
for (Entry<String, List<List<String>>> e : banksAndIndicators.entrySet()) {
|
||||
String bank = e.getKey();
|
||||
for (List<String> indicators : e.getValue()) {
|
||||
for (String indicator : indicators) {
|
||||
results.add(httpCalc(time, bank, indicator));
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// docs-calc-end
|
||||
|
||||
// docs-parallel-calc-start
|
||||
private ExecutorService executorService = Executors.newFixedThreadPool(4);
|
||||
|
||||
public List<String> parallelCalc(Long time, Map<String, List<List<String>>> banksAndIndicators) {
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
List<Future<String>> futures = new ArrayList<>();
|
||||
for (Entry<String, List<List<String>>> e : banksAndIndicators.entrySet()) {
|
||||
String bank = e.getKey();
|
||||
for (List<String> indicators : e.getValue()) {
|
||||
for (String indicator : indicators) {
|
||||
futures.add(executorService.submit(() -> httpCalc(time, bank, indicator)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Future<String> future : futures) {
|
||||
try {
|
||||
results.add(future.get());
|
||||
} catch (InterruptedException | ExecutionException e1) {
|
||||
results.add("error");
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// docs-parallel-calc-end
|
||||
|
||||
// docs-main-start
|
||||
public static void main(String[] args) {
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
String bank1 = "demo_bank_1";
|
||||
String bank2 = "demo_bank_2";
|
||||
String indicator1 = "demo_indicator_1";
|
||||
String indicator2 = "demo_indicator_2";
|
||||
Map<String, List<List<String>>> banksAndIndicators = new HashMap<>();
|
||||
banksAndIndicators.put(bank1, Arrays.asList(Arrays.asList(indicator1, indicator2)));
|
||||
banksAndIndicators.put(
|
||||
bank2, Arrays.asList(Arrays.asList(indicator1), Arrays.asList(indicator2)));
|
||||
|
||||
HttpStrategyCalcOnRayServe strategy = new HttpStrategyCalcOnRayServe();
|
||||
strategy.deploy();
|
||||
List<String> results = strategy.parallelCalc(time, banksAndIndicators);
|
||||
|
||||
System.out.println(results);
|
||||
}
|
||||
// docs-main-end
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
// docs-strategy-start
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class HttpStrategyOnRayServe {
|
||||
|
||||
static class BankIndicator {
|
||||
long time;
|
||||
String bank;
|
||||
String indicator;
|
||||
}
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
public String call(String dataJson) {
|
||||
BankIndicator data = gson.fromJson(dataJson, BankIndicator.class);
|
||||
// do bank data calculation
|
||||
return data.bank + "-" + data.indicator + "-" + data.time; // Demo;
|
||||
}
|
||||
}
|
||||
// docs-strategy-end
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ManageDeployment {
|
||||
|
||||
// docs-create-start
|
||||
public static class Counter {
|
||||
|
||||
private AtomicInteger value;
|
||||
|
||||
public Counter(String value) {
|
||||
this.value = new AtomicInteger(Integer.valueOf(value));
|
||||
}
|
||||
|
||||
public String call(String delta) {
|
||||
return String.valueOf(value.addAndGet(Integer.valueOf(delta)));
|
||||
}
|
||||
}
|
||||
|
||||
public void create() {
|
||||
Application app =
|
||||
Serve.deployment()
|
||||
.setName("counter")
|
||||
.setDeploymentDef(Counter.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.bind("1");
|
||||
Serve.run(app);
|
||||
}
|
||||
// docs-create-end
|
||||
|
||||
// docs-query-start
|
||||
public Deployment query() {
|
||||
Deployment deployment = Serve.getDeployment("counter");
|
||||
return deployment;
|
||||
}
|
||||
// docs-query-end
|
||||
|
||||
// docs-update-start
|
||||
public void update() {
|
||||
Application app =
|
||||
Serve.deployment()
|
||||
.setName("counter")
|
||||
.setDeploymentDef(Counter.class.getName())
|
||||
.setNumReplicas(1)
|
||||
.bind("2");
|
||||
Serve.run(app);
|
||||
}
|
||||
// docs-update-end
|
||||
|
||||
// docs-scale-start
|
||||
public void scaleOut() {
|
||||
Deployment deployment = Serve.getDeployment("counter");
|
||||
|
||||
// Scale up to 2 replicas.
|
||||
Serve.run(deployment.options().setNumReplicas(2).bind());
|
||||
|
||||
// Scale down to 1 replica.
|
||||
Serve.run(deployment.options().setNumReplicas(1).bind());
|
||||
}
|
||||
// docs-scale-end
|
||||
|
||||
// docs-resource-start
|
||||
public void manageResource() {
|
||||
Map<String, Object> rayActorOptions = new HashMap<>();
|
||||
rayActorOptions.put("num_gpus", 1);
|
||||
Application app =
|
||||
Serve.deployment()
|
||||
.setName("counter")
|
||||
.setDeploymentDef(Counter.class.getName())
|
||||
.setRayActorOptions(rayActorOptions)
|
||||
.bind();
|
||||
Serve.run(app);
|
||||
}
|
||||
// docs-resource-end
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import java.io.File;
|
||||
|
||||
public class ManagePythonDeployment {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
System.setProperty(
|
||||
"ray.job.code-search-path",
|
||||
System.getProperty("java.class.path") + File.pathSeparator + "/path/to/code/");
|
||||
|
||||
Serve.start(null);
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setLanguage(DeploymentLanguage.PYTHON)
|
||||
.setName("counter")
|
||||
.setDeploymentDef("counter.Counter")
|
||||
.setNumReplicas(1)
|
||||
.bind("1");
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
|
||||
System.out.println(handle.method("increase").remote("2").result());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
// docs-strategy-start
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class Strategy {
|
||||
|
||||
public List<String> calc(Long time, Map<String, List<List<String>>> banksAndIndicators) {
|
||||
List<String> results = new ArrayList<>();
|
||||
for (Entry<String, List<List<String>>> e : banksAndIndicators.entrySet()) {
|
||||
String bank = e.getKey();
|
||||
for (List<String> indicators : e.getValue()) {
|
||||
results.addAll(calcBankIndicators(time, bank, indicators));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<String> calcBankIndicators(Long time, String bank, List<String> indicators) {
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String indicator : indicators) {
|
||||
results.add(calcIndicator(time, bank, indicator));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public String calcIndicator(Long time, String bank, String indicator) {
|
||||
// do bank data calculation
|
||||
return bank + "-" + indicator + "-" + time; // Demo;
|
||||
}
|
||||
}
|
||||
// docs-strategy-end
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
// docs-strategy-calc-start
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class StrategyCalc {
|
||||
|
||||
public static void main(String[] args) {
|
||||
long time = System.currentTimeMillis();
|
||||
String bank1 = "demo_bank_1";
|
||||
String bank2 = "demo_bank_2";
|
||||
String indicator1 = "demo_indicator_1";
|
||||
String indicator2 = "demo_indicator_2";
|
||||
Map<String, List<List<String>>> banksAndIndicators = new HashMap<>();
|
||||
banksAndIndicators.put(bank1, Arrays.asList(Arrays.asList(indicator1, indicator2)));
|
||||
banksAndIndicators.put(
|
||||
bank2, Arrays.asList(Arrays.asList(indicator1), Arrays.asList(indicator2)));
|
||||
|
||||
Strategy strategy = new Strategy();
|
||||
List<String> results = strategy.calc(time, banksAndIndicators);
|
||||
|
||||
System.out.println(results);
|
||||
}
|
||||
}
|
||||
// docs-strategy-calc-end
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.handle.DeploymentResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class StrategyCalcOnRayServe {
|
||||
|
||||
// docs-deploy-start
|
||||
public void deploy() {
|
||||
Serve.start(null);
|
||||
|
||||
Application deployment =
|
||||
Serve.deployment()
|
||||
.setName("strategy")
|
||||
.setDeploymentDef(StrategyOnRayServe.class.getName())
|
||||
.setNumReplicas(4)
|
||||
.bind();
|
||||
Serve.run(deployment);
|
||||
}
|
||||
// docs-deploy-end
|
||||
|
||||
// docs-calc-start
|
||||
public List<String> calc(Long time, Map<String, List<List<String>>> banksAndIndicators) {
|
||||
Deployment deployment = Serve.getDeployment("strategy");
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
for (Entry<String, List<List<String>>> e : banksAndIndicators.entrySet()) {
|
||||
String bank = e.getKey();
|
||||
for (List<String> indicators : e.getValue()) {
|
||||
for (String indicator : indicators) {
|
||||
results.add(
|
||||
(String)
|
||||
deployment
|
||||
.getHandle()
|
||||
.method("calcIndicator")
|
||||
.remote(time, bank, indicator)
|
||||
.result());
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// docs-calc-end
|
||||
|
||||
// docs-parallel-calc-start
|
||||
public List<String> parallelCalc(Long time, Map<String, List<List<String>>> banksAndIndicators) {
|
||||
Deployment deployment = Serve.getDeployment("strategy");
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
List<DeploymentResponse> responses = new ArrayList<>();
|
||||
for (Entry<String, List<List<String>>> e : banksAndIndicators.entrySet()) {
|
||||
String bank = e.getKey();
|
||||
for (List<String> indicators : e.getValue()) {
|
||||
for (String indicator : indicators) {
|
||||
responses.add(
|
||||
deployment.getHandle().method("calcIndicator").remote(time, bank, indicator));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (DeploymentResponse response : responses) {
|
||||
results.add((String) response.result());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// docs-parallel-calc-end
|
||||
|
||||
// docs-main-start
|
||||
public static void main(String[] args) {
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
String bank1 = "demo_bank_1";
|
||||
String bank2 = "demo_bank_2";
|
||||
String indicator1 = "demo_indicator_1";
|
||||
String indicator2 = "demo_indicator_2";
|
||||
Map<String, List<List<String>>> banksAndIndicators = new HashMap<>();
|
||||
banksAndIndicators.put(bank1, Arrays.asList(Arrays.asList(indicator1, indicator2)));
|
||||
banksAndIndicators.put(
|
||||
bank2, Arrays.asList(Arrays.asList(indicator1), Arrays.asList(indicator2)));
|
||||
|
||||
StrategyCalcOnRayServe strategy = new StrategyCalcOnRayServe();
|
||||
strategy.deploy();
|
||||
List<String> results = strategy.parallelCalc(time, banksAndIndicators);
|
||||
|
||||
System.out.println(results);
|
||||
}
|
||||
// docs-main-end
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ray.serve.docdemo;
|
||||
|
||||
// docs-strategy-start
|
||||
public class StrategyOnRayServe {
|
||||
|
||||
public String calcIndicator(Long time, String bank, String indicator) {
|
||||
// do bank data calculation
|
||||
return bank + "-" + indicator + "-" + time; // Demo;
|
||||
}
|
||||
}
|
||||
// docs-strategy-end
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.ray.serve.docdemo.test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.docdemo.HttpStrategyCalcOnRayServe;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.hc.client5.http.fluent.Request;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class HttpStrategyCalcOnRayServeTest extends BaseServeTest {
|
||||
|
||||
public static class HttpStrategyCalcOnRayServeForTest extends HttpStrategyCalcOnRayServe {
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public String httpCalc(Long time, String bank, String indicator) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("time", time);
|
||||
data.put("bank", bank);
|
||||
data.put("indicator", indicator);
|
||||
String result;
|
||||
try {
|
||||
result =
|
||||
Request.post("http://127.0.0.1:8341/http-strategy")
|
||||
.bodyString(gson.toJson(data), null)
|
||||
.execute()
|
||||
.returnContent()
|
||||
.asString();
|
||||
} catch (IOException e) {
|
||||
result = "error";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = {"cluster"})
|
||||
public void test() {
|
||||
String prefix = "HttpStrategyCalcOnRayServeTest";
|
||||
String bank1 = prefix + "_bank_1";
|
||||
String bank2 = prefix + "_bank_2";
|
||||
String indicator1 = prefix + "_indicator_1";
|
||||
String indicator2 = prefix + "_indicator_2";
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Map<String, List<List<String>>> banksAndIndicators = new HashMap<>();
|
||||
banksAndIndicators.put(bank1, Arrays.asList(Arrays.asList(indicator1, indicator2)));
|
||||
banksAndIndicators.put(
|
||||
bank2, Arrays.asList(Arrays.asList(indicator1), Arrays.asList(indicator2)));
|
||||
|
||||
HttpStrategyCalcOnRayServe strategy = new HttpStrategyCalcOnRayServeForTest();
|
||||
strategy.deploy();
|
||||
|
||||
List<String> results = strategy.calc(time, banksAndIndicators);
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator2 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
|
||||
results = strategy.parallelCalc(time, banksAndIndicators);
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator2 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.serve.docdemo.test;
|
||||
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.deployment.Deployment;
|
||||
import io.ray.serve.docdemo.ManageDeployment;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ManageDeploymentTest extends BaseServeTest {
|
||||
|
||||
@Test(
|
||||
enabled = false,
|
||||
groups = {"cluster"})
|
||||
public void test() {
|
||||
ManageDeployment manageDeployment = new ManageDeployment();
|
||||
manageDeployment.create();
|
||||
|
||||
Deployment deployment = manageDeployment.query();
|
||||
Assert.assertEquals(deployment.getName(), "counter");
|
||||
Assert.assertEquals(deployment.getDeploymentConfig().getNumReplicas().intValue(), 1);
|
||||
Assert.assertEquals(deployment.getReplicaConfig().getInitArgs()[0], "1");
|
||||
|
||||
manageDeployment.update();
|
||||
deployment = manageDeployment.query();
|
||||
Assert.assertEquals(deployment.getReplicaConfig().getInitArgs()[0], "2");
|
||||
|
||||
manageDeployment.scaleOut();
|
||||
deployment = manageDeployment.query();
|
||||
Assert.assertEquals(deployment.getDeploymentConfig().getNumReplicas().intValue(), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.ray.serve.docdemo.test;
|
||||
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.docdemo.StrategyCalcOnRayServe;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class StrategyCalcOnRayServeTest extends BaseServeTest {
|
||||
|
||||
@Test(
|
||||
enabled = false,
|
||||
groups = {"cluster"})
|
||||
public void test() {
|
||||
String prefix = "StrategyCalcOnRayServeTest";
|
||||
String bank1 = prefix + "_bank_1";
|
||||
String bank2 = prefix + "_bank_2";
|
||||
String indicator1 = prefix + "_indicator_1";
|
||||
String indicator2 = prefix + "_indicator_2";
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Map<String, List<List<String>>> banksAndIndicators = new HashMap<>();
|
||||
banksAndIndicators.put(bank1, Arrays.asList(Arrays.asList(indicator1, indicator2)));
|
||||
banksAndIndicators.put(
|
||||
bank2, Arrays.asList(Arrays.asList(indicator1), Arrays.asList(indicator2)));
|
||||
|
||||
StrategyCalcOnRayServe strategy = new StrategyCalcOnRayServe();
|
||||
strategy.deploy();
|
||||
|
||||
List<String> results = strategy.calc(time, banksAndIndicators);
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator2 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
|
||||
results = strategy.parallelCalc(time, banksAndIndicators);
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank1 + "-" + indicator2 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
Assert.assertTrue(results.contains(bank2 + "-" + indicator1 + "-" + time));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ray.serve.docdemo.test;
|
||||
|
||||
import io.ray.serve.docdemo.StrategyCalc;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class StrategyCalcTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
StrategyCalc.main(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class KeyListenerTest {
|
||||
|
||||
@Test
|
||||
public void test() throws Throwable {
|
||||
int[] a = new int[] {0};
|
||||
KeyListener keyListener = (x) -> ((int[]) x)[0] = 1;
|
||||
keyListener.notifyChanged(a);
|
||||
Assert.assertEquals(a[0], 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class KeyTypeTest {
|
||||
|
||||
@Test
|
||||
public void equalsTest() {
|
||||
KeyType k1 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k1");
|
||||
KeyType k2 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k1");
|
||||
KeyType k3 = new KeyType(LongPollNamespace.ROUTE_TABLE, null);
|
||||
KeyType k4 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k4");
|
||||
|
||||
Assert.assertTrue(k1.equals(k1));
|
||||
Assert.assertTrue(k1.equals(k2));
|
||||
Assert.assertTrue(k2.equals(k1));
|
||||
Assert.assertFalse(k1.equals(k3));
|
||||
Assert.assertFalse(k3.equals(k1));
|
||||
Assert.assertFalse(k1.equals(k4));
|
||||
Assert.assertFalse(k4.equals(k1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeTest() {
|
||||
KeyType k1 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k1");
|
||||
KeyType k2 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k1");
|
||||
KeyType k3 = new KeyType(LongPollNamespace.ROUTE_TABLE, null);
|
||||
KeyType k4 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k4");
|
||||
|
||||
Assert.assertEquals(k1.hashCode(), k2.hashCode());
|
||||
Assert.assertNotEquals(k1.hashCode(), k3.hashCode());
|
||||
Assert.assertNotEquals(k1.hashCode(), k4.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
KeyType k1 = new KeyType(LongPollNamespace.ROUTE_TABLE, "k1");
|
||||
Assert.assertEquals(k1.toString(), "(ROUTE_TABLE,k1)");
|
||||
|
||||
KeyType k2 = new KeyType(LongPollNamespace.ROUTE_TABLE, null);
|
||||
Assert.assertEquals(k2.toString(), "ROUTE_TABLE");
|
||||
|
||||
KeyType k3 = new KeyType(null, "k3");
|
||||
Assert.assertEquals(k3.toString(), "k3");
|
||||
|
||||
KeyType k4 = new KeyType(null, null);
|
||||
Assert.assertEquals(k4.toString(), "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseFromTest() {
|
||||
KeyType k1 = KeyType.parseFrom("(ROUTE_TABLE,k1)");
|
||||
Assert.assertEquals(k1.getLongPollNamespace(), LongPollNamespace.ROUTE_TABLE);
|
||||
Assert.assertEquals(k1.getKey(), "k1");
|
||||
|
||||
KeyType k2 = KeyType.parseFrom("ROUTE_TABLE");
|
||||
Assert.assertEquals(k2.getLongPollNamespace(), LongPollNamespace.ROUTE_TABLE);
|
||||
Assert.assertNull(k2.getKey());
|
||||
|
||||
String key3 = "k3";
|
||||
KeyType k3 = KeyType.parseFrom(key3);
|
||||
Assert.assertEquals(k3.getKey(), key3);
|
||||
Assert.assertNull(k3.getLongPollNamespace());
|
||||
|
||||
KeyType k4 = KeyType.parseFrom("");
|
||||
Assert.assertNull(k4.getKey());
|
||||
Assert.assertNull(k4.getLongPollNamespace());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.DummyServeController;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.generated.EndpointInfo;
|
||||
import io.ray.serve.generated.EndpointSet;
|
||||
import io.ray.serve.generated.LongPollResult;
|
||||
import io.ray.serve.generated.UpdatedObject;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class LongPollClientTest {
|
||||
@Test
|
||||
public void disableTest() throws Throwable {
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put(RayServeConfig.LONG_POOL_CLIENT_ENABLED, "false");
|
||||
|
||||
ReplicaContext replicaContext = new ReplicaContext(null, null, null, config, null);
|
||||
Serve.setInternalReplicaContext(replicaContext);
|
||||
try {
|
||||
LongPollClientFactory.init(null);
|
||||
Assert.assertFalse(LongPollClientFactory.isInitialized());
|
||||
} finally {
|
||||
Serve.setInternalReplicaContext(null);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "unused"})
|
||||
@Test
|
||||
public void normalTest() throws Throwable {
|
||||
BaseServeTest.initRay();
|
||||
try {
|
||||
String prefix = "LongPollClientTest_normalTest";
|
||||
|
||||
// Init controller.
|
||||
ActorHandle<DummyServeController> controllerHandle =
|
||||
Ray.actor(DummyServeController::new, "")
|
||||
.setName(Constants.SERVE_CONTROLLER_NAME)
|
||||
.remote();
|
||||
|
||||
Serve.setInternalReplicaContext(null, null, null, null, null);
|
||||
|
||||
// Init route table.
|
||||
String endpointName1 = prefix + "_endpoint1";
|
||||
String endpointName2 = prefix + "_endpoint2";
|
||||
EndpointSet endpointSet =
|
||||
EndpointSet.newBuilder()
|
||||
.putEndpoints(
|
||||
endpointName1, EndpointInfo.newBuilder().setEndpointName(endpointName1).build())
|
||||
.putEndpoints(
|
||||
endpointName2, EndpointInfo.newBuilder().setEndpointName(endpointName2).build())
|
||||
.build();
|
||||
|
||||
// Construct a listener map.
|
||||
KeyType keyType = new KeyType(LongPollNamespace.ROUTE_TABLE, null);
|
||||
String[] testData = new String[] {"test"};
|
||||
Map<KeyType, KeyListener> keyListeners = new HashMap<>();
|
||||
keyListeners.put(
|
||||
keyType,
|
||||
(object) ->
|
||||
testData[0] =
|
||||
((Map<String, EndpointInfo>) object).get(endpointName1).getEndpointName());
|
||||
|
||||
// Register.
|
||||
LongPollClient longPollClient = new LongPollClient(controllerHandle, keyListeners);
|
||||
Assert.assertTrue(LongPollClientFactory.isInitialized());
|
||||
|
||||
// Construct updated object.
|
||||
int snapshotId = 10;
|
||||
UpdatedObject updatedObject =
|
||||
UpdatedObject.newBuilder()
|
||||
.setSnapshotId(snapshotId)
|
||||
.setObjectSnapshot(endpointSet.toByteString())
|
||||
.build();
|
||||
|
||||
// Mock LongPollResult.
|
||||
LongPollResult longPollResult =
|
||||
LongPollResult.newBuilder().putUpdatedObjects(keyType.toString(), updatedObject).build();
|
||||
ObjectRef<Boolean> mockLongPollResult =
|
||||
controllerHandle
|
||||
.task(DummyServeController::setLongPollResult, longPollResult.toByteArray())
|
||||
.remote();
|
||||
Assert.assertEquals(mockLongPollResult.get().booleanValue(), true);
|
||||
|
||||
// Poll.
|
||||
LongPollClientFactory.pollNext();
|
||||
|
||||
// Validation.
|
||||
Assert.assertEquals(LongPollClientFactory.SNAPSHOT_IDS.get(keyType).intValue(), snapshotId);
|
||||
Assert.assertEquals(
|
||||
((Map<String, EndpointInfo>) LongPollClientFactory.OBJECT_SNAPSHOTS.get(keyType)).size(),
|
||||
2);
|
||||
Assert.assertEquals(testData[0], endpointName1);
|
||||
|
||||
LongPollClientFactory.stop();
|
||||
Assert.assertFalse(LongPollClientFactory.isInitialized());
|
||||
} finally {
|
||||
BaseServeTest.clearAndShutdownRay();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ray.serve.poll;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class LongPollNamespaceTest {
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
String key = LongPollNamespace.ROUTE_TABLE.toString();
|
||||
Assert.assertEquals(key, "LongPollNamespace.ROUTE_TABLE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseFromTest() {
|
||||
String key = "ROUTE_TABLE";
|
||||
LongPollNamespace longPollNamespace = LongPollNamespace.parseFrom(key);
|
||||
Assert.assertEquals(longPollNamespace, LongPollNamespace.ROUTE_TABLE);
|
||||
|
||||
String unknown = "unknown";
|
||||
longPollNamespace = LongPollNamespace.parseFrom(unknown);
|
||||
Assert.assertNull(longPollNamespace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ray.serve.repdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
|
||||
public class DeploymentDemo {
|
||||
private String msg;
|
||||
|
||||
public DeploymentDemo(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String call() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Application deployment =
|
||||
Serve.deployment().setDeploymentDef(DeploymentDemo.class.getName()).bind();
|
||||
DeploymentHandle handle = Serve.run(deployment);
|
||||
System.out.println(handle.remote().result());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ray.serve.repdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
import io.ray.serve.handle.DeploymentResponse;
|
||||
|
||||
public class Driver {
|
||||
private DeploymentHandle modelAHandle;
|
||||
private DeploymentHandle modelBHandle;
|
||||
|
||||
public Driver(DeploymentHandle modelAHandle, DeploymentHandle modelBHandle) {
|
||||
this.modelAHandle = modelAHandle;
|
||||
this.modelBHandle = modelBHandle;
|
||||
}
|
||||
|
||||
public String call(String request) {
|
||||
DeploymentResponse responseA = modelAHandle.remote(request);
|
||||
DeploymentResponse responseB = modelBHandle.remote(request);
|
||||
return (String) responseA.result() + responseB.result();
|
||||
}
|
||||
|
||||
public static class ModelA {
|
||||
public String call(String msg) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModelB {
|
||||
public String call(String msg) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Application modelA = Serve.deployment().setDeploymentDef(ModelA.class.getName()).bind();
|
||||
Application modelB = Serve.deployment().setDeploymentDef(ModelB.class.getName()).bind();
|
||||
|
||||
Application driver =
|
||||
Serve.deployment().setDeploymentDef(Driver.class.getName()).bind(modelA, modelB);
|
||||
Serve.run(driver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ray.serve.repdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import java.util.Map;
|
||||
|
||||
public class Hello {
|
||||
|
||||
public static class HelloWorldArgs {
|
||||
private String message;
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelloWorld {
|
||||
private String message;
|
||||
|
||||
public HelloWorld(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String call() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public static Application appBuilder(Map<String, String> args) {
|
||||
return Serve.deployment()
|
||||
.setDeploymentDef(HelloWorld.class.getName())
|
||||
.bind(args.get("message"));
|
||||
}
|
||||
|
||||
public static Application typedAppBuilder(HelloWorldArgs args) {
|
||||
return Serve.deployment().setDeploymentDef(HelloWorld.class.getName()).bind(args.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ray.serve.repdemo;
|
||||
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.deployment.Application;
|
||||
import io.ray.serve.handle.DeploymentHandle;
|
||||
|
||||
public class Text {
|
||||
|
||||
public static class Hello {
|
||||
public String call() {
|
||||
return "Hello";
|
||||
}
|
||||
}
|
||||
|
||||
public static class World {
|
||||
public String call() {
|
||||
return " world!";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Ingress {
|
||||
private DeploymentHandle helloHandle;
|
||||
private DeploymentHandle worldHandle;
|
||||
|
||||
public Ingress(DeploymentHandle helloHandle, DeploymentHandle worldHandle) {
|
||||
this.helloHandle = helloHandle;
|
||||
this.worldHandle = worldHandle;
|
||||
}
|
||||
|
||||
public String call() {
|
||||
return (String) helloHandle.remote().result() + worldHandle.remote().result();
|
||||
}
|
||||
}
|
||||
|
||||
public static Application app() {
|
||||
Application hello = Serve.deployment().setDeploymentDef(Hello.class.getName()).bind();
|
||||
Application world = Serve.deployment().setDeploymentDef(World.class.getName()).bind();
|
||||
|
||||
Application app =
|
||||
Serve.deployment().setDeploymentDef(Ingress.class.getName()).bind(hello, world);
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class DummyReplica {
|
||||
|
||||
private AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
public String call() {
|
||||
return String.valueOf(counter.incrementAndGet());
|
||||
}
|
||||
|
||||
public void reconfigure(Object userConfig) {
|
||||
counter.set(0);
|
||||
}
|
||||
|
||||
public void reconfigure(Map<String, String> userConfig) {
|
||||
counter.set(Integer.valueOf(userConfig.get("value")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.DummyServeController;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.deployment.DeploymentVersion;
|
||||
import io.ray.serve.deployment.DeploymentWrapper;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class RayServeReplicaTest {
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
|
||||
try {
|
||||
BaseServeTest.initRay();
|
||||
|
||||
String prefix = "RayServeReplicaTest";
|
||||
String deploymentName = prefix + "_deployment";
|
||||
String replicaTag = prefix + "_replica";
|
||||
String version = "v1";
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put(RayServeConfig.LONG_POOL_CLIENT_ENABLED, "false");
|
||||
|
||||
ActorHandle<DummyServeController> controllerHandle =
|
||||
Ray.actor(DummyServeController::new, "")
|
||||
.setName(Constants.SERVE_CONTROLLER_NAME)
|
||||
.remote();
|
||||
controllerHandle
|
||||
.task(DummyServeController::getRootUrl)
|
||||
.remote()
|
||||
.get(); // Wait for the dummy controller to be ready.
|
||||
|
||||
DeploymentConfig deploymentConfig =
|
||||
new DeploymentConfig().setDeploymentLanguage(DeploymentLanguage.JAVA);
|
||||
DeploymentWrapper deploymentWrapper =
|
||||
new DeploymentWrapper()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentConfig(deploymentConfig)
|
||||
.setDeploymentVersion(new DeploymentVersion(version))
|
||||
.setDeploymentDef(DummyReplica.class.getName())
|
||||
.setAppName("app_test");
|
||||
|
||||
ActorHandle<RayServeWrappedReplica> replicHandle =
|
||||
Ray.actor(RayServeWrappedReplica::new, deploymentWrapper, replicaTag).remote();
|
||||
|
||||
// ready
|
||||
Assert.assertTrue(replicHandle.task(RayServeWrappedReplica::checkHealth).remote().get());
|
||||
|
||||
// handle request
|
||||
RequestMetadata.Builder requestMetadata = RequestMetadata.newBuilder();
|
||||
requestMetadata.setRequestId(RandomStringUtils.randomAlphabetic(10));
|
||||
requestMetadata.setCallMethod(Constants.CALL_METHOD);
|
||||
|
||||
ObjectRef<Object> resultRef =
|
||||
replicHandle
|
||||
.task(
|
||||
RayServeWrappedReplica::handleRequest,
|
||||
requestMetadata.build().toByteArray(),
|
||||
new Object[0])
|
||||
.remote();
|
||||
Assert.assertEquals((String) resultRef.get(), "1");
|
||||
|
||||
// reconfigure
|
||||
ObjectRef<Object> versionRef =
|
||||
replicHandle
|
||||
.task(
|
||||
RayServeWrappedReplica::reconfigure,
|
||||
(new DeploymentConfig().setVersion("")).toProtoBytes())
|
||||
.remote();
|
||||
Assert.assertEquals(
|
||||
DeploymentVersion.fromProtoBytes((byte[]) (versionRef.get())).getCodeVersion(), version);
|
||||
|
||||
deploymentConfig.setUserConfig(new Object()).setVersion("");
|
||||
replicHandle
|
||||
.task(RayServeWrappedReplica::reconfigure, deploymentConfig.toProtoBytes())
|
||||
.remote()
|
||||
.get();
|
||||
resultRef =
|
||||
replicHandle
|
||||
.task(
|
||||
RayServeWrappedReplica::handleRequest,
|
||||
requestMetadata.build().toByteArray(),
|
||||
new Object[0])
|
||||
.remote();
|
||||
Assert.assertEquals((String) resultRef.get(), "1");
|
||||
|
||||
deploymentConfig = deploymentConfig.setUserConfig(ImmutableMap.of("value", "100"));
|
||||
replicHandle
|
||||
.task(RayServeWrappedReplica::reconfigure, deploymentConfig.toProtoBytes())
|
||||
.remote()
|
||||
.get();
|
||||
resultRef =
|
||||
replicHandle
|
||||
.task(
|
||||
RayServeWrappedReplica::handleRequest,
|
||||
requestMetadata.build().toByteArray(),
|
||||
new Object[0])
|
||||
.remote();
|
||||
Assert.assertEquals((String) resultRef.get(), "101");
|
||||
|
||||
// get version
|
||||
versionRef = replicHandle.task(RayServeWrappedReplica::getVersion).remote();
|
||||
Assert.assertEquals(
|
||||
DeploymentVersion.fromProtoBytes((byte[]) (versionRef.get())).getCodeVersion(), version);
|
||||
|
||||
// prepare for shutdown
|
||||
ObjectRef<Boolean> shutdownRef =
|
||||
replicHandle.task(RayServeWrappedReplica::prepareForShutdown).remote();
|
||||
Assert.assertTrue(shutdownRef.get());
|
||||
|
||||
} finally {
|
||||
BaseServeTest.clearAndShutdownRay();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ray.serve.replica;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ReplicaNameTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String deploymentTag = "ReplicaNameTest";
|
||||
String replicaSuffix = RandomStringUtils.randomAlphabetic(6);
|
||||
ReplicaName replicaName = new ReplicaName(deploymentTag, replicaSuffix);
|
||||
|
||||
Assert.assertEquals(replicaName.getDeploymentTag(), deploymentTag);
|
||||
Assert.assertEquals(replicaName.getReplicaSuffix(), replicaSuffix);
|
||||
Assert.assertEquals(replicaName.getReplicaTag(), deploymentTag + "#" + replicaSuffix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package io.ray.serve.router;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.DummyServeController;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.deployment.DeploymentVersion;
|
||||
import io.ray.serve.deployment.DeploymentWrapper;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.generated.DeploymentTargetInfo;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.replica.RayServeWrappedReplica;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ReplicaSetTest {
|
||||
private String deploymentName = "ReplicaSetTest";
|
||||
|
||||
@Test
|
||||
public void updateWorkerReplicasTest() {
|
||||
try {
|
||||
BaseServeTest.initRay();
|
||||
ReplicaSet replicaSet = new ReplicaSet(deploymentName);
|
||||
DeploymentTargetInfo.Builder builder = DeploymentTargetInfo.newBuilder();
|
||||
|
||||
replicaSet.updateWorkerReplicas(builder.build());
|
||||
Map<String, Set<ObjectRef<Object>>> inFlightQueries = replicaSet.getInFlightQueries();
|
||||
Assert.assertTrue(inFlightQueries.isEmpty());
|
||||
} finally {
|
||||
BaseServeTest.shutdownRay();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void assignReplicaTest() {
|
||||
try {
|
||||
BaseServeTest.initRay();
|
||||
|
||||
String replicaTag = deploymentName + "_replica";
|
||||
String actorName = replicaTag;
|
||||
String version = "v1";
|
||||
String appName = "app1";
|
||||
// Controller
|
||||
ActorHandle<DummyServeController> controllerHandle =
|
||||
Ray.actor(DummyServeController::new, "")
|
||||
.setName(Constants.SERVE_CONTROLLER_NAME)
|
||||
.remote();
|
||||
|
||||
// Replica
|
||||
DeploymentConfig deploymentConfig =
|
||||
new DeploymentConfig().setDeploymentLanguage(DeploymentLanguage.JAVA);
|
||||
|
||||
Object[] initArgs =
|
||||
new Object[] {deploymentName, replicaTag, new Object(), new HashMap<>(), appName};
|
||||
|
||||
DeploymentWrapper deploymentWrapper =
|
||||
new DeploymentWrapper()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentConfig(deploymentConfig)
|
||||
.setDeploymentVersion(new DeploymentVersion(version))
|
||||
.setDeploymentDef(ReplicaContext.class.getName())
|
||||
.setInitArgs(initArgs);
|
||||
|
||||
ActorHandle<RayServeWrappedReplica> replicaHandle =
|
||||
Ray.actor(RayServeWrappedReplica::new, deploymentWrapper, replicaTag)
|
||||
.setName(actorName)
|
||||
.remote();
|
||||
Assert.assertTrue(replicaHandle.task(RayServeWrappedReplica::checkHealth).remote().get());
|
||||
|
||||
// ReplicaSet
|
||||
ReplicaSet replicaSet = new ReplicaSet(deploymentName);
|
||||
DeploymentTargetInfo.Builder builder = DeploymentTargetInfo.newBuilder();
|
||||
builder.addReplicaNames(actorName).setIsAvailable(true);
|
||||
replicaSet.updateWorkerReplicas(builder.build());
|
||||
|
||||
// assign
|
||||
RequestMetadata.Builder requestMetadata = RequestMetadata.newBuilder();
|
||||
requestMetadata.setRequestId(RandomStringUtils.randomAlphabetic(10));
|
||||
requestMetadata.setCallMethod("getDeploymentName");
|
||||
|
||||
Query query = new Query(requestMetadata.build(), null);
|
||||
ObjectRef<Object> resultRef = replicaSet.assignReplica(query);
|
||||
|
||||
Assert.assertEquals((String) resultRef.get(), deploymentName);
|
||||
} finally {
|
||||
BaseServeTest.clearAndShutdownRay();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package io.ray.serve.router;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.serve.BaseServeTest;
|
||||
import io.ray.serve.DummyServeController;
|
||||
import io.ray.serve.api.Serve;
|
||||
import io.ray.serve.common.Constants;
|
||||
import io.ray.serve.config.DeploymentConfig;
|
||||
import io.ray.serve.config.RayServeConfig;
|
||||
import io.ray.serve.deployment.DeploymentId;
|
||||
import io.ray.serve.deployment.DeploymentVersion;
|
||||
import io.ray.serve.deployment.DeploymentWrapper;
|
||||
import io.ray.serve.generated.DeploymentLanguage;
|
||||
import io.ray.serve.generated.DeploymentTargetInfo;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.replica.RayServeWrappedReplica;
|
||||
import io.ray.serve.replica.ReplicaContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class RouterTest {
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
try {
|
||||
BaseServeTest.initRay();
|
||||
|
||||
String deploymentName = "RouterTest";
|
||||
String replicaTag = deploymentName + "_replica";
|
||||
String actorName = replicaTag;
|
||||
String version = "v1";
|
||||
String appName = "app1";
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put(RayServeConfig.LONG_POOL_CLIENT_ENABLED, "false");
|
||||
|
||||
// Controller
|
||||
ActorHandle<DummyServeController> controllerHandle =
|
||||
Ray.actor(DummyServeController::new, "")
|
||||
.setName(Constants.SERVE_CONTROLLER_NAME)
|
||||
.remote();
|
||||
|
||||
// Replica
|
||||
DeploymentConfig deploymentConfig =
|
||||
new DeploymentConfig().setDeploymentLanguage(DeploymentLanguage.JAVA);
|
||||
|
||||
Object[] initArgs =
|
||||
new Object[] {deploymentName, replicaTag, new Object(), new HashMap<>(), appName};
|
||||
|
||||
DeploymentWrapper deploymentWrapper =
|
||||
new DeploymentWrapper()
|
||||
.setName(deploymentName)
|
||||
.setDeploymentConfig(deploymentConfig)
|
||||
.setDeploymentVersion(new DeploymentVersion(version))
|
||||
.setDeploymentDef(ReplicaContext.class.getName())
|
||||
.setInitArgs(initArgs);
|
||||
|
||||
ActorHandle<RayServeWrappedReplica> replicaHandle =
|
||||
Ray.actor(RayServeWrappedReplica::new, deploymentWrapper, replicaTag)
|
||||
.setName(actorName)
|
||||
.remote();
|
||||
Assert.assertTrue(replicaHandle.task(RayServeWrappedReplica::checkHealth).remote().get());
|
||||
|
||||
// Set ReplicaContext
|
||||
Serve.setInternalReplicaContext(null, null, null, config, null);
|
||||
|
||||
// Router
|
||||
Router router = new Router(controllerHandle, new DeploymentId(deploymentName, appName));
|
||||
DeploymentTargetInfo.Builder builder = DeploymentTargetInfo.newBuilder();
|
||||
builder.addReplicaNames(actorName).setIsAvailable(true);
|
||||
router.getReplicaSet().updateWorkerReplicas(builder.build());
|
||||
|
||||
// assign
|
||||
RequestMetadata.Builder requestMetadata = RequestMetadata.newBuilder();
|
||||
requestMetadata.setRequestId(RandomStringUtils.randomAlphabetic(10));
|
||||
requestMetadata.setCallMethod("getDeploymentName");
|
||||
|
||||
ObjectRef<Object> resultRef = router.assignRequest(requestMetadata.build(), null);
|
||||
Assert.assertEquals((String) resultRef.get(), deploymentName);
|
||||
} finally {
|
||||
BaseServeTest.clearAndShutdownRay();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class CommonUtilTest {
|
||||
|
||||
public static class InnerTest {}
|
||||
|
||||
@Test
|
||||
public void getSimpleClassNameTest() {
|
||||
String name = CommonUtil.getDeploymentName(InnerTest.class.getName());
|
||||
Assert.assertEquals(name, "InnerTest");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class LogUtilTest {
|
||||
|
||||
@Test
|
||||
public void formatTest() {
|
||||
String result = MessageFormatter.format("{},{},{}", "1", "2", "3");
|
||||
Assert.assertEquals(result, "1,2,3");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ReflectUtilTest {
|
||||
|
||||
static class ReflectExample {
|
||||
public ReflectExample() {}
|
||||
|
||||
public ReflectExample(Integer a) {}
|
||||
|
||||
public ReflectExample(String a) {}
|
||||
|
||||
public void test(String a) {}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void getConstructorTest() throws NoSuchMethodException {
|
||||
|
||||
Constructor<ReflectExample> constructor = ReflectUtil.getConstructor(ReflectExample.class);
|
||||
Assert.assertNotNull(constructor);
|
||||
|
||||
constructor = ReflectUtil.getConstructor(ReflectExample.class);
|
||||
Assert.assertNotNull(constructor);
|
||||
|
||||
constructor = ReflectUtil.getConstructor(ReflectExample.class, 2);
|
||||
Assert.assertNotNull(constructor);
|
||||
|
||||
constructor = ReflectUtil.getConstructor(ReflectExample.class, "");
|
||||
Assert.assertNotNull(constructor);
|
||||
|
||||
Assert.assertThrows(
|
||||
NoSuchMethodException.class,
|
||||
() -> ReflectUtil.getConstructor(ReflectExample.class, new HashMap<>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMethodTest() throws NoSuchMethodException {
|
||||
|
||||
Method method = ReflectUtil.getMethod(ReflectExample.class, "test", "");
|
||||
Assert.assertNotNull(method);
|
||||
|
||||
Assert.assertThrows(
|
||||
NoSuchMethodException.class,
|
||||
() -> ReflectUtil.getMethod(ReflectExample.class, "test", new HashMap<>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMethodStringsTest() {
|
||||
List<String> methodList = ReflectUtil.getMethodStrings(ReflectExample.class);
|
||||
String result = null;
|
||||
for (String method : methodList) {
|
||||
if (StringUtils.contains(method, "test")) {
|
||||
result = method;
|
||||
}
|
||||
}
|
||||
Assert.assertNotNull(result, "there should be test method");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.ray.serve.util;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.ray.serve.generated.RequestMetadata;
|
||||
import io.ray.serve.generated.RequestWrapper;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ServeProtoUtilTest {
|
||||
|
||||
@Test
|
||||
public void parseRequestMetadataTest() {
|
||||
String prefix = "parseRequestMetadataTest";
|
||||
String requestId = RandomStringUtils.randomAlphabetic(10);
|
||||
String callMethod = prefix + "_method";
|
||||
String endpoint = prefix + "_endpoint";
|
||||
String context = prefix + "_context";
|
||||
RequestMetadata requestMetadata =
|
||||
RequestMetadata.newBuilder()
|
||||
.setRequestId(requestId)
|
||||
.setCallMethod(callMethod)
|
||||
.putContext("context", context)
|
||||
.build();
|
||||
|
||||
RequestMetadata result = ServeProtoUtil.parseRequestMetadata(requestMetadata.toByteArray());
|
||||
Assert.assertNotNull(result);
|
||||
Assert.assertEquals(result.getCallMethod(), callMethod);
|
||||
Assert.assertEquals(result.getRequestId(), requestId);
|
||||
Assert.assertEquals(result.getContextMap().get("context"), context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseRequestWrapperTest() {
|
||||
byte[] body = new byte[] {1, 2};
|
||||
RequestWrapper requestWrapper =
|
||||
RequestWrapper.newBuilder().setBody(ByteString.copyFrom(body)).build();
|
||||
|
||||
RequestWrapper result = ServeProtoUtil.parseRequestWrapper(requestWrapper.toByteArray());
|
||||
Assert.assertNotNull(result);
|
||||
byte[] rstBody = result.getBody().toByteArray();
|
||||
Assert.assertEquals(rstBody[0], 1);
|
||||
Assert.assertEquals(rstBody[1], 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user