com.vmware.photon.controller.model.resources.LoadBalancerDescriptionService Maven / Gradle / Ivy
/*
* Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, without warranties or
* conditions of any kind, EITHER EXPRESS OR IMPLIED. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.vmware.photon.controller.model.resources;
import java.net.URI;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.esotericsoftware.kryo.serializers.VersionFieldSerializer.Since;
import com.vmware.photon.controller.model.ServiceUtils;
import com.vmware.photon.controller.model.UriPaths;
import com.vmware.photon.controller.model.constants.PhotonModelConstants;
import com.vmware.photon.controller.model.constants.ReleaseConstants;
import com.vmware.photon.controller.model.resources.LoadBalancerDescriptionService.LoadBalancerDescription.HealthCheckConfiguration;
import com.vmware.photon.controller.model.resources.LoadBalancerDescriptionService.LoadBalancerDescription.Protocol;
import com.vmware.photon.controller.model.resources.LoadBalancerDescriptionService.LoadBalancerDescription.RouteConfiguration;
import com.vmware.photon.controller.model.util.AssertUtil;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceDocumentDescription.DocumentIndexingOption;
import com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption;
import com.vmware.xenon.common.StatefulService;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
/**
* Represents the desired state of a load balancer.
*/
public class LoadBalancerDescriptionService extends StatefulService {
public static final String FACTORY_LINK = UriPaths.RESOURCES_LOAD_BALANCER_DESCRIPTIONS;
public static final String FIELD_NAME_ENDPOINT_LINK = PhotonModelConstants.FIELD_NAME_ENDPOINT_LINK;
public static final String FIELD_NAME_COMPUTE_DESCRIPTION_LINK = "computeDescriptionLink";
public static final String FIELD_NAME_COMPUTE_DESCRIPTION_LINKS = "computeDescriptionLinks";
public static final String FIELD_NAME_SUBNET_LINKS = "subnetLinks";
public static final String FIELD_NAME_SECURITY_GROUP_LINKS = "securityGroupLinks";
public static final String FIELD_NAME_ROUTES = "routes";
public static final String FIELD_NAME_ROUTES_PROTOCOL = "protocol";
public static final String FIELD_NAME_ROUTES_PORT = "port";
public static final String FIELD_NAME_ROUTES_INSTANCE_PROTOCOL = "instanceProtocol";
public static final String FIELD_NAME_ROUTES_INSTANCE_PORT = "instancePort";
public static final String FIELD_NAME_INTERNET_FACING = "internetFacing";
public static final int MIN_PORT_NUMBER = 1;
public static final int MAX_PORT_NUMBER = 65535;
/**
* Represents the desired state of a load balancer.
*/
public static class LoadBalancerDescription extends ResourceState {
/**
* Non-exhaustive list of commonly used protocols for routing and health checks.
*/
public static enum Protocol {
HTTP,
HTTPS,
TCP,
UDP
}
/**
* Represents a load balancer configuration for checking the health of the load-balanced
* back-end instances.
*/
public static class HealthCheckConfiguration {
/**
* (Required) Protocol used for the health check. String representation of
* {@link LoadBalancerDescription.Protocol} values can be used or a custom string that
* will be sent to the cloud provider.
*/
public String protocol;
/**
* (Required) Port on the back-end instance machine to use for the health check.
*/
public String port;
/**
* (Optional) URL path on the back-end instance against which a {@code GET} request will
* be performed for the health check. Useful when the health check protocol is
* HTTP/HTTPS.
*/
public String urlPath;
/**
* (Optional) Interval (in seconds) at which the health checks will be performed. If
* not specified, a provider-default will be used.
*/
public Integer intervalSeconds;
/**
* (Optional) Timeout (in seconds) to wait for a response from the back-end instance. If
* not specified, a provider-default will be used.
*/
public Integer timeoutSeconds;
/**
* (Optional) Number of consecutive check failures before considering a particular
* back-end instance as unhealthy. If not specified, a provider-default will be used.
*/
public Integer unhealthyThreshold;
/**
* (Optional) Number of consecutive successful checks before considering a particular
* back-end instance as healthy. If not specified, a provider-default will be used.
*/
public Integer healthyThreshold;
}
/**
* Represents a configuration for routing incoming requests to the back-end instances.
* A load balancer may support multiple such configurations.
*/
public static class RouteConfiguration {
/**
* (Required) Front-end (incoming) protocol. String representation of
* {@link LoadBalancerDescription.Protocol} values can be used or a custom string that
* will be sent to the cloud provider.
*/
public String protocol;
/**
* (Required) Front-end (incoming) port where the load balancer is listening to.
*/
public String port;
/**
* (Required) Back-end instance protocol. String representation of
* {@link LoadBalancerDescription.Protocol} values can be used or a custom string that
* will be sent to the cloud provider.
*/
public String instanceProtocol;
/**
* (Required) Back-end instance port where the traffic is routed to.
*/
public String instancePort;
/**
* (Optional) Health check configuration for this route configuration. Note that some
* providers may only support a single health check configuration even if there are
* multiple route configurations. In that case, it is up to the provider to pick the
* health configuration to use.
*/
public HealthCheckConfiguration healthCheckConfiguration;
}
/**
* Link to the cloud account endpoint the load balancer belongs to.
*/
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
public String endpointLink;
/**
* Link to the description of the instance cluster.
*/
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
@Deprecated
public String computeDescriptionLink;
/**
* Descriptions of the instances that are load balanced. Could be a single description of
* an instance cluster, or multiple ones.
*/
@Since(ReleaseConstants.RELEASE_VERSION_0_6_42)
public List computeDescriptionLinks;
/**
* Name of the network the load balancer is attached to. Similar to the {@code name} field
* in {@code NetworkInterfaceDescription} that is used to find a network for each NIC on
* the compute.
*
* Overridden by the {@link #subnetLinks} field, if set. One of the two fields must be set.
*/
@Since(ReleaseConstants.RELEASE_VERSION_0_6_19)
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
public String networkName;
/**
* List of subnets the load balancer is attached to. Typically these must be in different
* availability zones, and have nothing to do with the subnets the cluster instances are
* attached to.
*
* If not set, the subnets are determined based on the {@link #networkName} field. One of
* the two fields must be set.
*/
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
public Set subnetLinks;
/**
* Optional list of security groups to apply on the load balancer.
*/
@Since(ReleaseConstants.RELEASE_VERSION_0_6_23)
public List securityGroupLinks;
/**
* Internet-facing load balancer or an internal load balancer
*/
@Since(ReleaseConstants.RELEASE_VERSION_0_6_19)
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
public Boolean internetFacing;
/**
* Routing configuration between the load balancer and the back-end instances.
*
* {@code PATCH} merging strategy: if not {@code NULL} in the patch body, the current
* value is replaced by the one given in the patch request (no advanced per-item merging).
*/
@Since(ReleaseConstants.RELEASE_VERSION_0_6_19)
@UsageOption(option = PropertyUsageOption.REQUIRED)
public List routes;
/**
* The adapter to use to create the load balancer instance.
*/
@UsageOption(option = PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL)
public URI instanceAdapterReference;
}
public LoadBalancerDescriptionService() {
super(LoadBalancerDescription.class);
super.toggleOption(ServiceOption.PERSISTENCE, true);
super.toggleOption(ServiceOption.REPLICATION, true);
super.toggleOption(ServiceOption.OWNER_SELECTION, true);
super.toggleOption(ServiceOption.IDEMPOTENT_POST, true);
}
@Override
public void handleStart(Operation start) {
try {
processInput(start);
start.complete();
} catch (Throwable t) {
start.fail(t);
}
}
@Override
public void handleDelete(Operation delete) {
ResourceUtils.handleDelete(delete, this);
}
@Override
public void handlePut(Operation put) {
try {
LoadBalancerDescription returnState = processInput(put);
setState(put, returnState);
put.complete();
} catch (Throwable t) {
put.fail(t);
}
}
@Override
public void handlePost(Operation post) {
try {
LoadBalancerDescription returnState = processInput(post);
setState(post, returnState);
post.complete();
} catch (Throwable t) {
post.fail(t);
}
}
private LoadBalancerDescription processInput(Operation op) {
if (!op.hasBody()) {
throw (new IllegalArgumentException("body is required"));
}
LoadBalancerDescription state = op.getBody(LoadBalancerDescription.class);
// populate the new computeDescriptionLinks from the legacy computeDescriptionLink
if (state.computeDescriptionLink != null && state.computeDescriptionLinks == null) {
state.computeDescriptionLinks = Arrays.asList(state.computeDescriptionLink);
}
validateState(state);
return state;
}
@Override
public void handlePatch(Operation patch) {
LoadBalancerDescription currentState = getState(patch);
ResourceUtils.handlePatch(patch, currentState, getStateDescription(),
LoadBalancerDescription.class, op -> {
LoadBalancerDescription patchBody = op.getBody(LoadBalancerDescription.class);
boolean hasChanged = false;
if (patchBody.regionId != null && currentState.regionId == null) {
hasChanged = true;
currentState.regionId = patchBody.regionId;
}
// if routes are passed, they override the current ones
if (patchBody.routes != null) {
hasChanged = true;
currentState.routes = patchBody.routes;
}
// merge securityGroupLinks so that there are no duplicate values
if (patchBody.securityGroupLinks != null) {
if (currentState.securityGroupLinks == null) {
currentState.securityGroupLinks = patchBody.securityGroupLinks;
hasChanged = true;
} else {
for (String link : patchBody.securityGroupLinks) {
if (!currentState.securityGroupLinks.contains(link)) {
currentState.securityGroupLinks.add(link);
hasChanged = true;
}
}
}
}
// merge computeDescriptionLinks so that there are no duplicate values
if (patchBody.computeDescriptionLinks != null) {
if (currentState.computeDescriptionLinks == null) {
currentState.computeDescriptionLinks = patchBody.computeDescriptionLinks;
hasChanged = true;
} else {
for (String link : patchBody.computeDescriptionLinks) {
if (!currentState.computeDescriptionLinks.contains(link)) {
currentState.computeDescriptionLinks.add(link);
hasChanged = true;
}
}
}
}
// TODO pmitrov: temporary keep in sync legacy and new fields
if (patchBody.computeDescriptionLink != null) {
currentState.computeDescriptionLinks = Arrays.asList(patchBody
.computeDescriptionLink);
hasChanged = true;
}
return Boolean.valueOf(hasChanged);
});
}
private void validateState(LoadBalancerDescription state) {
Utils.validateState(getStateDescription(), state);
AssertUtil.assertTrue((state.networkName == null) != (state.subnetLinks == null),
"Either networkName or subnetLinks must be set.");
validateRoutes(state.routes);
}
static void validateRoutes(List routes) {
if (routes != null) {
routes.forEach(LoadBalancerDescriptionService::validateRoute);
}
}
private static void validateRoute(RouteConfiguration route) {
AssertUtil.assertNotNull(route, "A route configuration must not be null");
AssertUtil.assertNotEmpty(route.protocol, "No protocol provided in route configuration");
AssertUtil.assertNotEmpty(route.port, "No port provided in route configuration");
AssertUtil.assertNotEmpty(route.instanceProtocol,
"No instance protocol provided in route configuration");
AssertUtil.assertNotEmpty(route.instancePort,
"No instance port provided in route configuration");
validatePort(route.port);
validatePort(route.instancePort);
if (route.healthCheckConfiguration != null) {
validateHealthCheck(route.healthCheckConfiguration);
}
}
private static void validateHealthCheck(HealthCheckConfiguration config) {
AssertUtil.assertNotEmpty(config.protocol,
"No protocol provided in health check configuration");
AssertUtil.assertNotEmpty(config.port, "No port provided in health check configuration");
validatePort(config.port);
}
private static void validatePort(String port) {
int portNumber = Integer.parseInt(port);
AssertUtil.assertTrue(portNumber >= MIN_PORT_NUMBER && portNumber <= MAX_PORT_NUMBER,
"Invalid port number: %d." + portNumber);
}
@Override
public ServiceDocument getDocumentTemplate() {
ServiceDocument td = super.getDocumentTemplate();
// enable metadata indexing
td.documentDescription.documentIndexingOptions =
EnumSet.of(DocumentIndexingOption.INDEX_METADATA);
ServiceUtils.setRetentionLimit(td);
LoadBalancerDescription template = (LoadBalancerDescription) td;
template.id = UUID.randomUUID().toString();
template.name = "load-balancer";
template.endpointLink = UriUtils.buildUriPath(EndpointService.FACTORY_LINK,
"my-endpoint");
template.networkName = "lb-net";
RouteConfiguration routeConfiguration = new RouteConfiguration();
routeConfiguration.protocol = Protocol.HTTP.name();
routeConfiguration.port = "80";
routeConfiguration.instanceProtocol = Protocol.HTTP.name();
routeConfiguration.instancePort = "80";
routeConfiguration.healthCheckConfiguration = new HealthCheckConfiguration();
routeConfiguration.healthCheckConfiguration.protocol = Protocol.HTTP.name();
routeConfiguration.healthCheckConfiguration.port = "80";
template.routes = Arrays.asList(routeConfiguration);
return template;
}
}