| /* |
| * 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; |
| } |
| |
| } |