
com.vmware.photon.controller.model.adapters.awsadapter.AWSReservedInstancePlanService Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2015-2016 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 java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.logging.Level;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.ec2.model.DescribeRegionsRequest;
import com.amazonaws.services.ec2.model.DescribeRegionsResult;
import com.amazonaws.services.ec2.model.DescribeReservedInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeReservedInstancesResult;
import com.amazonaws.services.ec2.model.Region;
import com.amazonaws.services.ec2.model.ReservedInstances;
import org.apache.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
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.util.AdapterUtils;
import com.vmware.photon.controller.model.resources.ComputeService;
import com.vmware.photon.controller.model.util.ClusterUtil;
import com.vmware.photon.controller.model.util.ClusterUtil.ServiceTypeCluster;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.AuthCredentialsService;
/**
* Service to collect AWS Reserved Instances Plans for an account
*/
public class AWSReservedInstancePlanService extends StatelessService {
public static final String SELF_LINK = AWSUriPaths.AWS_RESERVED_INSTANCE_PLANS_ADAPTER;
private static final int NO_OF_MONTHS = 3;
private static final String REGION = "Region";
protected AWSClientManager ec2ClientManager;
public static class AWSReservedInstanceContext {
public List reservedInstancesPlan;
public ComputeService.ComputeStateWithDescription computeDesc;
public AuthCredentialsService.AuthCredentialsServiceState parentAuth;
}
/**
* Extend default 'start' logic with loading AWS client.
*/
@Override
public void handleStart(Operation op) {
this.ec2ClientManager = AWSClientManagerFactory
.getClientManager(AWSConstants.AwsClientType.EC2);
super.handleStart(op);
}
@Override
public void handlePost(Operation op) {
if (!op.hasBody()) {
op.fail(new IllegalArgumentException("body is required"));
return;
}
op.complete();
AWSReservedInstanceContext context = new AWSReservedInstanceContext();
String computeLink = op.getBody(String.class);
context.reservedInstancesPlan = Collections.synchronizedList(new ArrayList<>());
getAccountDescription(context, computeLink);
}
@Override
public void handleStop(Operation delete) {
AWSClientManagerFactory.returnClientManager(this.ec2ClientManager,
AWSConstants.AwsClientType.EC2);
super.handleStop(delete);
}
private Consumer getFailureConsumer(AWSReservedInstanceContext context, String msg) {
return ((t) -> {
if (context != null && context.computeDesc != null) {
log(Level.SEVERE, msg + " for compute " +
context.computeDesc.documentSelfLink + " " + t.getMessage());
}
});
}
protected void getAccountDescription(AWSReservedInstanceContext context, String computeLink) {
Consumer onSuccess = (op) -> {
context.computeDesc = op.getBody(ComputeService.ComputeStateWithDescription.class);
getParentAuth(context);
};
URI computeUri = UriUtils.extendUriWithQuery(UriUtils
.extendUri(getInventoryServiceUri(), computeLink),
UriUtils.URI_PARAM_ODATA_EXPAND, Boolean.TRUE.toString());
AdapterUtils.getServiceState(this, computeUri, onSuccess, getFailureConsumer(context,
"Error while retrieving compute description"));
}
protected void getParentAuth(AWSReservedInstanceContext context) {
Consumer onSuccess = (op) -> {
context.parentAuth = op.getBody(AuthCredentialsService.AuthCredentialsServiceState.class);
getReservedInstancesPlans(context);
};
String authLink = context.computeDesc.description.authCredentialsLink;
URI authUri = UriUtils.extendUri(getInventoryServiceUri(), authLink);
AdapterUtils.getServiceState(this, authUri, onSuccess, getFailureConsumer(context,
"Error while retrieving auth credentials"));
}
protected void getReservedInstancesPlans(AWSReservedInstanceContext context) {
this.ec2ClientManager.getOrCreateEC2ClientAsync(context.parentAuth, null, this)
.whenComplete((ec2Client, t) -> {
if (t != null) {
getFailureConsumer(context, "Error while creating EC2 client for")
.accept(t);
return;
}
ec2Client.describeRegionsAsync(getRegionAsyncHandler(context));
});
}
private AsyncHandler getRegionAsyncHandler(
AWSReservedInstanceContext context) {
AWSReservedInstancePlanService service = this;
return new AsyncHandler() {
@Override
public void onError(Exception e) {
log(Level.WARNING, "Error while fetching the regions for compute "
+ context.computeDesc.documentSelfLink
+ " " + Utils.toString(e));
}
@Override
public void onSuccess(DescribeRegionsRequest request,
DescribeRegionsResult describeRegionsResult) {
List regions = describeRegionsResult.getRegions();
if (CollectionUtils.isEmpty(regions)) {
log(Level.INFO, "No regions exist for compute"
+ context.computeDesc.documentSelfLink);
return;
}
AtomicInteger currentStageTaskCount = new AtomicInteger(regions.size());
/*
* Fetch all the regions from AWS and collect reserved instances plans for
* only those regions which are supported by SDK.
*/
for (Region region : regions) {
try {
Regions r = Regions.fromName(region.getRegionName());
if (r == null) {
new AWSReservedInstanceAsyncHandler(service.getHost(),
currentStageTaskCount, region, context)
.checkAndPatchReservedInstancesPlans();
continue;
}
} catch (Exception e) {
log(Level.WARNING, e.getMessage());
new AWSReservedInstanceAsyncHandler(service.getHost(),
currentStageTaskCount, region, context)
.checkAndPatchReservedInstancesPlans();
continue;
}
service.ec2ClientManager.getOrCreateEC2ClientAsync(context.parentAuth,
region.getRegionName(), service)
.whenComplete((ec2Client, t) -> {
if (t != null) {
getFailureConsumer(context,
"Error while creating EC2 client for").accept(t);
new AWSReservedInstanceAsyncHandler(service.getHost(),
currentStageTaskCount, region, context)
.checkAndPatchReservedInstancesPlans();
logWarning("client is null for region %s for compute %s",
region.getRegionName(),
context.computeDesc.documentSelfLink);
return;
}
ec2Client.describeReservedInstancesAsync(
new AWSReservedInstanceAsyncHandler(service.getHost(),
currentStageTaskCount, region, context));
});
}
}
};
}
public class AWSReservedInstanceAsyncHandler
implements AsyncHandler {
private AtomicInteger count;
private Region region;
private ServiceHost host;
private AWSReservedInstanceContext context;
public AWSReservedInstanceAsyncHandler(ServiceHost host, AtomicInteger count, Region region,
AWSReservedInstanceContext context) {
this.count = count;
this.region = region;
this.host = host;
this.context = context;
}
@Override
public void onError(Exception e) {
log(Level.WARNING,
"Error while fetching Reserved Instances for region " + this.region.getRegionName()
+ "for compute " + this.context.computeDesc.documentSelfLink + " " + Utils
.toString(e));
checkAndPatchReservedInstancesPlans();
}
@Override
public void onSuccess(DescribeReservedInstancesRequest request,
DescribeReservedInstancesResult describeReservedInstancesResult) {
List reservedInstances = describeReservedInstancesResult
.getReservedInstances();
if (CollectionUtils.isNotEmpty(reservedInstances)) {
DateTime endDate = new DateTime(DateTimeZone.UTC).withDayOfMonth(1)
.minusMonths(NO_OF_MONTHS)
.withTimeAtStartOfDay();
for (ReservedInstances reservedInstance : reservedInstances) {
if (reservedInstance.getEnd() != null && reservedInstance.getEnd().before(
endDate.toDate())) {
continue;
}
// Set the Region for RI's whose scope is region.
if (reservedInstance != null && reservedInstance.getScope() != null &&
reservedInstance.getScope().equals(REGION)) {
reservedInstance.setAvailabilityZone(this.region.getRegionName());
}
this.context.reservedInstancesPlan.add(reservedInstance);
}
}
checkAndPatchReservedInstancesPlans();
}
public void checkAndPatchReservedInstancesPlans() {
if (this.count.decrementAndGet() <= 0) {
// Patch the reserved instances plans only if they are changed
String reservedInstancesPlans = this.context.computeDesc.customProperties
.getOrDefault(AWSConstants.RESERVED_INSTANCE_PLAN_DETAILS, null);
if (reservedInstancesPlans != null) {
this.context.reservedInstancesPlan.sort(new ReservedInstancesIdComparator());
if (!reservedInstancesPlans
.equals(Utils.toJson(this.context.reservedInstancesPlan))) {
setCustomProperty(AWSConstants.RESERVED_INSTANCE_PLAN_DETAILS,
Utils.toJson(this.context.reservedInstancesPlan));
} else {
log(Level.FINE, "Reserved Instances plans are not changed for compute " +
this.context.computeDesc.documentSelfLink);
}
} else {
if (this.context.reservedInstancesPlan.size() > 0) {
log(Level.FINE, "Patching " + this.context.reservedInstancesPlan.size() +
" Reserved Instances Plans for compute "
+ this.context.computeDesc.documentSelfLink);
this.context.reservedInstancesPlan.sort(new ReservedInstancesIdComparator());
setCustomProperty(AWSConstants.RESERVED_INSTANCE_PLAN_DETAILS,
Utils.toJson(this.context.reservedInstancesPlan));
} else {
log(Level.FINE, "Reserved Instances plans are not present for compute " +
this.context.computeDesc.documentSelfLink);
}
}
}
}
private class ReservedInstancesIdComparator implements Comparator {
@Override
public int compare(ReservedInstances ri1, ReservedInstances ri2) {
return ri1.getReservedInstancesId().compareTo(ri2.getReservedInstancesId());
}
}
private void setCustomProperty(String key, String value) {
ComputeService.ComputeState accountState = new ComputeService.ComputeState();
accountState.customProperties = new HashMap<>();
accountState.customProperties.put(key, value);
sendRequest(Operation.createPatch(UriUtils
.extendUri(getInventoryServiceUri(), this.context.computeDesc.documentSelfLink))
.setBody(accountState));
}
}
private URI getInventoryServiceUri() {
return ClusterUtil.getClusterUri(getHost(), ServiceTypeCluster.INVENTORY_SERVICE);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy