[JCLOUDS-1377] add support for injectable Neutron Context into Nova - fix NovaComputeServiceExpectTest - fix NovaComputeServiceExpectTest - fix CreateSecurityGroupIfNeededTest - fix FindSecurityGroupInRegionOrCreateTest - fix checkstyle - fix removal from security group cache - fix listSecurityGroupsForNode - change both Nova and Neutron listSecurityGroupsForNode to use NovaApi.listSecurityGroupForServer
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/09936b57 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/09936b57 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/09936b57 Branch: refs/heads/master Commit: 09936b57fc90b5a3c7fe530358a2c6a757c32839 Parents: 2a56db0 Author: Andrea Turli <andrea.tu...@gmail.com> Authored: Wed Jan 17 11:46:55 2018 +0000 Committer: Andrea Turli <andrea.tu...@gmail.com> Committed: Fri Feb 2 16:26:22 2018 +0100 ---------------------------------------------------------------------- apis/openstack-nova/pom.xml | 5 + .../openstack/nova/v2_0/NovaApiMetadata.java | 2 + .../v2_0/compute/NovaComputeServiceAdapter.java | 2 +- .../config/NovaComputeServiceContextModule.java | 60 ++-- .../NeutronSecurityGroupExtension.java | 325 +++++++++++++++++++ .../extensions/NovaSecurityGroupExtension.java | 40 +-- .../compute/functions/CleanupResources.java | 34 +- .../functions/CreateSecurityGroupIfNeeded.java | 138 ++++++-- .../NeutronSecurityGroupToSecurityGroup.java | 85 +++++ .../NovaSecurityGroupToSecurityGroup.java | 97 ++++++ .../loaders/FindSecurityGroupOrCreate.java | 35 +- .../compute/options/NovaTemplateOptions.java | 1 + ...desWithGroupEncodedIntoNameThenAddToSet.java | 61 ++-- .../NeutronSecurityGroupInRegion.java | 80 +++++ .../openstack/nova/v2_0/features/ServerApi.java | 22 +- .../NovaComputeServiceAdapterExpectTest.java | 14 +- .../compute/NovaComputeServiceExpectTest.java | 6 +- .../compute/NovaComputeServiceLiveTest.java | 21 +- .../NeutronSecurityGroupExtensionLiveTest.java | 117 +++++++ .../NovaSecurityGroupExtensionExpectTest.java | 3 +- .../NovaSecurityGroupExtensionLiveTest.java | 1 - .../FindSecurityGroupInRegionOrCreateTest.java | 131 ++++++++ .../loaders/FindSecurityGroupOrCreateTest.java | 141 -------- .../nova/v2_0/features/ServerApiExpectTest.java | 33 ++ .../nova/v2_0/features/ServerApiLiveTest.java | 21 +- .../CreateSecurityGroupIfNeededTest.java | 112 ++++++- ...eComputeServiceTypicalSecurityGroupTest.java | 24 +- .../src/test/resources/image_list_detail.json | 27 ++ .../resources/image_list_detail_openstack.json | 27 ++ .../src/test/resources/logback-test.xml | 2 +- .../BaseSecurityGroupExtensionLiveTest.java | 5 +- 31 files changed, 1322 insertions(+), 350 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/pom.xml ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/pom.xml b/apis/openstack-nova/pom.xml index 85a9e28..8e90a0b 100644 --- a/apis/openstack-nova/pom.xml +++ b/apis/openstack-nova/pom.xml @@ -53,6 +53,11 @@ <artifactId>openstack-keystone</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.jclouds.api</groupId> + <artifactId>openstack-neutron</artifactId> + <version>${project.version}</version> + </dependency> <!-- for the extension namespaces --> <dependency> <groupId>com.google.inject.extensions</groupId> http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java index a330afa..2ee56a7 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java @@ -17,6 +17,7 @@ package org.jclouds.openstack.nova.v2_0; import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL; +import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE; import static org.jclouds.openstack.keystone.config.KeystoneProperties.CREDENTIAL_TYPE; import static org.jclouds.openstack.keystone.config.KeystoneProperties.KEYSTONE_VERSION; import static org.jclouds.openstack.keystone.config.KeystoneProperties.SERVICE_TYPE; @@ -78,6 +79,7 @@ public class NovaApiMetadata extends BaseHttpApiMetadata<NovaApi> { // before expiry by default. We choose a value less than the latter // since the former persists between jclouds invocations. properties.setProperty(PROPERTY_SESSION_INTERVAL, 30 * 60 + ""); + properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true,osVersionMatches=16.*"); return properties; } http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java index 02cfa2b..b2e18c8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java @@ -140,7 +140,7 @@ public class NovaComputeServiceAdapter implements logger.warn(message); String tagString = metadataAndTagsAsCommaDelimitedValue.get("jclouds_tags"); Set<String> tags = Sets.newHashSet(Splitter.on(',').split(tagString)); - cleanupResources.removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionId, tags); + cleanupResources.removeSecurityGroupCreatedByJcloudsAndInvalidateCache(tags); throw new IllegalStateException(message); } logger.trace("<< server(%s)", lightweightServer.getId()); http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java index 700a39b..4402d47 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java @@ -17,21 +17,22 @@ package org.jclouds.openstack.nova.v2_0.compute.config; import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING; import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED; import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_ALLOCATE_FLOATING_IPS; import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_GENERATE_KEYPAIRS; -import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.TIMEOUT_SECURITYGROUP_PRESENT; import static org.jclouds.util.Predicates2.retry; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import javax.inject.Named; import javax.inject.Singleton; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.assistedinject.FactoryModuleBuilder; +import org.jclouds.Context; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceAdapter; @@ -53,6 +54,7 @@ import org.jclouds.functions.IdentityFunction; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.compute.NovaComputeService; import org.jclouds.openstack.nova.v2_0.compute.NovaComputeServiceAdapter; +import org.jclouds.openstack.nova.v2_0.compute.extensions.NeutronSecurityGroupExtension; import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaImageExtension; import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaSecurityGroupExtension; import org.jclouds.openstack.nova.v2_0.compute.functions.CleanupResources; @@ -60,7 +62,9 @@ import org.jclouds.openstack.nova.v2_0.compute.functions.CreateSecurityGroupIfNe import org.jclouds.openstack.nova.v2_0.compute.functions.FlavorInRegionToHardware; import org.jclouds.openstack.nova.v2_0.compute.functions.ImageInRegionToImage; import org.jclouds.openstack.nova.v2_0.compute.functions.ImageToOperatingSystem; +import org.jclouds.openstack.nova.v2_0.compute.functions.NeutronSecurityGroupToSecurityGroup; import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInRegionToSecurityGroup; +import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup; import org.jclouds.openstack.nova.v2_0.compute.functions.OrphanedGroupsByRegionId; import org.jclouds.openstack.nova.v2_0.compute.functions.ServerInRegionToNodeMetadata; import org.jclouds.openstack.nova.v2_0.compute.loaders.FindSecurityGroupOrCreate; @@ -77,7 +81,6 @@ import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ServerInRegion; -import org.jclouds.openstack.nova.v2_0.predicates.FindSecurityGroupWithNameAndReturnTrue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; @@ -137,10 +140,10 @@ public class NovaComputeServiceContextModule extends bind(new TypeLiteral<CacheLoader<RegionAndId, Iterable<? extends FloatingIP>>>() { }).annotatedWith(Names.named("FLOATINGIP")).to(LoadFloatingIpsForInstance.class); - bind(new TypeLiteral<Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion>>() { + bind(new TypeLiteral<Function<RegionSecurityGroupNameAndPorts, SecurityGroup>>() { }).to(CreateSecurityGroupIfNeeded.class); - bind(new TypeLiteral<CacheLoader<RegionAndName, SecurityGroupInRegion>>() { + bind(new TypeLiteral<CacheLoader<RegionAndName, SecurityGroup>>() { }).to(FindSecurityGroupOrCreate.class); bind(CreateNodesWithGroupEncodedIntoNameThenAddToSet.class).to( @@ -149,11 +152,37 @@ public class NovaComputeServiceContextModule extends bind(new TypeLiteral<ImageExtension>() { }).to(NovaImageExtension.class); - bind(new TypeLiteral<SecurityGroupExtension>() { - }).to(NovaSecurityGroupExtension.class); - bind(new TypeLiteral<Function<NodeMetadata, Boolean>>() { }).to(CleanupResources.class); + + install(new FactoryModuleBuilder().build(NeutronSecurityGroupToSecurityGroup.Factory.class)); + install(new FactoryModuleBuilder().build(NovaSecurityGroupToSecurityGroup.Factory.class)); + + bind(new TypeLiteral<SecurityGroupExtension>() { + }).toProvider(SecurityGroupExtensionProvider.class); + + } + + @Singleton + public static class SecurityGroupExtensionProvider implements Provider<SecurityGroupExtension> { + @Inject(optional = true) + @Named("openstack-neutron") + protected Supplier<Context> neutronApiContextSupplier; + + private final NeutronSecurityGroupExtension neutronSecurityGroupExtension; + private final NovaSecurityGroupExtension novaSecurityGroupExtension; + + @Inject + SecurityGroupExtensionProvider(NeutronSecurityGroupExtension neutronSecurityGroupExtension, + NovaSecurityGroupExtension novaSecurityGroupExtension) { + this.neutronSecurityGroupExtension = neutronSecurityGroupExtension; + this.novaSecurityGroupExtension = novaSecurityGroupExtension; + } + + @Override + public SecurityGroupExtension get() { + return neutronApiContextSupplier != null ? neutronSecurityGroupExtension : novaSecurityGroupExtension; + } } @Override @@ -192,8 +221,8 @@ public class NovaComputeServiceContextModule extends @Provides @Singleton - protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap( - CacheLoader<RegionAndName, SecurityGroupInRegion> in) { + protected final LoadingCache<RegionAndName, SecurityGroup> securityGroupMap( + CacheLoader<RegionAndName, SecurityGroup> in) { return CacheBuilder.newBuilder().build(in); } @@ -205,15 +234,6 @@ public class NovaComputeServiceContextModule extends @Provides @Singleton - @Named("SECURITYGROUP_PRESENT") - protected final Predicate<AtomicReference<RegionAndName>> securityGroupEventualConsistencyDelay( - FindSecurityGroupWithNameAndReturnTrue in, - @Named(TIMEOUT_SECURITYGROUP_PRESENT) long msDelay) { - return retry(in, msDelay, 100L, MILLISECONDS); - } - - @Provides - @Singleton protected final Supplier<Map<String, Location>> createLocationIndexedById( @Memoized Supplier<Set<? extends Location>> locations) { return Suppliers.compose(new Function<Set<? extends Location>, Map<String, Location>>() { http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java new file mode 100644 index 0000000..dae8af7 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java @@ -0,0 +1,325 @@ +/* + * 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.openstack.nova.v2_0.compute.extensions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.notNull; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; + +import java.util.Map; +import java.util.Set; + +import javax.annotation.Resource; +import javax.inject.Named; + +import org.jclouds.Context; +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.extensions.SecurityGroupExtension; +import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.domain.Location; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.location.Region; +import org.jclouds.logging.Logger; +import org.jclouds.net.domain.IpPermission; +import org.jclouds.net.domain.IpProtocol; +import org.jclouds.openstack.neutron.v2.NeutronApi; +import org.jclouds.openstack.neutron.v2.domain.Rule; +import org.jclouds.openstack.neutron.v2.domain.RuleDirection; +import org.jclouds.openstack.neutron.v2.domain.RuleEthertype; +import org.jclouds.openstack.neutron.v2.domain.RuleProtocol; +import org.jclouds.openstack.neutron.v2.features.SecurityGroupApi; +import org.jclouds.openstack.nova.v2_0.NovaApi; +import org.jclouds.openstack.nova.v2_0.compute.functions.NeutronSecurityGroupToSecurityGroup; +import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup; +import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId; +import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; +import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; +import org.jclouds.rest.ApiContext; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.inject.Inject; + +/** + * An extension to compute service to allow for the manipulation of {@link org.jclouds.compute.domain.SecurityGroup}s. Implementation + * is optional by providers. + */ +public class NeutronSecurityGroupExtension implements SecurityGroupExtension { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + private final NovaApi api; + private final Supplier<Set<String>> regionIds; + private final GroupNamingConvention.Factory namingConvention; + private final LoadingCache<RegionAndName, SecurityGroup> groupCreator; + private final Supplier<Map<String, Location>> locationIndex; + private final NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup; + private final NovaSecurityGroupToSecurityGroup.Factory novaSecurityGroupToSecurityGroup; + + @Inject(optional = true) + @Named("openstack-neutron") + private Supplier<Context> neutronContextSupplier; + + @Inject + NeutronSecurityGroupExtension(NovaApi api, + @Region Supplier<Set<String>> regionIds, + GroupNamingConvention.Factory namingConvention, LoadingCache<RegionAndName, SecurityGroup> groupCreator, + Supplier<Map<String, Location>> locationIndex, + NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup, + NovaSecurityGroupToSecurityGroup.Factory novaSecurityGroupToSecurityGroup) { + this.api = api; + this.regionIds = checkNotNull(regionIds, "regionIds"); + this.namingConvention = checkNotNull(namingConvention, "namingConvention"); + this.groupCreator = groupCreator; + this.locationIndex = locationIndex; + this.neutronSecurityGroupToSecurityGroup = neutronSecurityGroupToSecurityGroup; + this.novaSecurityGroupToSecurityGroup = novaSecurityGroupToSecurityGroup; + } + + @Override + public SecurityGroup createSecurityGroup(String name, Location location) { + String region = location.getId(); + if (region == null) { + return null; + } + logger.debug(">> creating security group %s in %s...", name, location); + + String markerGroup = namingConvention.create().sharedNameForGroup(name); + RegionSecurityGroupNameAndPorts regionAndName = new RegionSecurityGroupNameAndPorts(region, markerGroup, ImmutableSet.<Integer> of()); + return groupCreator.getUnchecked(regionAndName); + } + + @Override + public Set<SecurityGroup> listSecurityGroups() { + Set<SecurityGroup> securityGroups = Sets.newHashSet(); + + for (String regionId : regionIds.get()) { + Location location = locationIndex.get().get(regionId); + securityGroups.addAll(listSecurityGroupsInLocation(location)); + } + return ImmutableSet.copyOf(securityGroups); + } + + + @Override + public Set<SecurityGroup> listSecurityGroupsInLocation(final Location location) { + String region = location.getId(); + if (region == null) { + return ImmutableSet.of(); + } + return getSecurityGroupApi(region).listSecurityGroups().concat().transform(neutronSecurityGroupToSecurityGroup.create(location)).toSet(); + } + + @Override + public Set<SecurityGroup> listSecurityGroupsForNode(String id) { + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id")); + String region = regionAndId.getRegion(); + Location location = locationIndex.get().get(region); + Set<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.getServerApi(region).listSecurityGroupForServer(regionAndId.getId()); + return ImmutableSet.copyOf(transform(filter(allGroups, notNull()), novaSecurityGroupToSecurityGroup.create(location))); + } + + @Override + public SecurityGroup getSecurityGroupById(String id) { + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id")); + String region = regionAndId.getRegion(); + String groupId = regionAndId.getId(); + + SecurityGroupApi securityGroupApi = getSecurityGroupApi(region); + + Location location = locationIndex.get().get(region); + return neutronSecurityGroupToSecurityGroup.create(location).apply(securityGroupApi.getSecurityGroup(groupId)); + } + + @Override + public boolean removeSecurityGroup(String id) { + checkNotNull(id, "id"); + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id); + String region = regionAndId.getRegion(); + String groupId = regionAndId.getId(); + + SecurityGroupApi securityGroupApi = getSecurityGroupApi(region); + + // Would be nice to delete the group and invalidate the cache atomically - i.e. use a mutex. + // Will make sure that a create operation in parallel won't see inconsistent state. + + boolean deleted = securityGroupApi.deleteSecurityGroup(groupId); + + for (SecurityGroup cachedSg : groupCreator.asMap().values()) { + if (id.equals(cachedSg.getId())) { + String groupName = cachedSg.getName(); + groupCreator.invalidate(new RegionSecurityGroupNameAndPorts(region, groupName, ImmutableSet.<Integer>of())); + break; + } + } + + return deleted; + } + + @Override + public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) { + String region = group.getLocation().getId(); + RegionAndId groupRegionAndId = RegionAndId.fromSlashEncoded(group.getId()); + String id = groupRegionAndId.getId(); + SecurityGroupApi securityGroupApi = getSecurityGroupApi(region); + + if (!ipPermission.getCidrBlocks().isEmpty()) { + for (String cidr : ipPermission.getCidrBlocks()) { + securityGroupApi.create(Rule.CreateRule.createBuilder(RuleDirection.INGRESS, group.getProviderId()) + .protocol(RuleProtocol.fromValue(ipPermission.getIpProtocol().name())) + .ethertype(RuleEthertype.IPV4) + .portRangeMin(ipPermission.getFromPort()) + .portRangeMax(ipPermission.getToPort()) + .remoteIpPrefix(cidr) + .build()); + } + } + + if (!ipPermission.getGroupIds().isEmpty()) { + for (String regionAndGroupRaw : ipPermission.getGroupIds()) { + RegionAndId regionAndId = RegionAndId.fromSlashEncoded(regionAndGroupRaw); + String groupId = regionAndId.getId(); + securityGroupApi.create(Rule.CreateRule.createBuilder(RuleDirection.INGRESS, groupId) + .protocol(RuleProtocol.fromValue(ipPermission.getIpProtocol().name())) + .ethertype(RuleEthertype.IPV4) + .portRangeMin(ipPermission.getFromPort()) + .portRangeMax(ipPermission.getToPort()) + .remoteGroupId(groupId) + .build()); + } + } + + return getSecurityGroupById(RegionAndId.fromRegionAndId(region, id).slashEncode()); + } + + @Override + public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort, + Multimap<String, String> tenantIdGroupNamePairs, + Iterable<String> ipRanges, + Iterable<String> groupIds, SecurityGroup group) { + IpPermission.Builder permBuilder = IpPermission.builder(); + permBuilder.ipProtocol(protocol); + permBuilder.fromPort(startPort); + permBuilder.toPort(endPort); + permBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs); + permBuilder.cidrBlocks(ipRanges); + permBuilder.groupIds(groupIds); + + return addIpPermission(permBuilder.build(), group); + } + + @Override + public SecurityGroup removeIpPermission(final IpPermission ipPermission, SecurityGroup group) { + String region = group.getLocation().getId(); + RegionAndId groupRegionAndId = RegionAndId.fromSlashEncoded(group.getId()); + String id = groupRegionAndId.getId(); + + SecurityGroupApi securityGroupApi = getSecurityGroupApi(region); + + org.jclouds.openstack.neutron.v2.domain.SecurityGroup securityGroup = securityGroupApi.getSecurityGroup(id); + + if (!ipPermission.getCidrBlocks().isEmpty()) { + for (final String cidr : ipPermission.getCidrBlocks()) { + for (Rule rule : filter(securityGroup.getRules(), + new Predicate<Rule>() { + @Override + public boolean apply(@Nullable Rule input) { + return input.getRemoteIpPrefix() != null && input.getRemoteIpPrefix().equals(cidr) && + input.getProtocol() != null && input.getProtocol().name().equals(ipPermission.getIpProtocol().name()) && + input.getPortRangeMin() != null && input.getPortRangeMin() == ipPermission.getFromPort() && + input.getPortRangeMax() != null && input.getPortRangeMax() == ipPermission.getToPort(); + } + })) { + securityGroupApi.deleteRule(rule.getId()); + } + } + } + + if (!ipPermission.getGroupIds().isEmpty()) { + for (final String groupId : ipPermission.getGroupIds()) { + for (Rule rule : filter(securityGroup.getRules(), + new Predicate<Rule>() { + @Override + public boolean apply(@Nullable Rule input) { + return input.getRemoteGroupId() != null && input.getRemoteGroupId().equals(groupId) && + input.getProtocol() != null && input.getProtocol().name().equals(ipPermission.getIpProtocol().name()) && + input.getPortRangeMin() != null && input.getPortRangeMin() == ipPermission.getFromPort() && + input.getPortRangeMax() != null && input.getPortRangeMax() == ipPermission.getToPort(); + } + })) { + securityGroupApi.deleteRule(rule.getId()); + } + } + } + + return getSecurityGroupById(RegionAndId.fromRegionAndId(region, id).slashEncode()); + } + + @Override + public SecurityGroup removeIpPermission(IpProtocol protocol, int startPort, int endPort, + Multimap<String, String> tenantIdGroupNamePairs, + Iterable<String> ipRanges, + Iterable<String> groupIds, SecurityGroup group) { + IpPermission.Builder permBuilder = IpPermission.builder(); + permBuilder.ipProtocol(protocol); + permBuilder.fromPort(startPort); + permBuilder.toPort(endPort); + permBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs); + permBuilder.cidrBlocks(ipRanges); + permBuilder.groupIds(groupIds); + + return removeIpPermission(permBuilder.build(), group); + } + + @Override + public boolean supportsTenantIdGroupNamePairs() { + return false; + } + + @Override + public boolean supportsTenantIdGroupIdPairs() { + return false; + } + + @Override + public boolean supportsGroupIds() { + return true; + } + + @Override + public boolean supportsPortRangesForGroups() { + return false; + } + + @Override + public boolean supportsExclusionCidrBlocks() { + return false; + } + + private SecurityGroupApi getSecurityGroupApi(String region) { + return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getSecurityGroupApi(region); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java index 4f992b9..153a406 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java @@ -16,14 +16,12 @@ */ package org.jclouds.openstack.nova.v2_0.compute.extensions; - import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.notNull; 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.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameIn; import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleCidr; import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleEndPort; import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleGroup; @@ -32,9 +30,9 @@ import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates import java.util.Set; -import javax.inject.Inject; import javax.inject.Named; +import com.google.inject.Inject; import org.jclouds.Constants; import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.compute.extensions.SecurityGroupExtension; @@ -46,13 +44,11 @@ import org.jclouds.net.domain.IpProtocol; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.domain.Ingress; import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule; -import org.jclouds.openstack.nova.v2_0.domain.ServerWithSecurityGroups; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi; -import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsApi; import com.google.common.base.Function; import com.google.common.base.Optional; @@ -73,7 +69,7 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension { protected final ListeningExecutorService userExecutor; protected final Supplier<Set<String>> regionIds; protected final Function<SecurityGroupInRegion, SecurityGroup> groupConverter; - protected final LoadingCache<RegionAndName, SecurityGroupInRegion> groupCreator; + protected final LoadingCache<RegionAndName, SecurityGroup> groupCreator; protected final GroupNamingConvention.Factory namingConvention; @Inject @@ -81,7 +77,7 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension { @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, @Region Supplier<Set<String>> regionIds, Function<SecurityGroupInRegion, SecurityGroup> groupConverter, - LoadingCache<RegionAndName, SecurityGroupInRegion> groupCreator, + LoadingCache<RegionAndName, SecurityGroup> groupCreator, GroupNamingConvention.Factory namingConvention) { this.api = checkNotNull(api, "api"); @@ -121,25 +117,9 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension { public Set<SecurityGroup> listSecurityGroupsForNode(String id) { RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id")); String region = regionAndId.getRegion(); - String instanceId = regionAndId.getId(); - - Optional<? extends ServerWithSecurityGroupsApi> serverApi = api.getServerWithSecurityGroupsApi(region); - Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupApi(region); - - if (!serverApi.isPresent() || !sgApi.isPresent()) { - return ImmutableSet.of(); - } - - ServerWithSecurityGroups instance = serverApi.get().get(instanceId); - if (instance == null) { - return ImmutableSet.of(); - } - - Set<String> groupNames = instance.getSecurityGroupNames(); - final FluentIterable<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = sgApi.get().list(); - Set<? extends SecurityGroupInRegion> rawGroups = - allGroups.filter(nameIn(groupNames)).transform(groupToGroupInRegion(allGroups, region)).toSet(); - + Set<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.getServerApi(region).listSecurityGroupForServer(regionAndId.getId()); + Set<? extends SecurityGroupInRegion> rawGroups = + FluentIterable.from(allGroups).transform(groupToGroupInRegion(allGroups, region)).toSet(); return ImmutableSet.copyOf(transform(filter(rawGroups, notNull()), groupConverter)); } @@ -174,8 +154,8 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension { String markerGroup = namingConvention.create().sharedNameForGroup(name); RegionSecurityGroupNameAndPorts regionAndName = new RegionSecurityGroupNameAndPorts(region, markerGroup, ImmutableSet.<Integer> of()); - SecurityGroupInRegion rawGroup = groupCreator.getUnchecked(regionAndName); - return groupConverter.apply(rawGroup); + SecurityGroup rawGroup = groupCreator.getUnchecked(regionAndName); + return rawGroup; } @Override @@ -196,8 +176,8 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension { boolean deleted = sgApi.get().delete(groupId); - for (SecurityGroupInRegion cachedSg : groupCreator.asMap().values()) { - if (groupId.equals(cachedSg.getSecurityGroup().getId())) { + for (SecurityGroup cachedSg : groupCreator.asMap().values()) { + if (id.equals(cachedSg.getId())) { String groupName = cachedSg.getName(); groupCreator.invalidate(new RegionSecurityGroupNameAndPorts(region, groupName, ImmutableSet.<Integer>of())); break; http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java index 9030395..5da0b87 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java @@ -27,13 +27,12 @@ import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.extensions.SecurityGroupExtension; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; -import org.jclouds.openstack.nova.v2_0.NovaApi; -import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; -import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; import com.google.common.base.Function; import com.google.common.base.Predicate; @@ -47,38 +46,29 @@ public class CleanupResources implements Function<NodeMetadata, Boolean> { @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; - protected final NovaApi novaApi; protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate; - protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap; + protected final LoadingCache<RegionAndName, SecurityGroup> securityGroupMap; - @Inject - public CleanupResources(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate, - LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap) { + private final SecurityGroupExtension securityGroupExtension; - this.novaApi = novaApi; + @Inject + public CleanupResources(RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate, + LoadingCache<RegionAndName, SecurityGroup> securityGroupMap, SecurityGroupExtension securityGroupExtension) { this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate; this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap"); + this.securityGroupExtension = securityGroupExtension; } @Override public Boolean apply(NodeMetadata node) { final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(node.getId()); removeFloatingIpFromNodeifAny(regionAndId); - return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionAndId.getRegion(), node.getTags()); + return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(node.getTags()); } - public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(String regionId, Set<String> tags) { + public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(Set<String> tags) { String securityGroupIdCreatedByJclouds = getSecurityGroupIdCreatedByJclouds(tags); - if (securityGroupIdCreatedByJclouds != null) { - SecurityGroup securityGroup = novaApi.getSecurityGroupApi(regionId).get().get(securityGroupIdCreatedByJclouds); - RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionId, securityGroup.getName()); - logger.debug(">> deleting securityGroup(%s)", regionAndName); - novaApi.getSecurityGroupApi(regionId).get().delete(securityGroupIdCreatedByJclouds); - securityGroupMap.invalidate(regionAndName); - logger.debug("<< deleted securityGroup(%s)", regionAndName); - return true; - } - return false; + return securityGroupExtension.removeSecurityGroup(securityGroupIdCreatedByJclouds); } private void removeFloatingIpFromNodeifAny(RegionAndId regionAndId) { @@ -88,7 +78,7 @@ public class CleanupResources implements Function<NodeMetadata, Boolean> { logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", regionAndId, e.getMessage()); } } - + private String getSecurityGroupIdCreatedByJclouds(Set<String> tags) { return FluentIterable.from(tags).filter(new Predicate<String>() { @Override http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java index deea2e9..3faa490 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java @@ -17,73 +17,153 @@ package org.jclouds.openstack.nova.v2_0.compute.functions; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.find; import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameEquals; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; import javax.annotation.Resource; -import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import com.google.common.annotations.VisibleForTesting; +import org.jclouds.Context; +import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.compute.reference.ComputeServiceConstants; +import org.jclouds.domain.Location; import org.jclouds.logging.Logger; import org.jclouds.net.domain.IpProtocol; +import org.jclouds.openstack.neutron.v2.NeutronApi; +import org.jclouds.openstack.neutron.v2.domain.Rule; +import org.jclouds.openstack.neutron.v2.domain.RuleDirection; +import org.jclouds.openstack.neutron.v2.domain.RuleProtocol; +import org.jclouds.openstack.neutron.v2.features.SecurityGroupApi; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.domain.Ingress; -import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup; +import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; -import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi; +import org.jclouds.rest.ApiContext; import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; import com.google.common.collect.FluentIterable; +import com.google.inject.Inject; @Singleton -public class CreateSecurityGroupIfNeeded implements Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> { +public class CreateSecurityGroupIfNeeded implements Function<RegionSecurityGroupNameAndPorts, SecurityGroup> { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; + protected final NovaApi novaApi; + private final Supplier<Map<String, Location>> locationIndex; + private final Function<SecurityGroupInRegion, SecurityGroup> securityGroupInRegionSecurityGroupFunction; + private final NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup; + + @Inject(optional = true) + @Named("openstack-neutron") + Supplier<Context> neutronContextSupplier; @Inject - public CreateSecurityGroupIfNeeded(NovaApi novaApi) { - this.novaApi = checkNotNull(novaApi, "novaApi"); + @VisibleForTesting + public CreateSecurityGroupIfNeeded(NovaApi novaApi, Supplier<Map<String, Location>> locationIndex, + Function<SecurityGroupInRegion, SecurityGroup> securityGroupInRegionSecurityGroupFunction, + NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup) { + this.novaApi = novaApi; + this.locationIndex = locationIndex; + this.securityGroupInRegionSecurityGroupFunction = securityGroupInRegionSecurityGroupFunction; + this.neutronSecurityGroupToSecurityGroup = neutronSecurityGroupToSecurityGroup; } @Override - public SecurityGroupInRegion apply(RegionSecurityGroupNameAndPorts regionSecurityGroupNameAndPorts) { - checkNotNull(regionSecurityGroupNameAndPorts, "regionSecurityGroupNameAndPorts"); - + public SecurityGroup apply(final RegionSecurityGroupNameAndPorts regionSecurityGroupNameAndPorts) { String regionId = regionSecurityGroupNameAndPorts.getRegion(); - Optional<? extends SecurityGroupApi> api = novaApi.getSecurityGroupApi(regionId); - checkArgument(api.isPresent(), "Security groups are required, but the extension is not available in region %s!", regionId); - final FluentIterable<SecurityGroup> allGroups = api.get().list(); + Location location = locationIndex.get().get(regionId); + logger.debug(">> creating securityGroup %s", regionSecurityGroupNameAndPorts); - try { - SecurityGroup securityGroup = api.get().createWithDescription( - regionSecurityGroupNameAndPorts.getName(), regionSecurityGroupNameAndPorts.getName()); - logger.debug("<< created securityGroup(%s)", securityGroup); - for (int port : regionSecurityGroupNameAndPorts.getPorts()) { - authorizeGroupToItselfAndAllIPsToTCPPort(api.get(), securityGroup, port); + SecurityGroupApi securityGroupApi = getNeutronSecurityGroupApi(regionId); + if (securityGroupApi != null) { + org.jclouds.openstack.neutron.v2.domain.SecurityGroup group = securityGroupApi + .create(org.jclouds.openstack.neutron.v2.domain.SecurityGroup.CreateSecurityGroup.createBuilder() + .name(regionSecurityGroupNameAndPorts.getName()).description("security group created by jclouds") + .build()); + return createSecurityGroupFrom(group, location, regionSecurityGroupNameAndPorts.getPorts()); + } else { + // try to use Nova + Optional<? extends org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi> api = novaApi + .getSecurityGroupApi(regionId); + checkArgument(api.isPresent(), + "Security groups are required, but the extension is not available in region %s!", regionId); + final FluentIterable<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.get().list(); + logger.debug(">> creating securityGroup %s", regionSecurityGroupNameAndPorts); + try { + org.jclouds.openstack.nova.v2_0.domain.SecurityGroup novaSecurityGroup = api.get().createWithDescription( + regionSecurityGroupNameAndPorts.getName(), regionSecurityGroupNameAndPorts.getName()); + + logger.debug("<< created securityGroup(%s)", novaSecurityGroup); + for (int port : regionSecurityGroupNameAndPorts.getPorts()) { + authorizeGroupToItselfAndAllIPsToTCPPort(api.get(), novaSecurityGroup, port); + } + return securityGroupInRegionSecurityGroupFunction + .apply(new SecurityGroupInRegion(api.get().get(novaSecurityGroup.getId()), regionId, allGroups)); + } catch (IllegalStateException e) { + logger.trace("<< trying to find securityGroup(%s): %s", regionSecurityGroupNameAndPorts, e.getMessage()); + org.jclouds.openstack.nova.v2_0.domain.SecurityGroup group = find(allGroups, + nameEquals(regionSecurityGroupNameAndPorts.getName())); + logger.debug("<< reused securityGroup(%s)", group.getId()); + return securityGroupInRegionSecurityGroupFunction + .apply(new SecurityGroupInRegion(group, regionId, allGroups)); + } + } + } + + private SecurityGroup createSecurityGroupFrom(final org.jclouds.openstack.neutron.v2.domain.SecurityGroup group, + Location location, Set<Integer> ports) { + SecurityGroup securityGroup = neutronSecurityGroupToSecurityGroup.create(location).apply(group); + logger.debug("<< created securityGroup(%s)", securityGroup); + + SecurityGroupApi securityGroupApi = getNeutronSecurityGroupApi(location.getId()); + try { + for (int inboundPort : ports) { + logger.debug(">> authorizing securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, inboundPort); + securityGroupApi.create( + Rule.CreateRule.createBuilder(RuleDirection.INGRESS, RegionAndId.fromSlashEncoded(securityGroup.getId()).getId()).protocol(RuleProtocol.TCP) + .portRangeMin(inboundPort).portRangeMax(inboundPort).remoteIpPrefix("0.0.0.0/0").build()); + logger.debug("<< authorized securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, inboundPort); } - return new SecurityGroupInRegion(api.get().get(securityGroup.getId()), regionId, allGroups); + return securityGroup; } catch (IllegalStateException e) { - logger.trace("<< trying to find securityGroup(%s): %s", regionSecurityGroupNameAndPorts, e.getMessage()); - SecurityGroup group = find(allGroups, nameEquals(regionSecurityGroupNameAndPorts.getName())); - logger.debug("<< reused securityGroup(%s)", group.getId()); - return new SecurityGroupInRegion(group, regionId, allGroups); + logger.trace("<< trying to find securityGroup(%s): %s", group, e.getMessage()); + + return securityGroupApi.listSecurityGroups().concat() + .filter(new Predicate<org.jclouds.openstack.neutron.v2.domain.SecurityGroup>() { + @Override + public boolean apply(@Nullable org.jclouds.openstack.neutron.v2.domain.SecurityGroup input) { + return input.getName().equals(group.getName()); + } + }).transform(neutronSecurityGroupToSecurityGroup.create(location)).first().orNull(); } } - private void authorizeGroupToItselfAndAllIPsToTCPPort(SecurityGroupApi securityGroupApi, - SecurityGroup securityGroup, int port) { + private SecurityGroupApi getNeutronSecurityGroupApi(String region) { + if (neutronContextSupplier == null) + return null; + return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getSecurityGroupApi(region); + } + + private void authorizeGroupToItselfAndAllIPsToTCPPort( + org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi securityGroupApi, + org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroup, int port) { logger.debug(">> authorizing securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, port); - securityGroupApi.createRuleAllowingCidrBlock(securityGroup.getId(), Ingress.builder().ipProtocol( - IpProtocol.TCP).fromPort(port).toPort(port).build(), "0.0.0.0/0"); + securityGroupApi.createRuleAllowingCidrBlock(securityGroup.getId(), + Ingress.builder().ipProtocol(IpProtocol.TCP).fromPort(port).toPort(port).build(), "0.0.0.0/0"); logger.debug("<< authorized securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, port); - } + } http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java new file mode 100644 index 0000000..0a6ffbc --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java @@ -0,0 +1,85 @@ +/* + * 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.openstack.nova.v2_0.compute.functions; + +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.inject.assistedinject.Assisted; +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.domain.SecurityGroupBuilder; +import org.jclouds.domain.Location; +import org.jclouds.net.domain.IpPermission; +import org.jclouds.net.domain.IpProtocol; +import org.jclouds.openstack.neutron.v2.domain.Rule; +import org.jclouds.openstack.neutron.v2.domain.RuleDirection; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; + +public class NeutronSecurityGroupToSecurityGroup implements Function<org.jclouds.openstack.neutron.v2.domain.SecurityGroup, SecurityGroup> { + + public interface Factory { + NeutronSecurityGroupToSecurityGroup create(Location location); + } + + private final Location location; + + @Inject + public NeutronSecurityGroupToSecurityGroup(@Assisted Location location) { + this.location = location; + } + + @Override + public SecurityGroup apply(@Nullable org.jclouds.openstack.neutron.v2.domain.SecurityGroup group) { + SecurityGroupBuilder builder = new SecurityGroupBuilder(); + builder.providerId(group.getId()); + builder.ownerId(group.getTenantId()); + builder.name(group.getName()); + final String regionId = location.getId(); + builder.location(location); + + builder.id(regionId + "/" + group.getId()); + if (group.getRules() != null) { + builder.ipPermissions(filter(transform(group.getRules(), new Function<Rule, IpPermission>() { + @Override + public IpPermission apply(Rule from) { + if (from.getDirection() == RuleDirection.EGRESS) return null; + IpPermission.Builder builder = IpPermission.builder(); + if (from.getProtocol() != null) { + builder.ipProtocol(IpProtocol.fromValue(from.getProtocol().name())); + } else { + builder.ipProtocol(IpProtocol.TCP); + } + if (from.getPortRangeMin() != null) builder.fromPort(from.getPortRangeMin()); + if (from.getPortRangeMax() != null) builder.toPort(from.getPortRangeMax()); + if (from.getRemoteGroupId() != null) { + builder.groupId(regionId + "/" + from.getRemoteGroupId()); + } else if (from.getRemoteIpPrefix() != null){ + builder.cidrBlock(from.getRemoteIpPrefix()); + } + + return builder.build(); + } + }), Predicates.notNull())); + } + + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java new file mode 100644 index 0000000..8739f60 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.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.openstack.nova.v2_0.compute.functions; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; + +import javax.annotation.Nullable; +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jclouds.compute.domain.SecurityGroup; +import org.jclouds.compute.domain.SecurityGroupBuilder; +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.openstack.nova.v2_0.domain.SecurityGroupRule; +import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName; + +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.inject.assistedinject.Assisted; + +/** + * A function for transforming a Nova-specific SecurityGroup into a generic + * SecurityGroup object. + */ +public class NovaSecurityGroupToSecurityGroup + implements Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroup> { + + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + + public interface Factory { + NovaSecurityGroupToSecurityGroup create(Location location); + } + + private final Location location; + + @Inject + public NovaSecurityGroupToSecurityGroup(@Assisted Location location) { + this.location = location; + } + + @Override + public SecurityGroup apply(@Nullable org.jclouds.openstack.nova.v2_0.domain.SecurityGroup group) { + SecurityGroupBuilder builder = new SecurityGroupBuilder(); + builder.providerId(group.getId()); + builder.ownerId(group.getTenantId()); + builder.name(group.getName()); + final String regionId = location.getId(); + builder.location(location); + + builder.id(regionId + "/" + group.getId()); + if (group.getRules() != null) { + builder.ipPermissions(filter(transform(group.getRules(), new Function<SecurityGroupRule, IpPermission>() { + @Override + public IpPermission apply(SecurityGroupRule input) { + return securityGroupRuleToIpPermission(input); + } + }), Predicates.notNull())); + } + return builder.build(); + } + + private IpPermission securityGroupRuleToIpPermission(SecurityGroupRule rule) { + IpPermission.Builder builder = IpPermission.builder(); + builder.ipProtocol(rule.getIpProtocol()); + builder.fromPort(rule.getFromPort()); + builder.toPort(rule.getToPort()); + final TenantIdAndName ruleGroup = rule.getGroup(); + if (ruleGroup != null) { + builder.groupId(location.getId() + "/" + ruleGroup.getTenantId()); + } + if (rule.getIpRange() != null) { + builder.cidrBlock(rule.getIpRange()); + } + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java index 1a980e5..274e4ef 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java @@ -19,54 +19,31 @@ package org.jclouds.openstack.nova.v2_0.compute.loaders; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import java.util.concurrent.atomic.AtomicReference; - import javax.inject.Inject; -import javax.inject.Named; +import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; -import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.cache.CacheLoader; -import com.google.common.util.concurrent.Atomics; -public class FindSecurityGroupOrCreate extends CacheLoader<RegionAndName, SecurityGroupInRegion> { +public class FindSecurityGroupOrCreate extends CacheLoader<RegionAndName, SecurityGroup> { - protected final Predicate<AtomicReference<RegionAndName>> returnSecurityGroupExistsInRegion; - protected final Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> groupCreator; + protected final Function<RegionSecurityGroupNameAndPorts, SecurityGroup> groupCreator; @Inject public FindSecurityGroupOrCreate( - @Named("SECURITYGROUP_PRESENT") Predicate<AtomicReference<RegionAndName>> returnSecurityGroupExistsInRegion, - Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> groupCreator) { - this.returnSecurityGroupExistsInRegion = checkNotNull(returnSecurityGroupExistsInRegion, - "returnSecurityGroupExistsInRegion"); + Function<RegionSecurityGroupNameAndPorts, SecurityGroup> groupCreator) { this.groupCreator = checkNotNull(groupCreator, "groupCreator"); } @Override - public SecurityGroupInRegion load(RegionAndName in) { - AtomicReference<RegionAndName> securityGroupInRegionRef = Atomics.newReference(checkNotNull(in, - "regionSecurityGroupNameAndPorts")); - if (returnSecurityGroupExistsInRegion.apply(securityGroupInRegionRef)) { - return returnExistingSecurityGroup(securityGroupInRegionRef); - } else { + public SecurityGroup load(RegionAndName in) { return createNewSecurityGroup(in); - } - } - - private SecurityGroupInRegion returnExistingSecurityGroup(AtomicReference<RegionAndName> securityGroupInRegionRef) { - RegionAndName securityGroupInRegion = securityGroupInRegionRef.get(); - checkState(securityGroupInRegion instanceof SecurityGroupInRegion, - "programming error: predicate %s should update the atomic reference to the actual security group found", - returnSecurityGroupExistsInRegion); - return SecurityGroupInRegion.class.cast(securityGroupInRegion); } - private SecurityGroupInRegion createNewSecurityGroup(RegionAndName in) { + private SecurityGroup createNewSecurityGroup(RegionAndName in) { checkState( checkNotNull(in, "regionSecurityGroupNameAndPorts") instanceof RegionSecurityGroupNameAndPorts, "programming error: when issuing get to this cacheloader, you need to pass an instance of RegionSecurityGroupNameAndPorts, not %s", http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java index 992302e..f9f4fb5 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java @@ -80,6 +80,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { eTo.configDrive(getConfigDrive()); eTo.novaNetworks(getNovaNetworks()); eTo.availabilityZone(getAvailabilityZone()); + eTo.blockDeviceMappings(getBlockDeviceMappings()); } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 5f747e6..532643a 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkState; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; @@ -33,25 +32,25 @@ import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.compute.config.CustomizationResponse; import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.SecurityGroup; import org.jclouds.compute.domain.Template; +import org.jclouds.compute.extensions.SecurityGroupExtension; import org.jclouds.compute.functions.GroupNamingConvention; 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.javax.annotation.Nullable; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.compute.functions.AllocateAndAddFloatingIpToNode; import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.domain.KeyPair; -import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; -import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion; import com.google.common.base.Function; import com.google.common.base.Strings; -import com.google.common.base.Throwables; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -70,8 +69,9 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT public static final String JCLOUDS_SG_PREFIX = "jclouds_sg"; private final AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode; - protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache; - protected final NovaApi novaApi; + private final LoadingCache<RegionAndName, SecurityGroup> securityGroupCache; + private final NovaApi novaApi; + private final SecurityGroupExtension securityGroupExtension; @Inject protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet( @@ -81,13 +81,16 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode, - LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache, NovaApi novaApi) { + LoadingCache<RegionAndName, SecurityGroup> securityGroupCache, + NovaApi novaApi, + SecurityGroupExtension securityGroupExtension) { super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, userExecutor, customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache"); this.createAndAddFloatingIpToNode = checkNotNull(createAndAddFloatingIpToNode, "createAndAddFloatingIpToNode"); this.novaApi = checkNotNull(novaApi, "novaApi"); + this.securityGroupExtension = securityGroupExtension; } @Override @@ -106,14 +109,8 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT checkArgument(novaApi.getKeyPairApi(region).isPresent(), "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions); } - final List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts()); - if (!templateOptions.getGroups().isEmpty() || !inboundPorts.isEmpty()) { - checkArgument(novaApi.getSecurityGroupApi(region).isPresent(), - "Security groups are required by options, but the extension is not available! options: %s", - templateOptions); - } - + KeyPair keyPair = null; if (templateOptions.shouldGenerateKeyPair()) { keyPair = generateKeyPair(region, namingConvention.create().sharedNameForGroup(group)); @@ -132,29 +129,29 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder(); if (!templateOptions.getGroups().isEmpty()) { - Set<String> securityGroupNames = novaApi.getSecurityGroupApi(region).get().list() - .transform(new Function<SecurityGroup, String>() { - @Override - public String apply(SecurityGroup input) { - return input.getName(); - } - }) - .toSet(); + Iterable<String> securityGroupNames = Iterables.transform(securityGroupExtension.listSecurityGroups(), new Function<org.jclouds.compute.domain.SecurityGroup, String>() { + @Override + public String apply(@Nullable org.jclouds.compute.domain.SecurityGroup input) { + return input.getName(); + } + }); for (String securityGroupName : templateOptions.getGroups()) { - checkState(securityGroupNames.contains(securityGroupName), "Cannot find security group with name " + securityGroupName + ". \nSecurity groups available are: \n" + Iterables.toString(securityGroupNames)); // { + checkState(Iterables.contains(securityGroupNames, securityGroupName), "Cannot find security group with name " + securityGroupName + ". \nSecurity groups available are: \n" + Iterables.toString(securityGroupNames)); // { } - } - else if (!inboundPorts.isEmpty()) { - SecurityGroupInRegion securityGroupInRegion; + + } else if (!inboundPorts.isEmpty()) { String securityGroupName = namingConvention.create().sharedNameForGroup(group); - try { - securityGroupInRegion = securityGroupCache.get(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts)); - } catch (ExecutionException e) { - throw Throwables.propagate(e.getCause()); + + // populate the security group cache with existing security groups + for (SecurityGroup existingSecurityGroup : securityGroupExtension.listSecurityGroupsInLocation(template.getLocation())) { + securityGroupCache.put(new RegionSecurityGroupNameAndPorts(region, existingSecurityGroup.getName(), inboundPorts), existingSecurityGroup); } - templateOptions.securityGroups(securityGroupName); - tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroupInRegion.getSecurityGroup().getId())); + + SecurityGroup securityGroup = securityGroupCache.getUnchecked(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts)); + templateOptions.securityGroups(securityGroup.getName()); + tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroup.getId())); } + templateOptions.tags(tagsBuilder.build()); Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes, http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java new file mode 100644 index 0000000..11e3f90 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java @@ -0,0 +1,80 @@ +/* + * 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.openstack.nova.v2_0.domain.regionscoped; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Map; + +import org.jclouds.openstack.neutron.v2.domain.SecurityGroup; +import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName; + +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +public class NeutronSecurityGroupInRegion extends RegionAndName { + protected final SecurityGroup securityGroup; + + protected final Multimap<TenantIdAndName, SecurityGroup> groupsByName; + + public NeutronSecurityGroupInRegion(SecurityGroup securityGroup, String regionId, Iterable<SecurityGroup> allGroupsInRegion) { + super(regionId, checkNotNull(securityGroup, "securityGroup").getName()); + this.securityGroup = securityGroup; + this.groupsByName = HashMultimap.create(); + for (SecurityGroup groupInRegion : allGroupsInRegion) { + final TenantIdAndName tenantIdAndName = TenantIdAndName.builder() + .tenantId(groupInRegion.getTenantId()) + .name(groupInRegion.getName()) + .build(); + this.groupsByName.put(tenantIdAndName, groupInRegion); + } + } + + public SecurityGroup getSecurityGroup() { + return securityGroup; + } + + /** + * Returns a map of group {@link TenantIdAndName}s to groups. + * + * The returned value is a collection, to take into account the possibility that certain clouds + * may permit duplicate group names. + * + * @return The map of names to (collections of) groups. + */ + public Map<TenantIdAndName, Collection<SecurityGroup>> getGroupsByName() { + return groupsByName.asMap(); + } + + // superclass hashCode/equals are good enough, and help us use RegionAndName and SecurityGroupInRegion + // interchangeably as Map keys + + @Override + protected ToStringHelper string() { + return super.string() + .add("securityGroup", securityGroup) + .add("groupsByName", groupsByName); + } + + @Override + public String toString() { + return string().toString(); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java index 33bf09a..512f4a0 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java @@ -19,6 +19,7 @@ package org.jclouds.openstack.nova.v2_0.features; import com.google.common.base.Optional; import java.util.Map; +import java.util.Set; import javax.inject.Named; import javax.ws.rs.Consumes; @@ -31,6 +32,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.jclouds.Fallbacks; import org.jclouds.Fallbacks.AbsentOn403Or404Or500; import org.jclouds.Fallbacks.EmptyMapOnNotFoundOr404; import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404; @@ -40,6 +42,7 @@ import org.jclouds.Fallbacks.VoidOnNotFoundOr404; import org.jclouds.collect.PagedIterable; import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions; import org.jclouds.javax.annotation.Nullable; +import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup; import org.jclouds.openstack.v2_0.domain.PaginatedCollection; import org.jclouds.openstack.keystone.auth.filters.AuthenticateRequest; import org.jclouds.openstack.keystone.v2_0.KeystoneFallbacks.EmptyPaginatedCollectionOnNotFoundOr404; @@ -362,8 +365,8 @@ public interface ServerApi { * * @param id * id of the image - * @param metadata - * a Map containing the metadata + * @param key + * a key containing the metadata * @return the value or null if not present */ @Named("server:getMetadata") @@ -428,4 +431,19 @@ public interface ServerApi { @ResponseParser(ParseDiagnostics.class) @Fallback(AbsentOn403Or404Or500.class) Optional<Map<String, String>> getDiagnostics(@PathParam("id") String id); + + /** + * Lists Security Groups for a server. + + * @param id + * id of the server + * @return a list of security groups attached to the server + */ + @Named("server:getSecurityGroups") + @GET + @Path("/{id}/os-security-groups") + @SelectJson("security_groups") + @Fallback(Fallbacks.EmptySetOnNotFoundOr404.class) + Set<SecurityGroup> listSecurityGroupForServer(@PathParam("id") String id); + } http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java ---------------------------------------------------------------------- diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java index 5528349..1317fc5 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java @@ -66,7 +66,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"networks\": [{\"uuid\": \"4ebd35cf-bfe7-4d93-b0d8-eb468ce2245a\"}]}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"4ebd35cf-bfe7-4d93-b0d8-eb468ce2245a\"}]}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -101,7 +101,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"OS-DCF:diskConfig\":\"AUTO\"}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"OS-DCF:diskConfig\":\"AUTO\"}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -136,7 +136,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"config_drive\":\"true\"}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"config_drive\":\"true\"}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -170,7 +170,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"12345\", \"port\":\"67890\", \"fixed_ip\":\"192.168.0.1\"},{\"uuid\":\"54321\", \"port\":\"09876\", \"fixed_ip\":\"192.168.0.2\"},{\"uuid\":\"non-nova-uuid\"}]}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"12345\",\"port\":\"67890\",\"fixed_ip\":\"192.168.0.1\"},{\"uuid\":\"54321\",\"port\":\"09876\",\"fixed_ip\":\"192.168.0.2\"},{\"uuid\":\"non-nova-uuid\"}]}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -217,7 +217,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"security_groups\":[{\"name\":\"group1\"}, {\"name\":\"group2\"}]}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"security_groups\":[{\"name\":\"group1\"},{\"name\":\"group2\"}]}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -258,7 +258,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"key_name\":\"foo\"}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"key_name\":\"foo\"}}", "application/json")) .build(); @@ -301,7 +301,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\"}}", "application/json")) + "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\"}}", "application/json")) .build(); HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")