
com.vmware.photon.controller.model.adapters.awsadapter.AWSSubnetService Maven / Gradle / Ivy
The newest version!
/*
* 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.adapters.awsadapter;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_ELASTIC_IP_ALLOCATION_ID;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_NAT_GATEWAY_ID;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_ROUTE_TABLE_ID;
import static com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManagerFactory.returnClientManager;
import static com.vmware.photon.controller.model.util.PhotonModelUriUtils.createInventoryUri;
import java.net.URI;
import java.util.HashMap;
import java.util.UUID;
import com.vmware.photon.controller.model.adapterapi.SubnetInstanceRequest;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManager;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManagerFactory;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSNetworkClient;
import com.vmware.photon.controller.model.adapters.util.TaskManager;
import com.vmware.photon.controller.model.resources.EndpointService.EndpointState;
import com.vmware.photon.controller.model.resources.NetworkService.NetworkState;
import com.vmware.photon.controller.model.resources.SubnetService.SubnetState;
import com.vmware.photon.controller.model.support.LifecycleState;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.services.common.AuthCredentialsService.AuthCredentialsServiceState;
/**
* Adapter for provisioning a subnet on AWS.
*/
public class AWSSubnetService extends StatelessService {
public static final String SELF_LINK = AWSUriPaths.AWS_SUBNET_ADAPTER;
/**
* Subnet request context.
*/
private static class AWSSubnetContext {
final SubnetInstanceRequest request;
EndpointState endpoint;
AuthCredentialsServiceState credentials;
SubnetState subnetState;
NetworkState parentNetwork;
SubnetState publicSubnet;
String awsSubnetId;
AWSNetworkClient client;
TaskManager taskManager;
AWSNATGatewayContext natSubContext;
AWSSubnetContext(StatelessService service, SubnetInstanceRequest request) {
this.request = request;
this.taskManager = new TaskManager(service, request.taskReference,
request.resourceLink());
}
private static class AWSNATGatewayContext {
String allocationId;
String natGatewayId;
String routeTableId;
}
}
private AWSClientManager clientManager;
/**
* Extend default 'start' logic with loading AWS client.
*/
@Override
public void handleStart(Operation op) {
this.clientManager = AWSClientManagerFactory
.getClientManager(AWSConstants.AwsClientType.EC2);
super.handleStart(op);
}
/**
* Extend default 'stop' logic with releasing AWS client.
*/
@Override
public void handleStop(Operation op) {
returnClientManager(this.clientManager, AWSConstants.AwsClientType.EC2);
super.handleStop(op);
}
@Override
public void handlePatch(Operation op) {
if (!op.hasBody()) {
op.fail(new IllegalArgumentException("body is required"));
return;
}
// Immediately complete the Operation from calling task.
op.complete();
// initialize context object
AWSSubnetContext context = new AWSSubnetContext(this,
op.getBody(SubnetInstanceRequest.class));
DeferredResult.completed(context)
.thenCompose(this::populateContext)
.thenCompose(this::handleSubnetInstanceRequest)
.whenComplete((o, e) -> {
// Once done patch the calling task with correct stage.
if (e == null) {
context.taskManager.finishTask();
} else {
context.taskManager.patchTaskToFailure(e);
}
});
}
private DeferredResult populateContext(AWSSubnetContext context) {
return DeferredResult.completed(context)
.thenCompose(this::getSubnetState)
.thenCompose(this::getNATContext)
.thenCompose(this::getParentNetwork)
.thenCompose(this::getPublicSubnet)
.thenCompose(this::getEndpointState)
.thenCompose(this::getCredentials)
.thenCompose(this::getAWSClient);
}
private DeferredResult getSubnetState(AWSSubnetContext context) {
return this.sendWithDeferredResult(
Operation.createGet(context.request.resourceReference),
SubnetState.class)
.thenApply(subnetState -> {
context.subnetState = subnetState;
return context;
});
}
private DeferredResult getNATContext(AWSSubnetContext context) {
if (context.subnetState == null || context.subnetState.customProperties == null) {
return DeferredResult.completed(context);
}
String allocationId = context.subnetState.customProperties.get(
AWS_ELASTIC_IP_ALLOCATION_ID);
String natGatewayId = context.subnetState.customProperties.get(
AWS_NAT_GATEWAY_ID);
String routeTableId = context.subnetState.customProperties.get(
AWS_ROUTE_TABLE_ID);
if (allocationId != null || natGatewayId != null || routeTableId != null) {
context.natSubContext = new AWSSubnetContext.AWSNATGatewayContext();
context.natSubContext.allocationId = allocationId;
context.natSubContext.natGatewayId = natGatewayId;
context.natSubContext.routeTableId = routeTableId;
}
return DeferredResult.completed(context);
}
private DeferredResult getParentNetwork(AWSSubnetContext context) {
URI uri = context.request.buildUri(context.subnetState.networkLink);
return this.sendWithDeferredResult(
Operation.createGet(uri),
NetworkState.class)
.thenApply(parentNetwork -> {
context.parentNetwork = parentNetwork;
return context;
});
}
private DeferredResult getPublicSubnet(AWSSubnetContext context) {
if (context.subnetState.externalSubnetLink == null) {
return DeferredResult.completed(context);
}
URI uri = context.request.buildUri(context.subnetState.externalSubnetLink);
return this.sendWithDeferredResult(
Operation.createGet(uri),
SubnetState.class)
.thenApply(publicSubnet -> {
context.publicSubnet = publicSubnet;
return context;
});
}
private DeferredResult getEndpointState(AWSSubnetContext context) {
URI uri = context.request.buildUri(context.subnetState.endpointLink);
return this.sendWithDeferredResult(
Operation.createGet(uri),
EndpointState.class)
.thenApply(endpointState -> {
context.endpoint = endpointState;
return context;
});
}
private DeferredResult getCredentials(AWSSubnetContext context) {
URI uri = createInventoryUri(this.getHost(), context.endpoint.authCredentialsLink);
return this.sendWithDeferredResult(
Operation.createGet(uri),
AuthCredentialsServiceState.class)
.thenApply(authCredentialsServiceState -> {
context.credentials = authCredentialsServiceState;
return context;
});
}
private DeferredResult getAWSClient(AWSSubnetContext context) {
if (context.request.isMockRequest) {
return DeferredResult.completed(context);
}
DeferredResult r = new DeferredResult<>();
this.clientManager.getOrCreateEC2ClientAsync(context.credentials,
context.parentNetwork.regionId, this)
.whenComplete((client, t) -> {
if (t != null) {
r.fail(t);
return;
}
context.client = new AWSNetworkClient(this, client);
r.complete(context);
});
return r;
}
private DeferredResult handleSubnetInstanceRequest(AWSSubnetContext context) {
DeferredResult execution = DeferredResult.completed(context);
switch (context.request.requestType) {
case CREATE:
if (context.request.isMockRequest) {
// no need to go to the end-point; just generate random subnet id and zoneId
context.subnetState.zoneId = context.awsSubnetId = UUID.randomUUID().toString();
execution = execution.thenCompose(this::updateSubnetState);
} else {
execution = execution
.thenCompose(this::createSubnet)
.thenCompose(this::updateSubnetState)
.thenCompose(this::nameTagSubnet)
.thenCompose(this::provideOutboundAccess);
}
return execution;
case DELETE:
if (context.request.isMockRequest) {
// no need to go to the end-point
this.logFine("Mock request to delete an AWS subnet ["
+ context.subnetState.name + "] processed.");
} else {
execution = execution
.thenCompose(this::deleteSubnet)
.thenCompose(this::deleteNATResources);
}
return execution.thenCompose(this::deleteSubnetState);
default:
IllegalStateException ex = new IllegalStateException("unsupported request type");
return DeferredResult.failed(ex);
}
}
private DeferredResult createSubnet(AWSSubnetContext context) {
return context.client.createSubnetAsync(
context.subnetState.subnetCIDR,
context.parentNetwork.id,
context.subnetState.zoneId)
.thenApply(subnet -> {
context.awsSubnetId = subnet.getSubnetId();
context.subnetState.zoneId = context.subnetState.zoneId == null ?
subnet.getAvailabilityZone() : context.subnetState.zoneId;
return context;
});
}
private DeferredResult nameTagSubnet(AWSSubnetContext context) {
return context.client.createNameTagAsync(context.awsSubnetId, context.subnetState.name)
.thenApply(none -> context);
}
private DeferredResult updateSubnetState(AWSSubnetContext context) {
context.subnetState.id = context.awsSubnetId;
context.subnetState.lifecycleState = LifecycleState.READY;
addNATCustomProperties(context.natSubContext, context.subnetState);
return this.sendWithDeferredResult(
Operation.createPatch(this, context.subnetState.documentSelfLink)
.setBody(context.subnetState))
.thenApply(op -> context);
}
private DeferredResult deleteSubnet(AWSSubnetContext context) {
return context.client.deleteSubnetAsync(context.subnetState.id)
.thenApply((result) -> context);
}
private DeferredResult deleteNATResources(AWSSubnetContext context) {
if (context.natSubContext == null) {
return DeferredResult.completed(context);
}
return DeferredResult.completed(context)
.thenCompose(this::deleteNATGateway)
.thenCompose(this::deleteRouteTable)
.thenCompose(this::releaseIPAddress)
.thenApply((result) -> context);
}
private DeferredResult deleteNATGateway(AWSSubnetContext context) {
if (context.natSubContext.natGatewayId == null) {
return DeferredResult.completed(context);
}
return context.client.deleteNATGateway(context.natSubContext.natGatewayId,
context.taskManager, context.subnetState.documentExpirationTimeMicros)
.thenApply((result) -> context);
}
private DeferredResult deleteRouteTable(AWSSubnetContext context) {
if (context.natSubContext.routeTableId == null) {
return DeferredResult.completed(context);
}
return context.client.deleteRouteTable(context.natSubContext.routeTableId)
.thenApply((result) -> context);
}
private DeferredResult releaseIPAddress(AWSSubnetContext context) {
if (context.natSubContext.allocationId == null) {
return DeferredResult.completed(context);
}
return context.client.releaseElasticIPAddress(context.natSubContext.allocationId)
.thenApply((result) -> context);
}
private DeferredResult deleteSubnetState(AWSSubnetContext context) {
return this.sendWithDeferredResult(
Operation.createDelete(this, context.subnetState.documentSelfLink))
.thenApply(operation -> context);
}
/**
* Provides outbound access for the newly created subnet. That requires 5 steps:
* 1. allocate an elastic IP for a NAT gateway (temporary).
* 2. create a NAT gateway.
* 3. create a new route table.
* 4. associate the subnet to the new route table.
* 5. add a route for Internet traffic to the NAT gateway.
*/
private DeferredResult provideOutboundAccess(AWSSubnetContext context) {
DeferredResult execution = DeferredResult.completed(context);
if (context.publicSubnet == null) {
return execution;
}
return execution
.thenCompose(this::getAddressAllocationId)
.thenCompose(this::createRouteTable)
.thenCompose(this::createNATGateway)
.thenCompose(this::associateSubnetToRouteTable)
.thenCompose(this::addRouteToNatGateway);
}
/**
* Allocates an elastic IP address for the NAT gateway
* Note: this is temporary. Eventually, this address will be allocated outside this service.
*/
private DeferredResult getAddressAllocationId(AWSSubnetContext context) {
return context.client.allocateElasticIPAddress()
.thenApply(allocationId -> {
context.natSubContext = new AWSSubnetContext.AWSNATGatewayContext();
context.natSubContext.allocationId = allocationId;
return context;
})
.thenCompose(this::updateSubnetState);
}
/**
* Creates a NAT gateway
*/
private DeferredResult createNATGateway(AWSSubnetContext context) {
return context.client.createNatGateway(context.publicSubnet.id,
context.natSubContext.allocationId, context.taskManager,
context.subnetState.documentExpirationTimeMicros)
.thenApply(natGatewayId -> {
context.natSubContext.natGatewayId = natGatewayId;
return context;
})
.thenCompose(this::updateSubnetState);
}
/**
* Creates a new route table
*/
private DeferredResult createRouteTable(AWSSubnetContext context) {
return context.client.createRouteTable(context.parentNetwork.id)
.thenApply(routeTableId -> {
context.natSubContext.routeTableId = routeTableId;
return context;
})
.thenCompose(this::updateSubnetState);
}
/**
* Associates the newly provisioned subnet to the route table
*/
private DeferredResult associateSubnetToRouteTable(AWSSubnetContext context) {
return context.client.associateSubnetToRouteTable(context.natSubContext.routeTableId,
context.awsSubnetId).thenApply(ignore -> context);
}
/**
* Adds route for Internet traffic to NAT gateway
*/
private DeferredResult addRouteToNatGateway(AWSSubnetContext context) {
return context.client.addRouteToNatGateway(context.natSubContext.routeTableId,
context.natSubContext.natGatewayId).thenApply(ignore -> context);
}
/**
* Adds NAT-specific data to the subnet's custom properties
*/
private void addNATCustomProperties(AWSSubnetContext.AWSNATGatewayContext natContext,
SubnetState subnetState) {
if (natContext == null) {
return;
}
if (subnetState.customProperties == null) {
subnetState.customProperties = new HashMap<>();
}
if (natContext.allocationId != null) {
subnetState.customProperties.put(AWS_ELASTIC_IP_ALLOCATION_ID, natContext.allocationId);
}
if (natContext.natGatewayId != null) {
subnetState.customProperties.put(AWS_NAT_GATEWAY_ID, natContext.natGatewayId);
}
if (natContext.routeTableId != null) {
subnetState.customProperties.put(AWS_ROUTE_TABLE_ID, natContext.routeTableId);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy