/*
* Copyright 2010 Proofpoint, Inc.
*
* 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.proofpoint.cloudmanagement.service;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Module;
import com.proofpoint.log.Logger;
import com.proofpoint.units.DataSize;
import com.proofpoint.units.DataSize.Unit;
import org.jclouds.Constants;
import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.domain.Volume;
import org.jclouds.domain.Location;
import org.jclouds.openstack.keystone.v2_0.config.CredentialType;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.collect.ImmutableList.of;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getFirst;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.transform;
import static org.jclouds.domain.LocationScope.PROVIDER;
import static org.jclouds.domain.LocationScope.REGION;
public class JCloudsInstanceConnector implements InstanceConnector
{
private static final Logger log = Logger.get(JCloudsInstanceConnector.class);
private final ComputeService computeService;
private final String defaultImageId;
private final String name;
private final Map<String, ? extends Hardware> hardwareMap;
private final Map<String, ? extends Location> locationMap;
private final String awsVpcSubnetId;
public JCloudsInstanceConnector(final JCloudsConfig config)
{
Preconditions.checkNotNull(config);
this.name = config.getName();
this.awsVpcSubnetId = config.getAwsVpcSubnetId();
Properties overrides = new Properties();
if (config.getLocation() != null) {
overrides.setProperty(Constants.PROPERTY_ENDPOINT, config.getLocation());
}
overrides.setProperty(KeystoneProperties.CREDENTIAL_TYPE, CredentialType.PASSWORD_CREDENTIALS.toString());
overrides.setProperty(KeystoneProperties.VERSION, "2.0");
Set<Module> moduleOverrides = ImmutableSet.<Module>of(new JCloudsLoggingAdapterModule());
ComputeServiceContext context = new ComputeServiceContextFactory().createContext(
config.getApi(),
config.getUser(),
config.getSecret(),
moduleOverrides,
overrides);
computeService = context.getComputeService();
//There are too many images in ec2 to list them all, so we can't verify.
if (!config.getApi().equals("aws-ec2")) {
Preconditions.checkState(
any(computeService.listImages(), new Predicate<org.jclouds.compute.domain.Image>()
{
@Override
public boolean apply(@Nullable org.jclouds.compute.domain.Image image)
{
return image.getId().endsWith(config.getDefaultImageId());
}
}), "No image found for default image id [" + config.getDefaultImageId() + "] please verify that this image exists");
}
defaultImageId = config.getDefaultImageId();
hardwareMap = Maps.uniqueIndex(computeService.listHardwareProfiles(), new Function<Hardware, String>()
{
@Override
public String apply(@Nullable Hardware hardware)
{
return firstNonNull(hardware.getName(), hardware.getId());
}
});
locationMap = Maps.uniqueIndex(computeService.listAssignableLocations(), new Function<Location, String>()
{
@Override
public String apply(@Nullable Location location)
{
return location.getId();
}
});
getAllInstances();
}
public String createInstance(final String sizeName, String groupName, String locationId)
{
Hardware hardware = hardwareMap.get(sizeName);
Location location = locationMap.get(locationId);
Preconditions.checkNotNull(hardware, "No size found for [" + sizeName + "] please verify that this is a valid size.");
Preconditions.checkNotNull(location, "No location found for [" + locationId + "] please verify that this is a valid location.");
Set<? extends NodeMetadata> nodes;
try {
TemplateBuilder instanceTemplateBuilder = computeService.templateBuilder()
.imageId(String.format("%s/%s", locateParentMostRegionOrZone(location).getId(), defaultImageId))
.fromHardware(hardware)
.locationId(locationId);
if (awsVpcSubnetId != null) {
instanceTemplateBuilder.options(AWSEC2TemplateOptions.Builder.subnetId(awsVpcSubnetId).blockUntilRunning(false));
}
else {
instanceTemplateBuilder.options(computeService.templateOptions().blockUntilRunning(false));
}
nodes = computeService.createNodesInGroup(groupName, 1, instanceTemplateBuilder.build());
}
catch (RunNodesException e) {
log.error(e, "Couldn't start up instance requested by %s with size %s", groupName, sizeName);
throw new RuntimeException(e);
}
if (nodes == null || nodes.isEmpty()) {
log.error("Couldn't start up instance requested by %s with size %s", groupName, sizeName);
throw new RuntimeException("Unknown failure in starting instance.");
}
return Iterables.getOnlyElement(nodes).getProviderId();
}
private Location locateParentMostRegionOrZone(Location location)
{
if (location.getParent() == null || location.getParent().getScope() == PROVIDER) {
return location;
}
return locateParentMostRegionOrZone(location.getParent());
}
private Location locateParentMostZone(Location location)
{
if (location.getParent() == null || any(of(PROVIDER, REGION), equalTo(location.getParent().getScope()))) {
return location;
}
return locateParentMostZone(location.getParent());
}
public Iterable<Instance> getAllInstances()
{
return transform(
filter(computeService.listNodesDetailsMatching(Predicates.<ComputeMetadata>alwaysTrue()),
new Predicate<NodeMetadata>()
{
@Override
public boolean apply(@Nullable NodeMetadata input)
{
return input.getState() != NodeState.TERMINATED;
}
}), new NodeMetadataToInstance());
}
public Instance getInstance(final String id)
{
final Set<? extends NodeMetadata> nodeMetadataSet = getNodesWithProviderId(id);
if (nodeMetadataSet.isEmpty()) {
return null;
}
return new NodeMetadataToInstance().apply(getOnlyElement(nodeMetadataSet));
}
private Set<? extends NodeMetadata> getNodesWithProviderId(final String id)
{
return computeService.listNodesDetailsMatching(new Predicate<ComputeMetadata>()
{
@Override
public boolean apply(@Nullable ComputeMetadata input)
{
return input.getProviderId().equals(id);
}
});
}
public InstanceDestructionStatus destroyInstance(String id)
{
Set<? extends NodeMetadata> toDestroy = getNodesWithProviderId(id);
if (toDestroy.isEmpty()) {
return InstanceDestructionStatus.NOT_FOUND;
}
computeService.destroyNode(getOnlyElement(toDestroy).getId());
return InstanceDestructionStatus.DESTROYED;
}
public Iterable<Size> getSizes(final String location)
{
return transform(
Iterables.filter(
hardwareMap.entrySet(),
new Predicate<Entry<String, ? extends Hardware>>()
{
@Override
public boolean apply(@Nullable Entry<String, ? extends Hardware> input)
{
return !input.getKey().contains("deprecated") && (input.getValue().getLocation() == null || input.getValue().getLocation().getId().equals(location));
}
}
),
new Function<Entry<String, ? extends Hardware>, Size>()
{
@Override
public Size apply(@Nullable Entry<String, ? extends Hardware> input)
{
float cpus = 0;
for (Processor processor : input.getValue().getProcessors()) {
cpus += processor.getCores();
}
float disk = 0;
for (Volume volume : input.getValue().getVolumes()) {
disk += volume.getSize();
}
return new Size(input.getKey(), Math.round(cpus), new DataSize(input.getValue().getRam(), Unit.MEGABYTE), new DataSize(disk, Unit.GIGABYTE));
}
});
}
@Override
public String getName()
{
return name;
}
@Override
public Iterable<com.proofpoint.cloudmanagement.service.Location> getLocations()
{
return transform(computeService.listAssignableLocations(), new Function<Location, com.proofpoint.cloudmanagement.service.Location>()
{
@Override
public com.proofpoint.cloudmanagement.service.Location apply(@Nullable Location input)
{
return new com.proofpoint.cloudmanagement.service.Location(input.getId(), input.getDescription());
}
});
}
@Override
public com.proofpoint.cloudmanagement.service.Location getLocation(final String location)
{
Location jcloudsLocation = Iterables.find(computeService.listAssignableLocations(), new Predicate<Location>()
{
@Override
public boolean apply(@Nullable Location input)
{
return input.getId().equals(location);
}
});
return new com.proofpoint.cloudmanagement.service.Location(jcloudsLocation.getId(), jcloudsLocation.getDescription(), this.getSizes(jcloudsLocation.getId()));
}
private class NodeMetadataToInstance implements Function<NodeMetadata, Instance>
{
@Override
public Instance apply(@Nullable NodeMetadata input)
{
return new Instance.Builder()
.setId(input.getProviderId())
.setName(input.getName())
.setSize(firstNonNull(input.getHardware().getName(), input.getHardware().getId()))
.setStatus(input.getState().name())
.setLocation(locateParentMostZone(input.getLocation()).getId())
.setHostname(getFirst(concat(input.getPublicAddresses(), input.getPrivateAddresses()), input.getHostname()))
.build();
}
}
}