From 87140fd2377553b532fb955a974b10aabf475e50 Mon Sep 17 00:00:00 2001 From: Richard Downer Date: Tue, 23 Jul 2013 15:27:15 +0100 Subject: [PATCH] JCLOUDS-367: GCE nodes n>1 ignoring inboundPort The inboundPort settings of the first node in the group dictated the firewall configuration. Subsequent nodes added to the group had their inboundPort settings ignored. GCE firewalls specify their "target" (VM instances) by means of tags - if a targetTag on a firewall matches the tag on an instance, the firewall's rules are allowed for the instance. This commit applies a tag for each requested inboundPort to new instances. Then, a firewall is created for each tag (if one does not already exist) which has 'allow' rules for the port. --- .../compute/GoogleComputeEngineService.java | 37 +++++++--- .../compute/GoogleComputeEngineServiceAdapter.java | 40 ++++++++--- .../GoogleComputeEngineServiceContextModule.java | 3 + .../functions/FirewallTagNamingConvention.java | 64 +++++++++++++++++ .../functions/InstanceInZoneToNodeMetadata.java | 15 +++- ...eNodesWithGroupEncodedIntoNameThenAddToSet.java | 80 +++++++++++----------- .../googlecomputeengine/domain/Firewall.java | 6 ++ .../GoogleComputeEngineServiceExpectTest.java | 64 ++++++++++++++++- .../firewall_list.json | 37 ++++++++++ .../network_get.json | 10 +++ 10 files changed, 291 insertions(+), 65 deletions(-) create mode 100644 google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java create mode 100644 google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/firewall_list.json create mode 100644 google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/network_get.json diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java index 0d5b800..bfc3bdb 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineService.java @@ -61,7 +61,10 @@ import org.jclouds.googlecomputeengine.GoogleComputeEngineApi; import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions; import org.jclouds.googlecomputeengine.config.UserProject; +import org.jclouds.googlecomputeengine.domain.Firewall; +import org.jclouds.googlecomputeengine.domain.Network; import org.jclouds.googlecomputeengine.domain.Operation; +import org.jclouds.googlecomputeengine.features.FirewallApi; import org.jclouds.http.HttpResponse; import org.jclouds.scriptbuilder.functions.InitAdminAccess; @@ -69,6 +72,7 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListeningExecutorService; /** @@ -146,21 +150,34 @@ protected synchronized void cleanUpIncidentalResourcesOfDeadNodes(Set operation = new AtomicReference(api.getFirewallApiForProject(project.get()) - .delete(resourceName)); + final Network network = api.getNetworkApiForProject(project.get()).get(resourceName); + FirewallApi firewallApi = api.getFirewallApiForProject(project.get()); + Predicate firewallBelongsToNetwork = new Predicate() { + @Override + public boolean apply(Firewall input) { + return input != null && input.getNetwork().equals(network.getSelfLink()); + } + }; + + Set> operations = Sets.newHashSet(); + for (Firewall firewall : firewallApi.list().concat().filter(firewallBelongsToNetwork)) { + operations.add(new AtomicReference(firewallApi.delete(firewall.getName()))); + } - retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, - MILLISECONDS).apply(operation); + for (AtomicReference operation : operations) { + retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, + MILLISECONDS).apply(operation); - if (operation.get().getHttpError().isPresent()) { - HttpResponse response = operation.get().getHttpError().get(); - logger.warn("delete orphaned firewall failed. Http Error Code: " + response.getStatusCode() + - " HttpError: " + response.getMessage()); + if (operation.get().getHttpError().isPresent()) { + HttpResponse response = operation.get().getHttpError().get(); + logger.warn("delete orphaned firewall %s failed. Http Error Code: %d HttpError: %s", + operation.get().getTargetId(), response.getStatusCode(), response.getMessage()); + } } - operation = new AtomicReference(api.getNetworkApiForProject(project.get()).delete(resourceName)); + AtomicReference operation = new AtomicReference(api.getNetworkApiForProject(project.get()).delete(resourceName)); retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(operation); diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java index b949f13..ecc2e63 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java @@ -29,12 +29,14 @@ import java.net.URI; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Resource; import javax.inject.Named; +import com.google.common.primitives.Ints; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.domain.Hardware; @@ -44,6 +46,7 @@ import org.jclouds.domain.Location; import org.jclouds.domain.LoginCredentials; import org.jclouds.googlecomputeengine.GoogleComputeEngineApi; +import org.jclouds.googlecomputeengine.compute.functions.FirewallTagNamingConvention; import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions; import org.jclouds.googlecomputeengine.config.UserProject; import org.jclouds.googlecomputeengine.domain.Image; @@ -55,6 +58,7 @@ import org.jclouds.googlecomputeengine.domain.Operation; import org.jclouds.googlecomputeengine.domain.SlashEncodedIds; import org.jclouds.googlecomputeengine.domain.Zone; +import org.jclouds.googlecomputeengine.features.InstanceApi; import org.jclouds.http.HttpResponse; import org.jclouds.logging.Logger; @@ -85,6 +89,7 @@ private final Predicate> retryOperationDonePredicate; private final long operationCompleteCheckInterval; private final long operationCompleteCheckTimeout; + private final FirewallTagNamingConvention.Factory firewallTagNamingConvention; @Inject public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api, @@ -95,7 +100,8 @@ public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api, @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval, @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout, @Memoized Supplier> zones, - @Memoized Supplier> hardwareMap) { + @Memoized Supplier> hardwareMap, + FirewallTagNamingConvention.Factory firewallTagNamingConvention) { this.api = checkNotNull(api, "google compute api"); this.userProject = checkNotNull(userProject, "user project name"); this.metatadaFromTemplateOptions = checkNotNull(metatadaFromTemplateOptions, @@ -108,6 +114,7 @@ public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api, operationCompleteCheckInterval, TimeUnit.MILLISECONDS); this.zones = checkNotNull(zones, "zones"); this.hardwareMap = checkNotNull(hardwareMap, "hardwareMap"); + this.firewallTagNamingConvention = checkNotNull(firewallTagNamingConvention, "firewallTagNamingConvention"); } @Override @@ -138,8 +145,9 @@ public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api, instanceTemplate.serviceAccounts(options.getServiceAccounts()); instanceTemplate.image(checkNotNull(template.getImage().getUri(), "image URI is null")); - Operation operation = api.getInstanceApiForProject(userProject.get()) - .createInZone(name, template.getLocation().getId(), instanceTemplate); + final InstanceApi instanceApi = api.getInstanceApiForProject(userProject.get()); + final String zone = template.getLocation().getId(); + Operation operation = instanceApi.createInZone(name, zone, instanceTemplate); if (options.shouldBlockUntilRunning()) { waitOperationDone(operation); @@ -151,14 +159,13 @@ public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api, retry(new Predicate>() { @Override public boolean apply(AtomicReference input) { - input.set(api.getInstanceApiForProject(userProject.get()).getInZone(template.getLocation().getId(), - name)); + input.set(instanceApi.getInZone(zone, name)); return input.get() != null; } }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance); - if (options.getTags().size() > 0) { - Operation tagsOperation = api.getInstanceApiForProject(userProject.get()).setTagsInZone(template.getLocation().getId(), + if (!options.getTags().isEmpty()) { + Operation tagsOperation = instanceApi.setTagsInZone(zone, name, options.getTags(), instance.get().getTags().getFingerprint()); waitOperationDone(tagsOperation); @@ -166,14 +173,27 @@ public boolean apply(AtomicReference input) { retry(new Predicate>() { @Override public boolean apply(AtomicReference input) { - input.set(api.getInstanceApiForProject(userProject.get()).getInZone(template.getLocation().getId(), - name)); + input.set(instanceApi.getInZone(zone, name)); return input.get() != null; } }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance); } - InstanceInZone instanceInZone = new InstanceInZone(instance.get(), template.getLocation().getId()); + // Add tags for security groups + final FirewallTagNamingConvention naming = firewallTagNamingConvention.get(group); + Set tags = FluentIterable.from(Ints.asList(options.getInboundPorts())) + .transform(new Function(){ + @Override + public String apply(Integer input) { + return input != null + ? naming.name(input) + : null; + } + }) + .toSet(); + instanceApi.setTagsInZone(zone, instance.get().getName(), tags, instance.get().getTags().getFingerprint()); + + InstanceInZone instanceInZone = new InstanceInZone(instance.get(), zone); return new NodeAndInitialCredentials(instanceInZone, instanceInZone.slashEncode(), credentials); } diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java index b0c04be..08cc9d3 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java @@ -29,6 +29,7 @@ import javax.inject.Named; import javax.inject.Singleton; +import com.google.inject.Scopes; import org.jclouds.collect.Memoized; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceAdapter; @@ -43,6 +44,7 @@ import org.jclouds.googlecomputeengine.compute.GoogleComputeEngineService; import org.jclouds.googlecomputeengine.compute.GoogleComputeEngineServiceAdapter; import org.jclouds.googlecomputeengine.compute.functions.BuildInstanceMetadata; +import org.jclouds.googlecomputeengine.compute.functions.FirewallTagNamingConvention; import org.jclouds.googlecomputeengine.compute.functions.GoogleComputeEngineImageToImage; import org.jclouds.googlecomputeengine.compute.functions.InstanceInZoneToNodeMetadata; import org.jclouds.googlecomputeengine.compute.functions.MachineTypeInZoneToHardware; @@ -119,6 +121,7 @@ protected void configure() { install(new LocationsFromComputeServiceAdapterModule() {}); + bind(FirewallTagNamingConvention.Factory.class).in(Scopes.SINGLETON); } @Provides diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java new file mode 100644 index 0000000..0f7bd92 --- /dev/null +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/FirewallTagNamingConvention.java @@ -0,0 +1,64 @@ +/* + * 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.googlecomputeengine.compute.functions; + +import com.google.common.base.Predicate; +import org.jclouds.compute.functions.GroupNamingConvention; + +import javax.inject.Inject; + +/** + * The convention for naming instance tags that firewall rules recognise. + * + * @author richardcloudsoft + */ +public class FirewallTagNamingConvention { + + public static class Factory { + + private final GroupNamingConvention.Factory namingConvention; + + @Inject + public Factory(GroupNamingConvention.Factory namingConvention) { + this.namingConvention = namingConvention; + } + + public FirewallTagNamingConvention get(String groupName) { + return new FirewallTagNamingConvention(namingConvention.create().sharedNameForGroup(groupName)); + } + } + + private final String sharedResourceName; + + public FirewallTagNamingConvention(String sharedResourceName) { + this.sharedResourceName = sharedResourceName; + } + + public String name(int port) { + return String.format("%s-port-%s", sharedResourceName, port); + } + + public Predicate isFirewallTag() { + return new Predicate() { + @Override + public boolean apply(String input) { + return input != null && input.startsWith(sharedResourceName + "-port-"); + } + }; + } + +} diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/InstanceInZoneToNodeMetadata.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/InstanceInZoneToNodeMetadata.java index b59dfcc..03f6e68 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/InstanceInZoneToNodeMetadata.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/functions/InstanceInZoneToNodeMetadata.java @@ -36,7 +36,9 @@ import org.jclouds.googlecomputeengine.domain.SlashEncodedIds; import com.google.common.base.Function; +import com.google.common.base.Predicates; import com.google.common.base.Supplier; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; /** @@ -51,18 +53,21 @@ private final Supplier> images; private final Supplier> hardwares; private final Supplier> locations; + private final FirewallTagNamingConvention.Factory firewallTagNamingConvention; @Inject public InstanceInZoneToNodeMetadata(Map toPortableNodeStatus, GroupNamingConvention.Factory namingConvention, @Memoized Supplier> images, @Memoized Supplier> hardwares, - @Memoized Supplier> locations) { + @Memoized Supplier> locations, + FirewallTagNamingConvention.Factory firewallTagNamingConvention) { this.toPortableNodeStatus = toPortableNodeStatus; this.nodeNamingConvention = namingConvention.createWithoutPrefix(); this.images = images; this.hardwares = hardwares; this.locations = locations; + this.firewallTagNamingConvention = checkNotNull(firewallTagNamingConvention, "firewallTagNamingConvention"); } @Override @@ -72,6 +77,10 @@ public NodeMetadata apply(InstanceInZone instanceInZone) { Image image = checkNotNull(imagesMap.get(checkNotNull(input.getImage(), "image")), "no image for %s. images: %s", input.getImage(), imagesMap.values()); + String group = nodeNamingConvention.groupInUniqueNameOrNull(input.getName()); + FluentIterable tags = FluentIterable.from(input.getTags().getItems()) + .filter(Predicates.not(firewallTagNamingConvention.get(group).isFirewallTag())); + return new NodeMetadataBuilder() .id(SlashEncodedIds.fromTwoIds(checkNotNull(locations.get().get(input.getZone()), "location for %s", input.getZone()).getId(), input.getName()).slashEncode()) @@ -84,10 +93,10 @@ public NodeMetadata apply(InstanceInZone instanceInZone) { input.getMachineType().toString())) .operatingSystem(image.getOperatingSystem()) .status(toPortableNodeStatus.get(input.getStatus())) - .tags(input.getTags().getItems()) + .tags(tags) .uri(input.getSelfLink()) .userMetadata(input.getMetadata()) - .group(nodeNamingConvention.groupInUniqueNameOrNull(input.getName())) + .group(group) .privateAddresses(collectPrivateAddresses(input)) .publicAddresses(collectPublicAddresses(input)) .build(); diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 5380313..40a9719 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/strategy/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -31,6 +31,7 @@ import javax.inject.Inject; import javax.inject.Named; +import com.google.common.collect.Sets; import org.jclouds.Constants; import org.jclouds.compute.config.CustomizationResponse; import org.jclouds.compute.domain.NodeMetadata; @@ -40,11 +41,13 @@ import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; import org.jclouds.compute.strategy.ListNodesStrategy; import org.jclouds.googlecomputeengine.GoogleComputeEngineApi; +import org.jclouds.googlecomputeengine.compute.functions.FirewallTagNamingConvention; import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions; import org.jclouds.googlecomputeengine.config.UserProject; import org.jclouds.googlecomputeengine.domain.Firewall; import org.jclouds.googlecomputeengine.domain.Network; import org.jclouds.googlecomputeengine.domain.Operation; +import org.jclouds.googlecomputeengine.features.FirewallApi; import org.jclouds.googlecomputeengine.options.FirewallOptions; import com.google.common.base.Predicate; @@ -68,6 +71,7 @@ private final Predicate> operationDonePredicate; private final long operationCompleteCheckInterval; private final long operationCompleteCheckTimeout; + private final FirewallTagNamingConvention.Factory firewallTagNamingConvention; @Inject protected CreateNodesWithGroupEncodedIntoNameThenAddToSet( @@ -82,7 +86,8 @@ protected CreateNodesWithGroupEncodedIntoNameThenAddToSet( @UserProject Supplier userProject, @Named("global") Predicate> operationDonePredicate, @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval, - @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout) { + @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout, + FirewallTagNamingConvention.Factory firewallTagNamingConvention) { super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor, customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); @@ -93,6 +98,7 @@ protected CreateNodesWithGroupEncodedIntoNameThenAddToSet( this.operationCompleteCheckTimeout = checkNotNull(operationCompleteCheckTimeout, "operation completed check timeout"); this.operationDonePredicate = operationDonePredicate; + this.firewallTagNamingConvention = checkNotNull(firewallTagNamingConvention, "firewallTagNamingConvention"); } @Override @@ -110,7 +116,7 @@ protected CreateNodesWithGroupEncodedIntoNameThenAddToSet( // get or create the network and create a firewall with the users configuration Network network = getOrCreateNetwork(templateOptions, sharedResourceName); - getOrCreateFirewall(templateOptions, network, sharedResourceName); + getOrCreateFirewalls(templateOptions, network, firewallTagNamingConvention.get(group)); templateOptions.network(network.getSelfLink()); return super.execute(group, count, mutableTemplate, goodNodes, badNodes, customizationResponses); @@ -145,51 +151,45 @@ private Network getOrCreateNetwork(GoogleComputeEngineTemplateOptions templateOp } /** - * Tries to find if a firewall already exists for this group, if not it creates one. - * - * @see org.jclouds.googlecomputeengine.features.FirewallAsyncApi#patch(String, org.jclouds.googlecomputeengine.options.FirewallOptions) + * Ensures that a firewall exists for every inbound port that the instance requests. + *

+ * For each port, there must be a firewall with a name following the {@link FirewallTagNamingConvention}, + * with a target tag also following the {@link FirewallTagNamingConvention}, which opens the requested port + * for all sources on both TCP and UDP protocols. */ - private void getOrCreateFirewall(GoogleComputeEngineTemplateOptions templateOptions, Network network, - String sharedResourceName) { + private void getOrCreateFirewalls(GoogleComputeEngineTemplateOptions templateOptions, Network network, + FirewallTagNamingConvention naming) { - Firewall firewall = api.getFirewallApiForProject(userProject.get()).get(sharedResourceName); + String projectName = userProject.get(); + FirewallApi firewallApi = api.getFirewallApiForProject(projectName); + Set> operations = Sets.newHashSet(); - if (firewall != null) { - return; - } - - ImmutableSet.Builder rules = ImmutableSet.builder(); - - Firewall.Rule.Builder tcpRule = Firewall.Rule.builder(); - tcpRule.IPProtocol(Firewall.Rule.IPProtocol.TCP); - Firewall.Rule.Builder udpRule = Firewall.Rule.builder(); - udpRule.IPProtocol(Firewall.Rule.IPProtocol.UDP); for (Integer port : templateOptions.getInboundPorts()) { - tcpRule.addPort(port); - udpRule.addPort(port); + String name = naming.name(port); + Firewall firewall = firewallApi.get(name); + if (firewall == null) { + ImmutableSet rules = ImmutableSet.of(Firewall.Rule.permitTcpRule(port), Firewall.Rule.permitUdpRule(port)); + FirewallOptions firewallOptions = new FirewallOptions() + .name(name) + .network(network.getSelfLink()) + .allowedRules(rules) + .sourceTags(templateOptions.getTags()) + .sourceRanges(of(DEFAULT_INTERNAL_NETWORK_RANGE, EXTERIOR_RANGE)) + .targetTags(ImmutableSet.of(name)); + AtomicReference operation = new AtomicReference(firewallApi.createInNetwork( + firewallOptions.getName(), + network.getSelfLink(), + firewallOptions)); + operations.add(operation); + } } - rules.add(tcpRule.build()); - rules.add(udpRule.build()); - - - FirewallOptions options = new FirewallOptions() - .name(sharedResourceName) - .network(network.getSelfLink()) - .sourceTags(templateOptions.getTags()) - .allowedRules(rules.build()) - .sourceRanges(of(DEFAULT_INTERNAL_NETWORK_RANGE, EXTERIOR_RANGE)); - AtomicReference operation = new AtomicReference(api.getFirewallApiForProject(userProject - .get()).createInNetwork( - sharedResourceName, - network.getSelfLink(), - options)); + for (AtomicReference operation : operations) { + retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, + MILLISECONDS).apply(operation); - retry(operationDonePredicate, operationCompleteCheckTimeout, operationCompleteCheckInterval, - MILLISECONDS).apply(operation); - - checkState(!operation.get().getHttpError().isPresent(), "Could not create firewall, operation failed" + operation); + checkState(!operation.get().getHttpError().isPresent(),"Could not create firewall, operation failed" + operation); + } } - } diff --git a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Firewall.java b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Firewall.java index feef148..efbd886 100644 --- a/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Firewall.java +++ b/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Firewall.java @@ -266,6 +266,12 @@ public static IPProtocol fromValue(String protocol) { private final IPProtocol ipProtocol; private final RangeSet ports; + /* Some handy shortcuts */ + public static Rule permitTcpRule(Integer start, Integer end) { return Rule.builder().IPProtocol(Firewall.Rule.IPProtocol.TCP).addPortRange(start, end).build(); } + public static Rule permitTcpRule(Integer port) { return Rule.builder().IPProtocol(Firewall.Rule.IPProtocol.TCP).addPort(port).build(); } + public static Rule permitUdpRule(Integer start, Integer end) { return Rule.builder().IPProtocol(Firewall.Rule.IPProtocol.UDP).addPortRange(start, end).build(); } + public static Rule permitUdpRule(Integer port) { return Rule.builder().IPProtocol(Firewall.Rule.IPProtocol.UDP).addPort(port).build(); } + @ConstructorProperties({ "IPProtocol", "ports" }) diff --git a/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceExpectTest.java b/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceExpectTest.java index ef88eb8..f0cf111 100644 --- a/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceExpectTest.java +++ b/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceExpectTest.java @@ -269,6 +269,26 @@ public void testNetworksAndFirewallDeletedWhenAllGroupNodesAreTerminated() throw .addHeader("Accept", "application/json") .addHeader("Authorization", "Bearer " + TOKEN).build(); + HttpRequest getNetworkRequest = HttpRequest.builder() + .method("GET") + .endpoint("https://www.googleapis" + + ".com/compute/v1beta15/projects/myproject/global/networks/jclouds-test-delete") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + TOKEN).build(); + + HttpResponse getNetworkResponse = HttpResponse.builder().statusCode(200) + .payload(staticPayloadFromResource("/GoogleComputeEngineServiceExpectTest/network_get.json")).build(); + + HttpRequest listFirewallsRequest = HttpRequest.builder() + .method("GET") + .endpoint("https://www.googleapis" + + ".com/compute/v1beta15/projects/myproject/global/firewalls") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + TOKEN).build(); + + HttpResponse listFirewallsResponse = HttpResponse.builder().statusCode(200) + .payload(staticPayloadFromResource("/GoogleComputeEngineServiceExpectTest/firewall_list.json")).build(); + HttpRequest deleteNetworkReqquest = HttpRequest.builder() .method("DELETE") .endpoint("https://www.googleapis" + @@ -289,6 +309,8 @@ public void testNetworksAndFirewallDeletedWhenAllGroupNodesAreTerminated() throw .add(GET_ZONE_OPERATION_REQUEST) .add(getInstanceRequestForInstance("test-delete-networks")) .add(LIST_INSTANCES_REQUEST) + .add(getNetworkRequest) + .add(listFirewallsRequest) .add(deleteFirewallRequest) .add(GET_GLOBAL_OPERATION_REQUEST) .add(deleteNetworkReqquest) @@ -313,6 +335,8 @@ public void testNetworksAndFirewallDeletedWhenAllGroupNodesAreTerminated() throw .add(getListInstancesResponseForSingleInstanceAndNetworkAndStatus("test-delete-networks", "test-network", Instance .Status.TERMINATED.name())) + .add(getNetworkResponse) + .add(listFirewallsResponse) .add(SUCESSFULL_OPERATION_RESPONSE) .add(GET_GLOBAL_OPERATION_RESPONSE) .add(SUCESSFULL_OPERATION_RESPONSE) @@ -363,6 +387,38 @@ public void testCreateNodeWhenNetworkNorFirewallExistDoesNotExist() throws RunNo HttpResponse getInstanceResponse = HttpResponse.builder().statusCode(200) .payload(payloadFromStringWithContentType(payload, "application/json")).build(); + HttpRequest getFirewallRequest = HttpRequest + .builder() + .method("GET") + .endpoint("https://www.googleapis" + + ".com/compute/v1beta15/projects/myproject/global/firewalls/jclouds-test-port-22") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + TOKEN).build(); + + HttpRequest insertFirewallRequest = HttpRequest + .builder() + .method("POST") + .endpoint("https://www.googleapis.com/compute/v1beta15/projects/myproject/global/firewalls") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + TOKEN) + .payload(payloadFromStringWithContentType("{\"name\":\"jclouds-test-port-22\",\"network\":\"https://www.googleapis" + + ".com/compute/v1beta15/projects/myproject/global/networks/jclouds-test\"," + + "\"sourceRanges\":[\"10.0.0.0/8\",\"0.0.0.0/0\"],\"targetTags\":[\"jclouds-test-port-22\"],\"allowed\":[{\"IPProtocol\":\"tcp\"," + + "\"ports\":[\"22\"]}," + + "{\"IPProtocol\":\"udp\",\"ports\":[\"22\"]}]}", + MediaType.APPLICATION_JSON)) + .build(); + + HttpRequest setTagsRequest = HttpRequest + .builder() + .method("POST") + .endpoint("https://www.googleapis.com/compute/v1beta15/projects/myproject/zones/us-central1-a/instances/test-1/setTags") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + TOKEN) + .payload(payloadFromStringWithContentType("{\"items\":[\"jclouds-test-port-22\"],\"fingerprint\":\"abcd\"}", + MediaType.APPLICATION_JSON)) + .build(); + List orderedRequests = ImmutableList.builder() .add(requestForScopes(COMPUTE_READONLY_SCOPE)) .add(GET_PROJECT_REQUEST) @@ -376,8 +432,8 @@ public void testCreateNodeWhenNetworkNorFirewallExistDoesNotExist() throws RunNo .add(INSERT_NETWORK_REQUEST) .add(GET_GLOBAL_OPERATION_REQUEST) .add(GET_NETWORK_REQUEST) - .add(GET_FIREWALL_REQUEST) - .add(INSERT_FIREWALL_REQUEST) + .add(getFirewallRequest) + .add(insertFirewallRequest) .add(GET_GLOBAL_OPERATION_REQUEST) .add(LIST_INSTANCES_REQUEST) .add(LIST_PROJECT_IMAGES_REQUEST) @@ -389,6 +445,8 @@ public void testCreateNodeWhenNetworkNorFirewallExistDoesNotExist() throws RunNo .add(SET_TAGS_REQUEST) .add(GET_ZONE_OPERATION_REQUEST) .add(getInstanceRequestForInstance("test-1")) + .add(setTagsRequest) + .add(setTagsRequest) .build(); List orderedResponses = ImmutableList.builder() @@ -417,6 +475,8 @@ public void testCreateNodeWhenNetworkNorFirewallExistDoesNotExist() throws RunNo .add(SET_TAGS_RESPONSE) .add(GET_ZONE_OPERATION_RESPONSE) .add(getInstanceResponse) + .add(SUCESSFULL_OPERATION_RESPONSE) + .add(SUCESSFULL_OPERATION_RESPONSE) .build(); diff --git a/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/firewall_list.json b/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/firewall_list.json new file mode 100644 index 0000000..94c8fa6 --- /dev/null +++ b/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/firewall_list.json @@ -0,0 +1,37 @@ +{ + "kind": "compute#firewallList", + "id": "projects/google/firewalls", + "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/google/global/firewalls", + "items": [ + { + + "kind": "compute#firewall", + "id": "12862241031274216284", + "creationTimestamp": "2012-04-13T03:05:02.855", + "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/myproject/global/firewalls/jclouds-test-delete", + "name": "jclouds-test-delete", + "description": "Internal traffic from default allowed", + "network": "https://www.googleapis.com/compute/v1beta15/projects/myproject/global/networks/jclouds-test-delete", + "sourceRanges": [ + "10.0.0.0/8" + ], + "allowed": [ + { + "IPProtocol": "tcp", + "ports": [ + "1-65535" + ] + }, + { + "IPProtocol": "udp", + "ports": [ + "1-65535" + ] + }, + { + "IPProtocol": "icmp" + } + ] + } + ] +} \ No newline at end of file diff --git a/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/network_get.json b/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/network_get.json new file mode 100644 index 0000000..3c9e939 --- /dev/null +++ b/google-compute-engine/src/test/resources/GoogleComputeEngineServiceExpectTest/network_get.json @@ -0,0 +1,10 @@ +{ + "kind": "compute#network", + "id": "13024414170909937976", + "creationTimestamp": "2012-10-24T20:13:19.967", + "selfLink": "https://www.googleapis.com/compute/v1beta15/projects/myproject/global/networks/jclouds-test-delete", + "name": "jclouds-test-delete", + "description": "Default network for the project", + "IPv4Range": "10.0.0.0/8", + "gatewayIPv4": "10.0.0.1" +} \ No newline at end of file -- 1.8.4