JCLOUDS-1231: Implement the SecurityGroupExtension in ARM
Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/867ddef6 Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/867ddef6 Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/867ddef6 Branch: refs/heads/master Commit: 867ddef6e9355aac624bf0981e46de45bb42843c Parents: a91ff3b Author: Daniel Estevez <[email protected]> Authored: Tue Jan 17 19:13:14 2017 -0500 Committer: Ignasi Barrera <[email protected]> Committed: Thu Jan 26 21:58:23 2017 +0100 ---------------------------------------------------------------------- .../arm/compute/AzureComputeService.java | 82 +++-- .../arm/compute/AzureComputeServiceAdapter.java | 20 +- .../AzureComputeServiceContextModule.java | 145 ++++++--- .../domain/RegionAndIdAndIngressRules.java | 66 ++++ .../AzureComputeSecurityGroupExtension.java | 315 +++++++++++++++++++ .../NetworkSecurityGroupToSecurityGroup.java | 72 +++++ .../NetworkSecurityRuleToIpPermission.java | 76 +++++ .../functions/VirtualMachineToNodeMetadata.java | 4 +- .../loaders/CreateSecurityGroupIfNeeded.java | 97 ++++++ .../CreateResourceGroupThenCreateNodes.java | 112 +++++-- .../arm/domain/NetworkSecurityGroup.java | 17 +- .../azurecompute/arm/domain/RegionAndId.java | 2 +- .../arm/functions/CleanupResources.java | 66 +++- .../AzureComputeImageExtensionLiveTest.java | 1 + ...reComputeSecurityGroupExtensionLiveTest.java | 154 +++++++++ .../NetworkSecurityGroupApiMockTest.java | 2 +- .../internal/BaseAzureComputeApiLiveTest.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- 18 files changed, 1100 insertions(+), 135 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java index c215e37..a561375 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeService.java @@ -16,11 +16,12 @@ */ package org.jclouds.azurecompute.arm.compute; -import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; + import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -30,6 +31,8 @@ import javax.inject.Provider; import javax.inject.Singleton; import org.jclouds.Constants; +import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName; +import org.jclouds.azurecompute.arm.functions.CleanupResources; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.callables.RunScriptOnNode; @@ -54,51 +57,74 @@ import org.jclouds.compute.strategy.ResumeNodeStrategy; import org.jclouds.compute.strategy.SuspendNodeStrategy; import org.jclouds.domain.Credentials; import org.jclouds.domain.Location; -import org.jclouds.azurecompute.arm.functions.CleanupResources; import org.jclouds.scriptbuilder.functions.InitAdminAccess; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListeningExecutorService; @Singleton public class AzureComputeService extends BaseComputeService { - protected final CleanupResources cleanupResources; + private final CleanupResources cleanupResources; + private final LocationToResourceGroupName locationToResourceGroupName; @Inject protected AzureComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore, - @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes, - @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy, - GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy, - CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy, - DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy, - SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider, - @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider, - @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning, - @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated, - @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended, - InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, - RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, - PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, - @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, - CleanupResources cleanupResources, - Optional<ImageExtension> imageExtension, - Optional<SecurityGroupExtension> securityGroupExtension) { + @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes, + @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy, + GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy, + CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy, + DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy, + SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider, + @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider, + @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning, + @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended, + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, + RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, + PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, + @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, + CleanupResources cleanupResources, Optional<ImageExtension> imageExtension, + Optional<SecurityGroupExtension> securityGroupExtension, + LocationToResourceGroupName locationToResourceGroupName) { super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy, - getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, - startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, - nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, - persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension); - this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources"); - + getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, + startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, + nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, + persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension); + this.cleanupResources = cleanupResources; + this.locationToResourceGroupName = locationToResourceGroupName; } @Override protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) { + ImmutableMultimap.Builder<String, String> regionGroups = ImmutableMultimap.builder(); + ImmutableSet.Builder<String> resourceGroups = ImmutableSet.builder(); + for (NodeMetadata deadNode : deadNodes) { - cleanupResources.apply(deadNode.getId()); + String resourceGroup = locationToResourceGroupName.apply(deadNode.getLocation().getId()); + + resourceGroups.add(resourceGroup); + if (deadNode.getGroup() != null) { + regionGroups.put(resourceGroup, deadNode.getGroup()); + } + + try { + cleanupResources.cleanupNode(deadNode.getId()); + } catch (Exception ex) { + logger.warn(ex, "Error cleaning up resources for node %s", deadNode); + } + } + + for (Entry<String, String> regionGroup : regionGroups.build().entries()) { + cleanupResources.cleanupSecurityGroupIfOrphaned(regionGroup.getKey(), regionGroup.getValue()); } - } + for (String resourceGroup : resourceGroups.build()) { + cleanupResources.deleteResourceGroupIfEmpty(resourceGroup); + } + } } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java index 3b207b7..269f6b0 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/AzureComputeServiceAdapter.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.contains; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.find; +import static com.google.common.collect.Iterables.getOnlyElement; import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CONTAINER_NAME; import static org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension.CUSTOM_IMAGE_OFFER; import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId; @@ -81,6 +82,7 @@ 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; @@ -137,11 +139,10 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual // TODO ARM specific options // TODO network ids => create one nic in each network - // TODO inbound ports String locationName = template.getLocation().getId(); String subnetId = templateOptions.getSubnetId(); - NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, azureGroup); + NetworkInterfaceCard nic = createNetworkInterfaceCard(subnetId, name, locationName, azureGroup, template.getOptions()); StorageProfile storageProfile = createStorageProfile(name, template.getImage(), templateOptions.getBlob()); HardwareProfile hardwareProfile = HardwareProfile.builder().vmSize(template.getHardware().getId()).build(); OSProfile osProfile = createOsProfile(name, template); @@ -341,7 +342,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual @Override public void destroyNode(final String id) { - checkState(cleanupResources.apply(id), "server(%s) and its resources still there after deleting!?", id); + checkState(cleanupResources.cleanupNode(id), "server(%s) and its resources still there after deleting!?", id); } @Override @@ -405,7 +406,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual } private NetworkInterfaceCard createNetworkInterfaceCard(String subnetId, String name, String locationName, - String azureGroup) { + String azureGroup, TemplateOptions options) { final PublicIPAddressApi ipApi = api.getPublicIPAddressApi(azureGroup); PublicIPAddressProperties properties = PublicIPAddressProperties.builder().publicIPAllocationMethod("Static") @@ -418,7 +419,7 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual checkState(publicIpAvailable.create(azureGroup).apply(publicIpAddressName), "Public IP was not provisioned in the configured timeout"); - final NetworkInterfaceCardProperties networkInterfaceCardProperties = NetworkInterfaceCardProperties + final NetworkInterfaceCardProperties.Builder networkInterfaceCardProperties = NetworkInterfaceCardProperties .builder() .ipConfigurations( ImmutableList.of(IpConfiguration @@ -427,11 +428,16 @@ public class AzureComputeServiceAdapter implements ComputeServiceAdapter<Virtual .properties( IpConfigurationProperties.builder().privateIPAllocationMethod("Dynamic") .publicIPAddress(IdReference.create(ip.id())).subnet(IdReference.create(subnetId)) - .build()).build())).build(); + .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, ImmutableMap.of("jclouds", name)); + networkInterfaceCardProperties.build(), ImmutableMap.of("jclouds", name)); } private StorageProfile createStorageProfile(String name, Image image, String blob) { http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java index 34a5160..6a577eb 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzureComputeServiceContextModule.java @@ -42,15 +42,22 @@ import javax.inject.Singleton; import org.jclouds.azurecompute.arm.AzureComputeApi; import org.jclouds.azurecompute.arm.compute.AzureComputeService; import org.jclouds.azurecompute.arm.compute.AzureComputeServiceAdapter; +import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules; import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeImageExtension; +import org.jclouds.azurecompute.arm.compute.extensions.AzureComputeSecurityGroupExtension; import org.jclouds.azurecompute.arm.compute.functions.LocationToLocation; +import org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityGroupToSecurityGroup; +import org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityRuleToIpPermission; import org.jclouds.azurecompute.arm.compute.functions.ResourceDefinitionToCustomImage; import org.jclouds.azurecompute.arm.compute.functions.VMHardwareToHardware; import org.jclouds.azurecompute.arm.compute.functions.VMImageToImage; import org.jclouds.azurecompute.arm.compute.functions.VirtualMachineToNodeMetadata; +import org.jclouds.azurecompute.arm.compute.loaders.CreateSecurityGroupIfNeeded; import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.compute.strategy.CreateResourceGroupThenCreateNodes; import org.jclouds.azurecompute.arm.domain.Location; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; import org.jclouds.azurecompute.arm.domain.PublicIPAddress; import org.jclouds.azurecompute.arm.domain.ResourceDefinition; import org.jclouds.azurecompute.arm.domain.VMHardware; @@ -64,32 +71,38 @@ import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.config.ComputeServiceAdapterContextModule; import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.compute.extensions.ImageExtension; +import org.jclouds.compute.extensions.SecurityGroupExtension; import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatement; import org.jclouds.compute.functions.NodeAndTemplateOptionsToStatementWithoutPublicKey; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.reference.ComputeServiceConstants.PollPeriod; import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts; import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; +import org.jclouds.net.domain.IpPermission; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.inject.Inject; import com.google.inject.Provides; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; -public class AzureComputeServiceContextModule - extends ComputeServiceAdapterContextModule<VirtualMachine, VMHardware, VMImage, Location> { +public class AzureComputeServiceContextModule extends + ComputeServiceAdapterContextModule<VirtualMachine, VMHardware, VMImage, Location> { @Override protected void configure() { super.configure(); - + bind(new TypeLiteral<ComputeServiceAdapter<VirtualMachine, VMHardware, VMImage, Location>>() { }).to(AzureComputeServiceAdapter.class); - + bind(new TypeLiteral<Function<VMImage, org.jclouds.compute.domain.Image>>() { }).to(VMImageToImage.class); bind(new TypeLiteral<Function<VMHardware, Hardware>>() { @@ -98,19 +111,27 @@ public class AzureComputeServiceContextModule }).to(VirtualMachineToNodeMetadata.class); bind(new TypeLiteral<Function<Location, org.jclouds.domain.Location>>() { }).to(LocationToLocation.class); + bind(new TypeLiteral<Function<NetworkSecurityGroup, SecurityGroup>>() { + }).to(NetworkSecurityGroupToSecurityGroup.class); + bind(new TypeLiteral<Function<NetworkSecurityRule, IpPermission>>() { + }).to(NetworkSecurityRuleToIpPermission.class); bind(ComputeService.class).to(AzureComputeService.class); - + install(new LocationsFromComputeServiceAdapterModule<VirtualMachine, VMHardware, VMImage, Location>() { }); - + install(new FactoryModuleBuilder().build(ResourceDefinitionToCustomImage.Factory.class)); bind(TemplateOptions.class).to(AzureTemplateOptions.class); bind(NodeAndTemplateOptionsToStatement.class).to(NodeAndTemplateOptionsToStatementWithoutPublicKey.class); bind(CreateNodesInGroupThenAddToSet.class).to(CreateResourceGroupThenCreateNodes.class); - + bind(new TypeLiteral<CacheLoader<RegionAndIdAndIngressRules, String>>() { + }).to(CreateSecurityGroupIfNeeded.class); + bind(new TypeLiteral<ImageExtension>() { }).to(AzureComputeImageExtension.class); + bind(new TypeLiteral<SecurityGroupExtension>() { + }).to(AzureComputeSecurityGroupExtension.class); } @Singleton @@ -190,63 +211,82 @@ public class AzureComputeServiceContextModule } @Provides + @Singleton + protected final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap( + CacheLoader<RegionAndIdAndIngressRules, String> in) { + return CacheBuilder.newBuilder().build(in); + } + + @Provides @Named(TIMEOUT_NODE_RUNNING) protected VirtualMachineInStatePredicateFactory provideVirtualMachineRunningPredicate(final AzureComputeApi api, - Timeouts timeouts, PollPeriod pollPeriod) { + final Timeouts timeouts, final PollPeriod pollPeriod) { return new VirtualMachineInStatePredicateFactory(api, PowerState.RUNNING, timeouts.nodeRunning, pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); } - + @Provides @Named(TIMEOUT_NODE_TERMINATED) - protected Predicate<URI> provideNodeTerminatedPredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) { + protected Predicate<URI> provideNodeTerminatedPredicate(final AzureComputeApi api, final Timeouts timeouts, + final PollPeriod pollPeriod) { return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, - pollPeriod.pollMaxPeriod); + pollPeriod.pollMaxPeriod); } @Provides @Named(TIMEOUT_IMAGE_AVAILABLE) - protected Predicate<URI> provideImageAvailablePredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) { + protected Predicate<URI> provideImageAvailablePredicate(final AzureComputeApi api, final Timeouts timeouts, + final PollPeriod pollPeriod) { return retry(new ImageDonePredicate(api), timeouts.imageAvailable, pollPeriod.pollInitialPeriod, - pollPeriod.pollMaxPeriod); + pollPeriod.pollMaxPeriod); } @Provides @Named(TIMEOUT_RESOURCE_DELETED) - protected Predicate<URI> provideResourceDeletedPredicate(final AzureComputeApi api, Timeouts timeouts, PollPeriod pollPeriod) { + protected Predicate<URI> provideResourceDeletedPredicate(final AzureComputeApi api, final Timeouts timeouts, + final PollPeriod pollPeriod) { return retry(new ActionDonePredicate(api), timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, - pollPeriod.pollMaxPeriod); + pollPeriod.pollMaxPeriod); } @Provides @Named(TIMEOUT_NODE_SUSPENDED) protected VirtualMachineInStatePredicateFactory provideNodeSuspendedPredicate(final AzureComputeApi api, - Timeouts timeouts, PollPeriod pollPeriod) { + final Timeouts timeouts, final PollPeriod pollPeriod) { return new VirtualMachineInStatePredicateFactory(api, PowerState.STOPPED, timeouts.nodeTerminated, pollPeriod.pollInitialPeriod, pollPeriod.pollMaxPeriod); } - + @Provides protected PublicIpAvailablePredicateFactory providePublicIpAvailablePredicate(final AzureComputeApi api, - final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, Timeouts timeouts, - PollPeriod pollPeriod) { + final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts, + final PollPeriod pollPeriod) { return new PublicIpAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(), azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod()); } + @Provides + protected SecurityGroupAvailablePredicateFactory provideSecurityGroupAvailablePredicate(final AzureComputeApi api, + final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, final Timeouts timeouts, + final PollPeriod pollPeriod) { + return new SecurityGroupAvailablePredicateFactory(api, azureComputeConstants.operationTimeout(), + azureComputeConstants.operationPollInitialPeriod(), azureComputeConstants.operationPollMaxPeriod()); + } + @VisibleForTesting static class ActionDonePredicate implements Predicate<URI> { private final AzureComputeApi api; - public ActionDonePredicate(AzureComputeApi api) { + public ActionDonePredicate(final AzureComputeApi api) { this.api = checkNotNull(api, "api must not be null"); } @Override - public boolean apply(URI uri) { + public boolean apply(final URI uri) { checkNotNull(uri, "uri cannot be null"); - return (ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri)) || (ParseJobStatus.JobStatus.NO_CONTENT == api.getJobApi().jobStatus(uri)); + return ParseJobStatus.JobStatus.DONE == api.getJobApi().jobStatus(uri) + || ParseJobStatus.JobStatus.NO_CONTENT == api.getJobApi().jobStatus(uri); } } @@ -256,14 +296,16 @@ public class AzureComputeServiceContextModule private final AzureComputeApi api; - public ImageDonePredicate(AzureComputeApi api) { + public ImageDonePredicate(final AzureComputeApi api) { this.api = checkNotNull(api, "api must not be null"); } @Override - public boolean apply(URI uri) { + public boolean apply(final URI uri) { checkNotNull(uri, "uri cannot be null"); - if (api.getJobApi().jobStatus(uri) != ParseJobStatus.JobStatus.DONE) return false; + if (api.getJobApi().jobStatus(uri) != ParseJobStatus.JobStatus.DONE) { + return false; + } List<ResourceDefinition> definitions = api.getJobApi().captureStatus(uri); return definitions != null; } @@ -277,8 +319,8 @@ public class AzureComputeServiceContextModule private final long period; private final long maxPeriod; - VirtualMachineInStatePredicateFactory(AzureComputeApi api, PowerState powerState, long timeout, - long period, long maxPeriod) { + VirtualMachineInStatePredicateFactory(final AzureComputeApi api, final PowerState powerState, final long timeout, + final long period, final long maxPeriod) { this.api = checkNotNull(api, "api cannot be null"); this.powerState = checkNotNull(powerState, "powerState cannot be null"); this.timeout = timeout; @@ -289,17 +331,18 @@ public class AzureComputeServiceContextModule public Predicate<String> create(final String azureGroup) { return retry(new Predicate<String>() { @Override - public boolean apply(String name) { + public boolean apply(final String name) { checkNotNull(name, "name cannot be null"); VirtualMachineInstance vmInstance = api.getVirtualMachineApi(azureGroup).getInstanceDetails(name); - if (vmInstance == null) + if (vmInstance == null) { return false; + } return powerState == vmInstance.powerState(); } }, timeout, period, maxPeriod); } } - + public static class PublicIpAvailablePredicateFactory { private final AzureComputeApi api; @@ -307,25 +350,57 @@ public class AzureComputeServiceContextModule private final long period; private final long maxPeriod; - PublicIpAvailablePredicateFactory(AzureComputeApi api, long timeout, - long period, long maxPeriod) { + PublicIpAvailablePredicateFactory(final AzureComputeApi api, final long timeout, final long period, + final long maxPeriod) { this.api = checkNotNull(api, "api cannot be null"); this.timeout = timeout; this.period = period; this.maxPeriod = maxPeriod; } - + public Predicate<String> create(final String azureGroup) { return retry(new Predicate<String>() { @Override - public boolean apply(String name) { + public boolean apply(final String name) { checkNotNull(name, "name cannot be null"); PublicIPAddress publicIp = api.getPublicIPAddressApi(azureGroup).get(name); - if (publicIp == null) return false; + if (publicIp == null) { + return false; + } return publicIp.properties().provisioningState().equalsIgnoreCase("Succeeded"); } }, timeout, period, maxPeriod); } } + public static class SecurityGroupAvailablePredicateFactory { + private final AzureComputeApi api; + private final long timeout; + private final long period; + private final long maxPeriod; + + SecurityGroupAvailablePredicateFactory(final AzureComputeApi api, final long timeout, final long period, + final long maxPeriod) { + this.api = checkNotNull(api, "api cannot be null"); + this.timeout = timeout; + this.period = period; + this.maxPeriod = maxPeriod; + } + + public Predicate<String> create(final String resourceGroup) { + checkNotNull(resourceGroup, "resourceGroup cannot be null"); + return retry(new Predicate<String>() { + @Override + public boolean apply(final String name) { + checkNotNull(name, "name cannot be null"); + NetworkSecurityGroup sg = api.getNetworkSecurityGroupApi(resourceGroup).get(name); + if (sg == null) { + return false; + } + return sg.properties().provisioningState().equalsIgnoreCase("Succeeded"); + } + }, timeout, period, maxPeriod); + } + } + } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java new file mode 100644 index 0000000..fa9730d --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/domain/RegionAndIdAndIngressRules.java @@ -0,0 +1,66 @@ +/* + * 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.domain; + +import org.jclouds.azurecompute.arm.domain.RegionAndId; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Objects; + +@AutoValue +public abstract class RegionAndIdAndIngressRules { + + abstract RegionAndId regionAndId(); // Intentionally hidden + public abstract int[] inboundPorts(); + + RegionAndIdAndIngressRules() { + + } + + public static RegionAndIdAndIngressRules create(String region, String id, int[] inboundPorts) { + return new AutoValue_RegionAndIdAndIngressRules(RegionAndId.fromRegionAndId(region, id), inboundPorts); + } + + public String id() { + return regionAndId().id(); + } + + public String region() { + return regionAndId().region(); + } + + // Intentionally delegate equals and hashcode to the fields in the parent + // class so that we can search only by region/id in a map + + @Override + public int hashCode() { + return Objects.hashCode(region(), id()); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof RegionAndId)) { + return false; + } + RegionAndId that = (RegionAndId) obj; + return Objects.equal(region(), that.region()) && Objects.equal(id(), that.id()); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java new file mode 100644 index 0000000..12d140b --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeSecurityGroupExtension.java @@ -0,0 +1,315 @@ +/* + * 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.extensions; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Predicates.notNull; +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.transform; +import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.azurecompute.arm.AzureComputeApi; +import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule.SecurityGroupAvailablePredicateFactory; +import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName; +import org.jclouds.azurecompute.arm.domain.IdReference; +import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol; +import org.jclouds.azurecompute.arm.domain.RegionAndId; +import org.jclouds.azurecompute.arm.domain.VirtualMachine; +import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi; +import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.domain.SecurityGroupBuilder; +import org.jclouds.compute.extensions.SecurityGroupExtension; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.domain.Location; +import org.jclouds.logging.Logger; +import org.jclouds.net.domain.IpPermission; +import org.jclouds.net.domain.IpProtocol; + +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.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; + +public class AzureComputeSecurityGroupExtension implements SecurityGroupExtension { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final AzureComputeApi api; + private final Function<NetworkSecurityGroup, SecurityGroup> securityGroupConverter; + private final LocationToResourceGroupName locationToResourceGroupName; + private final Supplier<Set<? extends Location>> locations; + private final SecurityGroupAvailablePredicateFactory securityGroupAvailable; + private final Predicate<URI> resourceDeleted; + + @Inject + AzureComputeSecurityGroupExtension(AzureComputeApi api, @Memoized Supplier<Set<? extends Location>> locations, + LocationToResourceGroupName locationToResourceGroupName, + Function<NetworkSecurityGroup, SecurityGroup> groupConverter, + SecurityGroupAvailablePredicateFactory securityRuleAvailable, + @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted) { + this.api = api; + this.locations = locations; + this.securityGroupConverter = groupConverter; + this.locationToResourceGroupName = locationToResourceGroupName; + this.securityGroupAvailable = securityRuleAvailable; + this.resourceDeleted = resourceDeleted; + } + + @Override + public Set<SecurityGroup> listSecurityGroups() { + return ImmutableSet.copyOf(concat(transform(locations.get(), new Function<Location, Set<SecurityGroup>>() { + @Override + public Set<SecurityGroup> apply(Location input) { + return listSecurityGroupsInLocation(input); + } + }))); + } + + @Override + public Set<SecurityGroup> listSecurityGroupsInLocation(Location location) { + logger.debug(">> getting security groups for %s...", location); + final String resourcegroup = locationToResourceGroupName.apply(location.getId()); + List<NetworkSecurityGroup> networkGroups = api.getNetworkSecurityGroupApi(resourcegroup).list(); + return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter)); + } + + @Override + public Set<SecurityGroup> listSecurityGroupsForNode(String nodeId) { + logger.debug(">> getting security groups for node %s...", nodeId); + + final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(nodeId); + final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region()); + + VirtualMachine vm = api.getVirtualMachineApi(resourceGroup).get(regionAndId.id()); + List<IdReference> networkInterfacesIdReferences = vm.properties().networkProfile().networkInterfaces(); + List<NetworkSecurityGroup> networkGroups = new ArrayList<NetworkSecurityGroup>(); + + for (IdReference networkInterfaceCardIdReference : networkInterfacesIdReferences) { + String nicName = Iterables.getLast(Splitter.on("/").split(networkInterfaceCardIdReference.id())); + NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(resourceGroup).get(nicName); + if (card != null && card.properties().networkSecurityGroup() != null) { + String secGroupName = Iterables.getLast(Splitter.on("/").split( + card.properties().networkSecurityGroup().id())); + NetworkSecurityGroup group = api.getNetworkSecurityGroupApi(resourceGroup).get(secGroupName); + networkGroups.add(group); + } + } + + return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter)); + } + + @Override + public SecurityGroup getSecurityGroupById(String id) { + logger.debug(">> getting security group %s...", id); + final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); + final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region()); + + return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).get(regionAndId.id())); + } + + @Override + public SecurityGroup createSecurityGroup(String name, Location location) { + final String resourceGroup = locationToResourceGroupName.apply(location.getId()); + + logger.debug(">> creating security group %s in %s...", name, location); + + SecurityGroupBuilder builder = new SecurityGroupBuilder(); + builder.name(name); + builder.location(location); + + return securityGroupConverter.apply(api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name, + location.getId(), null, NetworkSecurityGroupProperties.builder().build())); + } + + @Override + public boolean removeSecurityGroup(String id) { + logger.debug(">> deleting security group %s...", id); + + final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); + final String resourcegroup = locationToResourceGroupName.apply(regionAndId.region()); + URI uri = api.getNetworkSecurityGroupApi(resourcegroup).delete(regionAndId.id()); + return resourceDeleted.apply(uri); + } + + @Override + public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) { + return addIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(), + ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group); + } + + @Override + public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group) { + return removeIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(), + ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group); + } + + @Override + public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort, + Multimap<String, String> tenantIdGroupNamePairs, Iterable<String> ipRanges, Iterable<String> groupIds, + SecurityGroup group) { + String portRange = startPort + "-" + endPort; + String ruleName = protocol + "-" + portRange; + + logger.debug(">> adding ip permission [%s] to %s...", ruleName, group.getName()); + + // TODO: Support Azure network tags somehow? + + final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId()); + final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region()); + + NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup); + NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id()); + + if (networkSecurityGroup == null) { + throw new IllegalArgumentException("Security group " + group.getName() + " was not found"); + } + + NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name()); + int nextPriority = getRuleStartingPriority(ruleApi); + + for (String ipRange : ipRanges) { + NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder() + .protocol(Protocol.fromValue(protocol.name())) // + .sourceAddressPrefix(ipRange) // + .sourcePortRange("*") // + .destinationAddressPrefix("*") // + .destinationPortRange(portRange) // + .direction(Direction.Inbound) // + .access(Access.Allow) // + .priority(nextPriority++) // + .build(); + + logger.debug(">> creating network security rule %s for %s...", ruleName, ipRange); + + ruleApi.createOrUpdate(ruleName, properties); + + checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()), + "Security group was not updated in the configured timeout"); + } + + return getSecurityGroupById(group.getId()); + } + + @Override + public SecurityGroup removeIpPermission(final IpProtocol protocol, int startPort, int endPort, + Multimap<String, String> tenantIdGroupNamePairs, final Iterable<String> ipRanges, Iterable<String> groupIds, + SecurityGroup group) { + final String portRange = startPort + "-" + endPort; + String ruleName = protocol + "-" + portRange; + + logger.debug(">> deleting ip permissions matching [%s] from %s...", ruleName, group.getName()); + + final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(group.getId()); + final String resourceGroup = locationToResourceGroupName.apply(regionAndId.region()); + + NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroup); + NetworkSecurityGroup networkSecurityGroup = groupApi.get(regionAndId.id()); + + if (networkSecurityGroup == null) { + throw new IllegalArgumentException("Security group " + group.getName() + " was not found"); + } + + NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroup, networkSecurityGroup.name()); + Iterable<NetworkSecurityRule> rules = filter(ruleApi.list(), new Predicate<NetworkSecurityRule>() { + @Override + public boolean apply(NetworkSecurityRule input) { + NetworkSecurityRuleProperties props = input.properties(); + return Objects.equal(portRange, props.destinationPortRange()) + && Objects.equal(Protocol.fromValue(protocol.name()), props.protocol()) + && Objects.equal(Direction.Inbound, props.direction()) // + && Objects.equal(Access.Allow, props.access()) + && any(ipRanges, equalTo(props.sourceAddressPrefix().replace("*", "0.0.0.0/0"))); + } + }); + + for (NetworkSecurityRule matchingRule : rules) { + logger.debug(">> deleting network security rule %s from %s...", matchingRule.name(), group.getName()); + ruleApi.delete(matchingRule.name()); + checkState(securityGroupAvailable.create(resourceGroup).apply(networkSecurityGroup.name()), + "Security group was not updated in the configured timeout"); + } + + return getSecurityGroupById(group.getId()); + } + + @Override + public boolean supportsTenantIdGroupNamePairs() { + return false; + } + + @Override + public boolean supportsTenantIdGroupIdPairs() { + return false; + } + + @Override + public boolean supportsGroupIds() { + return false; + } + + @Override + public boolean supportsPortRangesForGroups() { + return false; + } + + @Override + public boolean supportsExclusionCidrBlocks() { + return false; + } + + private int getRuleStartingPriority(NetworkSecurityRuleApi ruleApi) { + List<NetworkSecurityRule> existingRules = ruleApi.list(); + return existingRules.isEmpty() ? 100 : rulesByPriority().max(existingRules).properties().priority() + 1; + } + + private static Ordering<NetworkSecurityRule> rulesByPriority() { + return new Ordering<NetworkSecurityRule>() { + @Override + public int compare(NetworkSecurityRule left, NetworkSecurityRule right) { + return left.properties().priority() - right.properties().priority(); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java new file mode 100644 index 0000000..65f5b0d --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityGroupToSecurityGroup.java @@ -0,0 +1,72 @@ +/* + * 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.functions; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; +import static org.jclouds.azurecompute.arm.compute.functions.NetworkSecurityRuleToIpPermission.InboundRule; +import static org.jclouds.azurecompute.arm.compute.functions.VirtualMachineToNodeMetadata.getLocation; + +import java.util.Set; + +import javax.inject.Singleton; + +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; +import org.jclouds.azurecompute.arm.domain.RegionAndId; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.domain.SecurityGroupBuilder; +import org.jclouds.domain.Location; +import org.jclouds.net.domain.IpPermission; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.inject.Inject; + +@Singleton +public class NetworkSecurityGroupToSecurityGroup implements Function<NetworkSecurityGroup, SecurityGroup> { + private final Function<NetworkSecurityRule, IpPermission> ruleToPermission; + private final Supplier<Set<? extends Location>> locations; + + @Inject + NetworkSecurityGroupToSecurityGroup(Function<NetworkSecurityRule, IpPermission> ruleToPermission, + @Memoized Supplier<Set<? extends Location>> locations) { + this.ruleToPermission = ruleToPermission; + this.locations = locations; + } + + @Override + public SecurityGroup apply(NetworkSecurityGroup input) { + SecurityGroupBuilder builder = new SecurityGroupBuilder(); + + builder.id(RegionAndId.fromRegionAndId(input.location(), input.name()).slashEncode()); + builder.providerId(input.properties().resourceGuid()); + builder.name(input.name()); + builder.location(getLocation(locations, input.location())); + + if (input.properties().securityRules() != null) { + // We just supoprt security groups that allow traffic to a set of + // targets. We don't support deny rules or origin based rules in the + // security group api. + builder.ipPermissions(transform(filter(input.properties().securityRules(), InboundRule), ruleToPermission)); + } + + return builder.build(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java new file mode 100644 index 0000000..e601d59 --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/NetworkSecurityRuleToIpPermission.java @@ -0,0 +1,76 @@ +/* + * 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.functions; + +import javax.annotation.Resource; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.logging.Logger; +import org.jclouds.net.domain.IpPermission; +import org.jclouds.net.domain.IpProtocol; +import org.jclouds.net.util.IpPermissions; +import org.jclouds.net.util.IpPermissions.PortSelection; +import org.jclouds.net.util.IpPermissions.ToSourceSelection; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; + +@Singleton +public class NetworkSecurityRuleToIpPermission implements Function<NetworkSecurityRule, IpPermission> { + + public static final Predicate<NetworkSecurityRule> InboundRule = new Predicate<NetworkSecurityRule>() { + @Override + public boolean apply(NetworkSecurityRule input) { + return Direction.Inbound.equals(input.properties().direction()) + && Access.Allow.equals(input.properties().access()); + } + }; + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + @Override + public IpPermission apply(final NetworkSecurityRule rule) { + if (!InboundRule.apply(rule)) { + logger.warn(">> ignoring non-inbound networks ecurity rule %s...", rule.name()); + return null; + } + + IpPermission permissions = IpPermissions.permit(IpProtocol.fromValue(rule.properties().protocol().name())); + + String portRange = rule.properties().destinationPortRange(); + if (!"*".equals(portRange)) { + String[] range = portRange.split("-"); + permissions = PortSelection.class.cast(permissions).fromPort(Integer.parseInt(range[0])) + .to(Integer.parseInt(range[1])); + } + + if (!"*".equals(rule.properties().sourceAddressPrefix())) { + permissions = ToSourceSelection.class.cast(permissions).originatingFromCidrBlock( + rule.properties().sourceAddressPrefix()); + } + + return permissions; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java index 5e98fbf..22c818c 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/functions/VirtualMachineToNodeMetadata.java @@ -191,7 +191,7 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No .name())); String locationName = virtualMachine.location(); - builder.location(getLocation(locationName)); + builder.location(getLocation(locations, locationName)); Optional<? extends Image> image = findImage(virtualMachine.properties().storageProfile(), locationName, azureGroup); if (image.isPresent()) { @@ -251,7 +251,7 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No return publicIpAddresses; } - protected Location getLocation(final String locationName) { + protected static Location getLocation(Supplier<Set<? extends Location>> locations, final String locationName) { return find(locations.get(), new Predicate<Location>() { @Override public boolean apply(Location location) { http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java new file mode 100644 index 0000000..efb8abc --- /dev/null +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/loaders/CreateSecurityGroupIfNeeded.java @@ -0,0 +1,97 @@ +/* + * 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.loaders; + +import static org.jclouds.compute.util.ComputeServiceUtils.getPortRangesFromList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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.domain.RegionAndIdAndIngressRules; +import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.logging.Logger; + +import com.google.common.cache.CacheLoader; + +@Singleton +public class CreateSecurityGroupIfNeeded extends CacheLoader<RegionAndIdAndIngressRules, String> { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final AzureComputeApi api; + private final LocationToResourceGroupName locationToResourceGroupName; + + @Inject + CreateSecurityGroupIfNeeded(AzureComputeApi api, LocationToResourceGroupName locationToResourceGroupName) { + this.api = api; + this.locationToResourceGroupName = locationToResourceGroupName; + } + + @Override + public String load(RegionAndIdAndIngressRules key) throws Exception { + String resourceGroup = locationToResourceGroupName.apply(key.region()); + return createSecurityGroup(key.region(), resourceGroup, key.id(), key.inboundPorts()); + } + + private String createSecurityGroup(String location, String resourceGroup, String name, int[] inboundPorts) { + logger.debug(">> creating security group %s in %s...", name, location); + + Map<Integer, Integer> portRanges = getPortRangesFromList(inboundPorts); + + List<NetworkSecurityRule> rules = new ArrayList<NetworkSecurityRule>(); + + int startPriority = 100; + for (Map.Entry<Integer, Integer> portRange : portRanges.entrySet()) { + String range = portRange.getKey() + "-" + portRange.getValue(); + String ruleName = "tcp-" + range; + + NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder().protocol(Protocol.Tcp) // + .sourceAddressPrefix("*") // + .sourcePortRange("*") // + .destinationAddressPrefix("*") // + .destinationPortRange(range) // + .direction(Direction.Inbound) // + .access(Access.Allow) // + .priority(startPriority++) // + .build(); + + rules.add(NetworkSecurityRule.create(ruleName, null, null, properties)); + } + + NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup).createOrUpdate(name, location, + null, NetworkSecurityGroupProperties.builder().securityRules(rules).build()); + + return securityGroup.id(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java index 5ce30b0..524eb69 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/strategy/CreateResourceGroupThenCreateNodes.java @@ -16,8 +16,10 @@ */ package org.jclouds.azurecompute.arm.compute.strategy; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; import static org.jclouds.azurecompute.arm.compute.functions.VMImageToImage.decodeFieldsFromUniqueId; import static org.jclouds.util.Predicates2.retry; @@ -35,8 +37,11 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.azurecompute.arm.AzureComputeApi; import org.jclouds.azurecompute.arm.compute.config.AzureComputeServiceContextModule; +import org.jclouds.azurecompute.arm.compute.domain.RegionAndIdAndIngressRules; import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupName; import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; +import org.jclouds.azurecompute.arm.domain.RegionAndId; import org.jclouds.azurecompute.arm.domain.ResourceGroup; import org.jclouds.azurecompute.arm.domain.StorageService; import org.jclouds.azurecompute.arm.domain.Subnet; @@ -51,15 +56,18 @@ import org.jclouds.compute.domain.Image; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; import org.jclouds.compute.strategy.ListNodesStrategy; import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet; +import org.jclouds.domain.Location; import org.jclouds.logging.Logger; import com.google.common.base.Predicate; import com.google.common.base.Strings; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.ListenableFuture; @@ -75,34 +83,37 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco private final AzureComputeApi api; private final AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants; private final LocationToResourceGroupName locationToResourceGroupName; + private final LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap; @Inject protected CreateResourceGroupThenCreateNodes( - CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy, - ListNodesStrategy listNodesStrategy, - GroupNamingConvention.Factory namingConvention, + CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy, + ListNodesStrategy listNodesStrategy, + GroupNamingConvention.Factory namingConvention, @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, AzureComputeApi api, AzureComputeServiceContextModule.AzureComputeConstants azureComputeConstants, - LocationToResourceGroupName locationToResourceGroupName) { + LocationToResourceGroupName locationToResourceGroupName, + LoadingCache<RegionAndIdAndIngressRules, String> securityGroupMap) { super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor, - customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); + customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); this.api = checkNotNull(api, "api cannot be null"); checkNotNull(userExecutor, "userExecutor cannot be null"); this.azureComputeConstants = azureComputeConstants; this.locationToResourceGroupName = locationToResourceGroupName; + this.securityGroupMap = securityGroupMap; } @Override public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template, - Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes, - Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { + Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes, + Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { // If there is a script to be run on the node and public key // authentication has been configured, warn users if the private key // is not present if (hasRunScriptWithKeyAuthAndNoPrivateKey(template)) { - logger.warn(">> a runScript was configured but no SSH key has been provided. " + - "Authentication will delegate to the ssh-agent"); + logger.warn(">> a runScript was configured but no SSH key has been provided. " + + "Authentication will delegate to the ssh-agent"); } String azureGroupName = locationToResourceGroupName.apply(template.getLocation().getId()); @@ -112,7 +123,7 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco ResourceGroup resourceGroup = resourceGroupApi.get(azureGroupName); final String location = template.getLocation().getId(); - if (resourceGroup == null){ + if (resourceGroup == null) { final Map<String, String> tags = ImmutableMap.of("description", "jclouds managed VMs"); resourceGroupApi.create(azureGroupName, location, tags).name(); } @@ -125,33 +136,35 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco } this.getOrCreateVirtualNetworkWithSubnet(vnetName, subnetName, location, options, azureGroupName); + configureSecurityGroupForOptions(group, azureGroupName, template.getLocation(), options); StorageService storageService = getOrCreateStorageService(group, azureGroupName, location, template.getImage()); String blob = storageService.storageServiceProperties().primaryEndpoints().get("blob"); options.blob(blob); - + Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes, - customizationResponses); + customizationResponses); return responses; } - protected synchronized void getOrCreateVirtualNetworkWithSubnet( - final String virtualNetworkName, final String subnetName, final String location, - AzureTemplateOptions options, final String azureGroupName) { + protected synchronized void getOrCreateVirtualNetworkWithSubnet(final String virtualNetworkName, + final String subnetName, final String location, AzureTemplateOptions options, final String azureGroupName) { - //Subnets belong to a virtual network so that needs to be created first + // Subnets belong to a virtual network so that needs to be created first VirtualNetworkApi vnApi = api.getVirtualNetworkApi(azureGroupName); VirtualNetwork vn = vnApi.get(virtualNetworkName); if (vn == null) { - VirtualNetwork.VirtualNetworkProperties virtualNetworkProperties = VirtualNetwork.VirtualNetworkProperties.builder() - .addressSpace(VirtualNetwork.AddressSpace.create(Arrays.asList(this.azureComputeConstants.azureDefaultVnetAddressPrefixProperty()))) - .subnets( - Arrays.asList( - Subnet.create(subnetName, null, null, - Subnet.SubnetProperties.builder().addressPrefix(this.azureComputeConstants.azureDefaultSubnetAddressPrefixProperty()).build()))) - .build(); + VirtualNetwork.VirtualNetworkProperties virtualNetworkProperties = VirtualNetwork.VirtualNetworkProperties + .builder() + .addressSpace( + VirtualNetwork.AddressSpace.create(Arrays.asList(this.azureComputeConstants + .azureDefaultVnetAddressPrefixProperty()))) + .subnets( + Arrays.asList(Subnet.create(subnetName, null, null, Subnet.SubnetProperties.builder() + .addressPrefix(this.azureComputeConstants.azureDefaultSubnetAddressPrefixProperty()).build()))) + .build(); vn = vnApi.createOrUpdate(virtualNetworkName, location, virtualNetworkProperties); } @@ -164,10 +177,11 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco private static boolean hasRunScriptWithKeyAuthAndNoPrivateKey(Template template) { return template.getOptions().getRunScript() != null && template.getOptions().getPublicKey() != null - && !template.getOptions().hasLoginPrivateKeyOption(); + && !template.getOptions().hasLoginPrivateKeyOption(); } - public StorageService getOrCreateStorageService(String name, String resourceGroupName, String locationName, Image image) { + public StorageService getOrCreateStorageService(String name, String resourceGroupName, String locationName, + Image image) { String storageAccountName = null; VMImage imageRef = decodeFieldsFromUniqueId(image.getId()); if (imageRef.custom()) { @@ -179,10 +193,12 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco } StorageService storageService = api.getStorageAccountApi(resourceGroupName).get(storageAccountName); - if (storageService != null) return storageService; + if (storageService != null) + return storageService; - URI uri = api.getStorageAccountApi(resourceGroupName).create(storageAccountName, locationName, ImmutableMap.of("jclouds", - name), ImmutableMap.of("accountType", StorageService.AccountType.Standard_LRS.toString())); + URI uri = api.getStorageAccountApi(resourceGroupName).create(storageAccountName, locationName, + ImmutableMap.of("jclouds", name), + ImmutableMap.of("accountType", StorageService.AccountType.Standard_LRS.toString())); boolean starageAccountCreated = retry(new Predicate<URI>() { @Override public boolean apply(URI uri) { @@ -195,16 +211,44 @@ public class CreateResourceGroupThenCreateNodes extends CreateNodesWithGroupEnco return api.getStorageAccountApi(resourceGroupName).get(storageAccountName); } + private void configureSecurityGroupForOptions(String group, String resourceGroup, Location location, + TemplateOptions options) { + + checkArgument(options.getGroups().size() <= 1, + "Only one security group can be configured for each network interface"); + + if (!options.getGroups().isEmpty()) { + String groupName = getOnlyElement(options.getGroups()); + String groupNameWithourRegion = groupName.indexOf('/') == -1 ? groupName : RegionAndId.fromSlashEncoded( + groupName).id(); + NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroup).get(groupNameWithourRegion); + checkArgument(securityGroup != null, "Security group %s was not found", groupName); + options.securityGroups(securityGroup.id()); + } else if (options.getInboundPorts().length > 0) { + String name = namingConvention.create().sharedNameForGroup(group); + RegionAndIdAndIngressRules regionAndIdAndIngressRules = RegionAndIdAndIngressRules.create(location.getId(), + name, options.getInboundPorts()); + // this will create if not yet exists. + String securityGroupId = securityGroupMap.getUnchecked(regionAndIdAndIngressRules); + options.securityGroups(securityGroupId); + } + } + /** * Generates a valid storage account * - * Storage account names must be between 3 and 24 characters in length and may contain numbers and lowercase letters only. + * Storage account names must be between 3 and 24 characters in length and + * may contain numbers and lowercase letters only. * - * @param name the node name - * @return the storage account name starting from a sanitized name (with only numbers and lowercase letters only ). - * If sanitized name is between 3 and 24 characters, storage account name is equals to sanitized name. - * If sanitized name is less than 3 characters, storage account is sanitized name plus 4 random chars. - * If sanitized name is more than 24 characters, storage account is first 10 chars of sanitized name plus 4 random chars plus last 10 chars of sanitized name. + * @param name + * the node name + * @return the storage account name starting from a sanitized name (with only + * numbers and lowercase letters only ). If sanitized name is between + * 3 and 24 characters, storage account name is equals to sanitized + * name. If sanitized name is less than 3 characters, storage account + * is sanitized name plus 4 random chars. If sanitized name is more + * than 24 characters, storage account is first 10 chars of sanitized + * name plus 4 random chars plus last 10 chars of sanitized name. */ public static String generateStorageAccountName(String name) { String random = UUID.randomUUID().toString().substring(0, 4); http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java index ebe842e..e7c75e8 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/NetworkSecurityGroup.java @@ -25,7 +25,7 @@ import java.util.Map; @AutoValue public abstract class NetworkSecurityGroup { - @Nullable + public abstract String id(); public abstract String name(); @Nullable @@ -40,15 +40,10 @@ public abstract class NetworkSecurityGroup { @Nullable public abstract String etag(); - @SerializedNames({"name", "location", "tags", "properties", "etag"}) - public static NetworkSecurityGroup create(final String name, - final String location, - final Map<String, String> tags, - final NetworkSecurityGroupProperties properties, - final String etag) { - return new AutoValue_NetworkSecurityGroup(name, location, - (tags == null) ? null : ImmutableMap.copyOf(tags), - properties, etag); + @SerializedNames({ "id", "name", "location", "tags", "properties", "etag" }) + public static NetworkSecurityGroup create(final String id, final String name, final String location, + final Map<String, String> tags, final NetworkSecurityGroupProperties properties, final String etag) { + return new AutoValue_NetworkSecurityGroup(id, name, location, (tags == null) ? null : ImmutableMap.copyOf(tags), + properties, etag); } } - http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java index 233109a..4105ee3 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/RegionAndId.java @@ -29,7 +29,7 @@ public abstract class RegionAndId { public abstract String region(); public abstract String id(); - RegionAndId() { + protected RegionAndId() { } http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java index ead676a..9d2a1c1 100644 --- a/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java +++ b/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/functions/CleanupResources.java @@ -35,10 +35,13 @@ import org.jclouds.azurecompute.arm.compute.functions.LocationToResourceGroupNam import org.jclouds.azurecompute.arm.domain.IdReference; import org.jclouds.azurecompute.arm.domain.IpConfiguration; import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard; +import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; import org.jclouds.azurecompute.arm.domain.RegionAndId; import org.jclouds.azurecompute.arm.domain.StorageServiceKeys; import org.jclouds.azurecompute.arm.domain.VirtualMachine; +import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi; import org.jclouds.azurecompute.arm.util.BlobHelper; +import org.jclouds.compute.functions.GroupNamingConvention; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; @@ -49,7 +52,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @Singleton -public class CleanupResources implements Function<String, Boolean> { +public class CleanupResources { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) @@ -59,22 +62,23 @@ public class CleanupResources implements Function<String, Boolean> { private final Predicate<URI> resourceDeleted; private final StorageProfileToStorageAccountName storageProfileToStorageAccountName; private final LocationToResourceGroupName locationToResourceGroupName; + private final GroupNamingConvention.Factory namingConvention; @Inject CleanupResources(AzureComputeApi azureComputeApi, @Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted, StorageProfileToStorageAccountName storageProfileToStorageAccountName, - LocationToResourceGroupName locationToResourceGroupName) { + LocationToResourceGroupName locationToResourceGroupName, GroupNamingConvention.Factory namingConvention) { this.api = azureComputeApi; this.resourceDeleted = resourceDeleted; this.storageProfileToStorageAccountName = storageProfileToStorageAccountName; this.locationToResourceGroupName = locationToResourceGroupName; + this.namingConvention = namingConvention; } - @Override - public Boolean apply(final String id) { + public boolean cleanupNode(final String id) { RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); String group = locationToResourceGroupName.apply(regionAndId.region()); - + VirtualMachine virtualMachine = api.getVirtualMachineApi(group).get(regionAndId.id()); if (virtualMachine == null) { return true; @@ -82,10 +86,17 @@ public class CleanupResources implements Function<String, Boolean> { logger.debug(">> destroying %s ...", regionAndId.slashEncode()); boolean vmDeleted = deleteVirtualMachine(group, virtualMachine); - + // We don't delete the network here, as it is global to the resource // group. It will be deleted when the resource group is deleted + cleanupVirtualMachineNICs(group, virtualMachine); + cleanupVirtualMachineStorage(group, virtualMachine); + + return vmDeleted; + } + + public void cleanupVirtualMachineNICs(String group, VirtualMachine virtualMachine) { for (String nicName : getNetworkCardInterfaceNames(virtualMachine)) { NetworkInterfaceCard nic = api.getNetworkInterfaceCardApi(group).get(nicName); Iterable<String> publicIps = getPublicIps(group, nic); @@ -99,8 +110,11 @@ public class CleanupResources implements Function<String, Boolean> { api.getPublicIPAddressApi(group).delete(publicIp); } } + } - String storageAccountName = storageProfileToStorageAccountName.apply(virtualMachine.properties().storageProfile()); + public void cleanupVirtualMachineStorage(String group, VirtualMachine virtualMachine) { + String storageAccountName = storageProfileToStorageAccountName + .apply(virtualMachine.properties().storageProfile()); StorageServiceKeys keys = api.getStorageAccountApi(group).getKeys(storageAccountName); // Remove the virtual machine files @@ -118,20 +132,44 @@ public class CleanupResources implements Function<String, Boolean> { } finally { closeQuietly(blobHelper); } + } - deleteResourceGroupIfEmpty(group); + public boolean cleanupSecurityGroupIfOrphaned(String resourceGroup, String group) { + String name = namingConvention.create().sharedNameForGroup(group); + NetworkSecurityGroupApi sgapi = api.getNetworkSecurityGroupApi(resourceGroup); - return vmDeleted; + boolean deleted = false; + + try { + NetworkSecurityGroup securityGroup = sgapi.get(name); + if (securityGroup != null) { + List<NetworkInterfaceCard> nics = securityGroup.properties().networkInterfaces(); + if (nics == null || nics.isEmpty()) { + logger.debug(">> deleting orphaned security group %s from %s...", name, resourceGroup); + try { + deleted = resourceDeleted.apply(sgapi.delete(name)); + } catch (Exception ex) { + logger.warn(ex, ">> error deleting orphaned security group %s from %s...", name, resourceGroup); + } + } + } + } catch (Exception ex) { + logger.warn(ex, "Error deleting security groups for %s and group %s", resourceGroup, group); + } + + return deleted; } - public void deleteResourceGroupIfEmpty(String group) { - if (api.getVirtualMachineApi(group).list().isEmpty() - && api.getStorageAccountApi(group).list().isEmpty() + public boolean deleteResourceGroupIfEmpty(String group) { + boolean deleted = false; + if (api.getVirtualMachineApi(group).list().isEmpty() && api.getStorageAccountApi(group).list().isEmpty() && api.getNetworkInterfaceCardApi(group).list().isEmpty() - && api.getPublicIPAddressApi(group).list().isEmpty()) { + && api.getPublicIPAddressApi(group).list().isEmpty() + && api.getNetworkSecurityGroupApi(group).list().isEmpty()) { logger.debug(">> the resource group %s is empty. Deleting...", group); - resourceDeleted.apply(api.getResourceGroupApi().delete(group)); + deleted = resourceDeleted.apply(api.getResourceGroupApi().delete(group)); } + return deleted; } private Iterable<String> getPublicIps(String group, NetworkInterfaceCard nic) { http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/867ddef6/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java ---------------------------------------------------------------------- diff --git a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java index c8a9259..8939eb1 100644 --- a/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java +++ b/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/compute/extensions/AzureComputeImageExtensionLiveTest.java @@ -71,4 +71,5 @@ public class AzureComputeImageExtensionLiveTest extends BaseImageExtensionLiveTe .overrideLoginPrivateKey(keyPair.get("private"))); } + }
