blob: 8f9b67c883cab296e17d927b42fd3479640f08aa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.azurecompute.arm.compute;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.builder;
import static com.google.common.collect.ImmutableList.of;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.transform;
import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId;
import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.getMarketplacePlanFromImageMetadata;
import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.IMAGE_PUBLISHERS;
import static org.jclouds.azurecompute.arm.util.VMImages.isCustom;
import static org.jclouds.compute.util.ComputeServiceUtils.metadataAndTagsAsCommaDelimitedValue;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.PublicIpAvailablePredicateFactory;
import org.jclouds.azurecompute.arm.compute.functions.CustomImageToVMImage;
import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions;
import org.jclouds.azurecompute.arm.compute.strategy.CleanupResources;
import org.jclouds.azurecompute.arm.domain.AvailabilitySet;
import org.jclouds.azurecompute.arm.domain.CreationData;
import org.jclouds.azurecompute.arm.domain.DataDisk;
import org.jclouds.azurecompute.arm.domain.HardwareProfile;
import org.jclouds.azurecompute.arm.domain.IdReference;
import org.jclouds.azurecompute.arm.domain.ImageReference;
import org.jclouds.azurecompute.arm.domain.IpConfiguration;
import org.jclouds.azurecompute.arm.domain.IpConfigurationProperties;
import org.jclouds.azurecompute.arm.domain.Location;
import org.jclouds.azurecompute.arm.domain.ManagedDiskParameters;
import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCardProperties;
import org.jclouds.azurecompute.arm.domain.NetworkProfile;
import org.jclouds.azurecompute.arm.domain.OSDisk;
import org.jclouds.azurecompute.arm.domain.OSProfile;
import org.jclouds.azurecompute.arm.domain.Offer;
import org.jclouds.azurecompute.arm.domain.Plan;
import org.jclouds.azurecompute.arm.domain.PublicIPAddress;
import org.jclouds.azurecompute.arm.domain.PublicIPAddressProperties;
import org.jclouds.azurecompute.arm.domain.RegionAndId;
import org.jclouds.azurecompute.arm.domain.ResourceGroup;
import org.jclouds.azurecompute.arm.domain.ResourceProviderMetaData;
import org.jclouds.azurecompute.arm.domain.SKU;
import org.jclouds.azurecompute.arm.domain.StorageAccountType;
import org.jclouds.azurecompute.arm.domain.StorageProfile;
import org.jclouds.azurecompute.arm.domain.VMHardware;
import org.jclouds.azurecompute.arm.domain.VMImage;
import org.jclouds.azurecompute.arm.domain.VMSize;
import org.jclouds.azurecompute.arm.domain.Version;
import org.jclouds.azurecompute.arm.domain.VirtualMachine;
import org.jclouds.azurecompute.arm.domain.VirtualMachineProperties;
import org.jclouds.azurecompute.arm.features.OSImageApi;
import org.jclouds.azurecompute.arm.features.PublicIPAddressApi;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.location.Region;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* Defines the connection between the {@link AzureComputeApi} implementation and
* the jclouds {@link org.jclouds.compute.ComputeService}.
*/
@Singleton
public class AzureComputeServiceAdapter implements ComputeServiceAdapter<VirtualMachine, VMHardware, VMImage, Location> {
public static final String GROUP_KEY = "jclouds_group";
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final CleanupResources cleanupResources;
private final AzureComputeApi api;
private final List<String> imagePublishers;
private final Supplier<Set<String>> regionIds;
private final PublicIpAvailablePredicateFactory publicIpAvailable;
private final LoadingCache<String, ResourceGroup> resourceGroupMap;
private final CustomImageToVMImage customImagetoVmImage;
@Inject
AzureComputeServiceAdapter(final AzureComputeApi api, @Named(IMAGE_PUBLISHERS) String imagePublishers,
CleanupResources cleanupResources, @Region Supplier<Set<String>> regionIds,
PublicIpAvailablePredicateFactory publicIpAvailable, LoadingCache<String, ResourceGroup> resourceGroupMap,
CustomImageToVMImage customImagetoVmImage) {
this.api = api;
this.imagePublishers = Splitter.on(',').trimResults().omitEmptyStrings().splitToList(imagePublishers);
this.cleanupResources = cleanupResources;
this.regionIds = regionIds;
this.publicIpAvailable = publicIpAvailable;
this.resourceGroupMap = resourceGroupMap;
this.customImagetoVmImage = customImagetoVmImage;
}
@Override
public NodeAndInitialCredentials<VirtualMachine> createNodeWithGroupEncodedIntoName(final String group, final String name, final Template template) {
// TODO network ids => create one nic in each network
String locationName = template.getLocation().getId();
Image image = template.getImage();
String hardwareId = template.getHardware().getId();
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(locationName);
// TODO ARM specific options
AzureTemplateOptions templateOptions = template.getOptions().as(AzureTemplateOptions.class);
String subnetId = templateOptions.getSubnetId();
IdReference availabilitySet = getAvailabilitySetIdReference(templateOptions.getAvailabilitySet());
StorageProfile storageProfile = createStorageProfile(image, templateOptions.getDataDisks());
NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, resourceGroup.name(), template.getOptions());
HardwareProfile hardwareProfile = HardwareProfile.builder().vmSize(hardwareId).build();
OSProfile osProfile = createOsProfile(name, template);
NetworkProfile networkProfile = NetworkProfile.builder().networkInterfaces(of(IdReference.create(nic.id()))).build();
VirtualMachineProperties virtualMachineProperties = VirtualMachineProperties.builder()
.licenseType(null) // TODO
.availabilitySet(availabilitySet)
.hardwareProfile(hardwareProfile)
.storageProfile(storageProfile)
.osProfile(osProfile)
.networkProfile(networkProfile)
.build();
// Store group apart from the name to be able to identify nodes with
// custom names in the configured group
template.getOptions().getUserMetadata().put(GROUP_KEY, group);
Map<String, String> metadataAndTags = metadataAndTagsAsCommaDelimitedValue(template.getOptions());
Plan plan = getMarketplacePlanFromImageMetadata(template.getImage());
VirtualMachine virtualMachine = api.getVirtualMachineApi(resourceGroup.name()).createOrUpdate(name, template.getLocation().getId(),
virtualMachineProperties, metadataAndTags, plan);
// Safe to pass null credentials here, as jclouds will default populate
// the node with the default credentials from the image, or the ones in
// the options, if provided.
RegionAndId regionAndId = RegionAndId.fromRegionAndId(locationName, name);
return new NodeAndInitialCredentials<VirtualMachine>(virtualMachine, regionAndId.slashEncode(), null);
}
@Override
public Iterable<VMHardware> listHardwareProfiles() {
final List<VMHardware> hwProfiles = Lists.newArrayList();
for (Location location : listLocations()) {
Iterable<VMSize> vmSizes = api.getVMSizeApi(location.name()).list();
for (VMSize vmSize : vmSizes) {
VMHardware hwProfile = VMHardware
.create(vmSize.name(), vmSize.numberOfCores(), vmSize.osDiskSizeInMB(),
vmSize.resourceDiskSizeInMB(), vmSize.memoryInMB(), vmSize.maxDataDiskCount(), location.name(),
false);
hwProfiles.add(hwProfile);
}
}
return hwProfiles;
}
private List<VMImage> getImagesFromPublisher(String publisherName, String location) {
List<VMImage> osImagesRef = Lists.newArrayList();
OSImageApi osImageApi = api.getOSImageApi(location);
Iterable<Offer> offerList = osImageApi.listOffers(publisherName);
for (Offer offer : offerList) {
Iterable<SKU> skuList = osImageApi.listSKUs(publisherName, offer.name());
for (SKU sku : skuList) {
Iterable<Version> versionList = osImageApi.listVersions(publisherName, offer.name(), sku.name());
for (Version version : versionList) {
Version versionDetails = osImageApi.getVersion(publisherName, offer.name(), sku.name(), version.name());
VMImage vmImage = VMImage.azureImage().publisher(publisherName).offer(offer.name()).sku(sku.name())
.version(versionDetails.name()).location(location).versionProperties(versionDetails.properties())
.build();
osImagesRef.add(vmImage);
}
}
}
return osImagesRef;
}
private List<VMImage> listImagesByLocation(String location) {
final List<VMImage> osImages = Lists.newArrayList();
for (String publisher : imagePublishers) {
osImages.addAll(getImagesFromPublisher(publisher, location));
}
return osImages;
}
private List<VMImage> listCustomImagesByLocation(String location) {
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(location);
List<org.jclouds.azurecompute.arm.domain.Image> customImages = api.getVirtualMachineImageApi(resourceGroup.name()).list();
return Lists.transform(customImages, customImagetoVmImage);
}
@Override
public Iterable<VMImage> listImages() {
final ImmutableList.Builder<VMImage> osImages = ImmutableList.builder();
Iterable<String> availableLocationNames = transform(listLocations(), new Function<Location, String>() {
@Override
public String apply(Location location) {
return location.name();
}
});
for (String locationName : availableLocationNames) {
osImages.addAll(listImagesByLocation(locationName));
osImages.addAll(listCustomImagesByLocation(locationName));
}
return osImages.build();
}
@Override
public VMImage getImage(final String id) {
VMImage image = decodeFieldsFromUniqueId(id);
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(image.location());
if (image.custom()) {
org.jclouds.azurecompute.arm.domain.Image vmImage = api.getVirtualMachineImageApi(resourceGroup.name()).get(image.name());
return vmImage == null ? null : customImagetoVmImage.apply(vmImage);
}
String location = image.location();
String publisher = image.publisher();
String offer = image.offer();
String sku = image.sku();
OSImageApi osImageApi = api.getOSImageApi(location);
List<Version> versions = osImageApi.listVersions(publisher, offer, sku);
if (!versions.isEmpty()) {
Version version = osImageApi.getVersion(publisher, offer, sku, versions.get(0).name());
return VMImage.azureImage().publisher(publisher).offer(offer).sku(sku).version(version.name())
.location(location).versionProperties(version.properties()).build();
}
return null;
}
@Override
public Iterable<Location> listLocations() {
final Iterable<String> vmLocations = FluentIterable.from(api.getResourceProviderApi().get("Microsoft.Compute"))
.filter(new Predicate<ResourceProviderMetaData>() {
@Override
public boolean apply(ResourceProviderMetaData input) {
return input.resourceType().equals("virtualMachines");
}
}).transformAndConcat(new Function<ResourceProviderMetaData, Iterable<String>>() {
@Override
public Iterable<String> apply(ResourceProviderMetaData resourceProviderMetaData) {
return resourceProviderMetaData.locations();
}
});
List<Location> locations = FluentIterable.from(api.getLocationApi().list()).filter(new Predicate<Location>() {
@Override
public boolean apply(Location location) {
return Iterables.contains(vmLocations, location.displayName());
}
}).filter(new Predicate<Location>() {
@Override
public boolean apply(Location location) {
return regionIds.get().isEmpty() ? true : regionIds.get().contains(location.name());
}
}).toList();
return locations;
}
@Override
public VirtualMachine getNode(final String id) {
RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
return api.getVirtualMachineApi(resourceGroup.name()).get(regionAndId.id());
}
@Override
public void destroyNode(final String id) {
checkState(cleanupResources.cleanupNode(id), "server(%s) and its resources still there after deleting!?", id);
}
@Override
public void rebootNode(final String id) {
RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
api.getVirtualMachineApi(resourceGroup.name()).restart(regionAndId.id());
}
@Override
public void resumeNode(final String id) {
RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
api.getVirtualMachineApi(resourceGroup.name()).start(regionAndId.id());
}
@Override
public void suspendNode(final String id) {
RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
ResourceGroup resourceGroup = resourceGroupMap.getUnchecked(regionAndId.region());
api.getVirtualMachineApi(resourceGroup.name()).stop(regionAndId.id());
}
@Override
public Iterable<VirtualMachine> listNodes() {
ImmutableList.Builder<VirtualMachine> nodes = builder();
for (ResourceGroup resourceGroup : api.getResourceGroupApi().list()) {
nodes.addAll(api.getVirtualMachineApi(resourceGroup.name()).list());
}
return nodes.build();
}
@Override
public Iterable<VirtualMachine> listNodesByIds(final Iterable<String> ids) {
return filter(listNodes(), new Predicate<VirtualMachine>() {
@Override
public boolean apply(VirtualMachine virtualMachine) {
return contains(ids, virtualMachine.id());
}
});
}
private OSProfile createOsProfile(String computerName, Template template) {
String defaultLoginUser = template.getImage().getDefaultCredentials().getUser();
String defaultLoginPassword = template.getImage().getDefaultCredentials().getOptionalPassword().get();
String adminUsername = Objects.firstNonNull(template.getOptions().getLoginUser(), defaultLoginUser);
String adminPassword = Objects.firstNonNull(template.getOptions().getLoginPassword(), defaultLoginPassword);
OSProfile.Builder builder = OSProfile.builder().adminUsername(adminUsername).adminPassword(adminPassword)
.computerName(computerName);
if (template.getOptions().getPublicKey() != null
&& OsFamily.WINDOWS != template.getImage().getOperatingSystem().getFamily()) {
OSProfile.LinuxConfiguration linuxConfiguration = OSProfile.LinuxConfiguration.create("true",
OSProfile.LinuxConfiguration.SSH.create(of(OSProfile.LinuxConfiguration.SSH.SSHPublicKey
.create(String.format("/home/%s/.ssh/authorized_keys", adminUsername), template.getOptions()
.getPublicKey()))));
builder.linuxConfiguration(linuxConfiguration);
}
return builder.build();
}
private NetworkInterfaceCard createNetworkInterfaceCard(String subnetId, String name, String locationName,
String azureGroup, TemplateOptions options) {
final PublicIPAddressApi ipApi = api.getPublicIPAddressApi(azureGroup);
PublicIPAddressProperties properties = PublicIPAddressProperties.builder().publicIPAllocationMethod("Static")
.idleTimeoutInMinutes(4).build();
String publicIpAddressName = "public-address-" + name;
PublicIPAddress ip = ipApi.createOrUpdate(publicIpAddressName, locationName, ImmutableMap.of("jclouds", name),
properties);
checkState(publicIpAvailable.create(azureGroup).apply(publicIpAddressName),
"Public IP was not provisioned in the configured timeout");
final NetworkInterfaceCardProperties.Builder networkInterfaceCardProperties = NetworkInterfaceCardProperties
.builder()
.ipConfigurations(
of(IpConfiguration
.builder()
.name("ipConfig-" + name)
.properties(
IpConfigurationProperties.builder().privateIPAllocationMethod("Dynamic")
.publicIPAddress(IdReference.create(ip.id())).subnet(IdReference.create(subnetId))
.build()).build()));
String securityGroup = getOnlyElement(options.getGroups(), null);
if (securityGroup != null) {
networkInterfaceCardProperties.networkSecurityGroup(IdReference.create(securityGroup));
}
String networkInterfaceCardName = "jc-nic-" + name;
return api.getNetworkInterfaceCardApi(azureGroup).createOrUpdate(networkInterfaceCardName, locationName,
networkInterfaceCardProperties.build(), ImmutableMap.of("jclouds", name));
}
private StorageProfile createStorageProfile(Image image, List<DataDisk> dataDisks) {
return StorageProfile.create(createImageReference(image), createOSDisk(image), dataDisks);
}
private ImageReference createImageReference(Image image) {
return isCustom(image.getId()) ? ImageReference.builder().customImageId(image.getProviderId()).build() : ImageReference
.builder().publisher(image.getProviderId()).offer(image.getName()).sku(image.getVersion())
.version("latest").build();
}
private OSDisk createOSDisk(Image image) {
OsFamily osFamily = image.getOperatingSystem().getFamily();
String osType = osFamily == OsFamily.WINDOWS ? "Windows" : "Linux";
return OSDisk.builder()
.osType(osType)
.caching(DataDisk.CachingTypes.READ_WRITE.toString())
.createOption(CreationData.CreateOptions.FROM_IMAGE.toString())
.managedDiskParameters(ManagedDiskParameters.create(null, StorageAccountType.STANDARD_LRS.toString()))
.build();
}
private IdReference getAvailabilitySetIdReference(AvailabilitySet availabilitySet) {
return availabilitySet != null ? IdReference.create(availabilitySet.id()) : null;
}
}