From c9b5b1db8779b795cd537c8ba592f3d158c67f8b Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 20 Mar 2012 22:37:18 -0700 Subject: [PATCH] openstack implicit security group integration with compute service; ensured hpcloud doesn't pickup kernel or ramdisk images --- .../compute/predicates/NodePredicates.java | 18 +++ core/src/main/resources/rest.properties | 2 +- .../HPCloudComputeContextBuilder.java} | 34 ++-- .../HPCloudComputePropertiesBuilder.java | 7 +- .../compute/HPCloudComputeServiceAdapter.java | 45 ++++++ .../HPCloudComputeServiceContextModule.java | 37 +++++ ...HPCloudComputeTemplateBuilderLiveTest.java | 1 + .../nova/v1_1/NovaPropertiesBuilder.java | 1 - .../nova/v1_1/compute/NovaComputeService.java | 147 ++++++++++++++++++ .../compute/NovaComputeServiceAdapter.java | 29 ++-- .../NovaComputeServiceContextModule.java | 43 +++-- .../AllocateAndAddFloatingIpToNode.java | 2 +- ....java => CreateSecurityGroupIfNeeded.java} | 32 ++-- .../functions/FlavorInZoneToHardware.java | 2 +- .../compute/functions/ImageInZoneToImage.java | 8 +- .../functions/OrphanedGroupsByZoneId.java | 79 ++++++++++ ...RemoveFloatingIpFromNodeAndDeallocate.java | 2 +- .../functions/ServerInZoneToNodeMetadata.java | 35 ++++- .../CreateOrUpdateSecurityGroupAsNeeded.java | 83 ---------- .../loaders/FindSecurityGroupOrCreate.java | 88 +++++++++++ .../loaders/LoadFloatingIpsForInstance.java | 2 +- .../predicates/AllNodesInGroupTerminated.java | 58 +++++++ ...sWithGroupEncodedIntoNameThenAddToSet.java | 47 +++++- .../openstack/nova/v1_1/domain/Image.java | 86 ++++++---- .../openstack/nova/v1_1/domain/Server.java | 85 ++++++---- .../nova/v1_1/domain/ServerStatus.java | 61 -------- .../zonescoped}/FlavorInZone.java | 2 +- .../zonescoped}/ImageInZone.java | 14 +- .../zonescoped}/SecurityGroupInZone.java | 2 +- .../zonescoped}/ServerInZone.java | 2 +- .../zonescoped}/ZoneAndId.java | 2 +- .../zonescoped}/ZoneAndName.java | 22 ++- .../ZoneSecurityGroupNameAndPorts.java | 14 +- .../nova/v1_1/handlers/NovaErrorHandler.java | 2 + ...indSecurityGroupWithNameAndReturnTrue.java | 4 +- .../nova/v1_1/predicates/ImagePredicates.java | 57 +++++++ .../predicates/SecurityGroupPredicates.java | 56 +++++++ .../nova/v1_1/NovaErrorHandlerTest.java | 11 ++ .../NovaComputeServiceAdapterExpectTest.java | 2 +- ...cateAndAddFloatingIpToNodeExpectTest.java} | 2 +- .../functions/FlavorInZoneToHardwareTest.java | 2 +- .../functions/ImageInZoneToImageTest.java | 2 +- .../functions/OrphanedGroupsByZoneIdTest.java | 108 +++++++++++++ .../ServerInZoneToNodeMetadataTest.java | 8 +- .../FindSecurityGroupOrCreateTest.java | 145 +++++++++++++++++ .../LoadFloatingIpsForInstanceTest.java | 2 +- .../extensions/FloatingIPClientLiveTest.java | 4 +- ...a => CreateSecurityGroupIfNeededTest.java} | 80 +++++++--- ...yGroupWithNameAndReturnTrueExpectTest.java | 4 +- .../v1_1/parse/ParseCreatedServerTest.java | 4 +- .../nova/v1_1/parse/ParseImageTest.java | 6 +- .../nova/v1_1/parse/ParseServerTest.java | 6 +- .../v1_1/predicates/ImagePredicatesTest.java | 46 ++++++ .../SecurityGroupPredicatesTest.java | 44 ++++++ ...p_list_details_computeservice_typical.json | 53 +++++++ 55 files changed, 1380 insertions(+), 360 deletions(-) rename labs/{openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ImageStatus.java => hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeContextBuilder.java} (51%) create mode 100644 labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeServiceAdapter.java create mode 100644 labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/config/HPCloudComputeServiceContextModule.java create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/{CreateSecurityGroupInZone.java => CreateSecurityGroupIfNeeded.java} (70%) create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneId.java delete mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/CreateOrUpdateSecurityGroupAsNeeded.java create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreate.java create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/AllNodesInGroupTerminated.java delete mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/FlavorInZone.java (96%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/ImageInZone.java (79%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/SecurityGroupInZone.java (96%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/ServerInZone.java (96%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/ZoneAndId.java (97%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/ZoneAndName.java (83%) rename labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/{compute/domain => domain/zonescoped}/ZoneSecurityGroupNameAndPorts.java (84%) create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicates.java create mode 100644 labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicates.java rename labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/{AllocateAndAddFloatingIpToNodeTest.java => AllocateAndAddFloatingIpToNodeExpectTest.java} (98%) create mode 100644 labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneIdTest.java create mode 100644 labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreateTest.java rename labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/{CreateSecurityGroupInZoneExpectTest.java => CreateSecurityGroupIfNeededTest.java} (69%) create mode 100644 labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicatesTest.java create mode 100644 labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicatesTest.java create mode 100644 labs/openstack-nova/src/test/resources/securitygroup_list_details_computeservice_typical.json diff --git a/compute/src/main/java/org/jclouds/compute/predicates/NodePredicates.java b/compute/src/main/java/org/jclouds/compute/predicates/NodePredicates.java index 5acb6a450a..f1cc800cdc 100644 --- a/compute/src/main/java/org/jclouds/compute/predicates/NodePredicates.java +++ b/compute/src/main/java/org/jclouds/compute/predicates/NodePredicates.java @@ -204,7 +204,25 @@ public class NodePredicates { } }; } + + /** + * Return nodes who have a value for {@link NodeMetadata#getGroup} + * + */ + public static Predicate hasGroup() { + return new Predicate() { + @Override + public boolean apply(NodeMetadata nodeMetadata) { + return nodeMetadata.getGroup() != null; + } + @Override + public String toString() { + return "hasGroup()"; + } + }; + } + /** * Return nodes with specified group that are in the NODE_RUNNING state. * diff --git a/core/src/main/resources/rest.properties b/core/src/main/resources/rest.properties index ec8471e594..c5db5e495c 100644 --- a/core/src/main/resources/rest.properties +++ b/core/src/main/resources/rest.properties @@ -243,5 +243,5 @@ filesystem.propertiesbuilder=org.jclouds.filesystem.FilesystemBlobStorePropertie hpcloud-objectstorage-lvs.contextbuilder=org.jclouds.hpcloud.objectstorage.lvs.HPCloudObjectStorageLasVegasContextBuilder hpcloud-objectstorage-lvs.propertiesbuilder=org.jclouds.hpcloud.objectstorage.lvs.HPCloudObjectStorageLasVegasPropertiesBuilder -hpcloud-compute.contextbuilder=org.jclouds.openstack.nova.v1_1.NovaContextBuilder +hpcloud-compute.contextbuilder=org.jclouds.hpcloud.compute.HPCloudComputeContextBuilder hpcloud-compute.propertiesbuilder=org.jclouds.hpcloud.compute.HPCloudComputePropertiesBuilder diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ImageStatus.java b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeContextBuilder.java similarity index 51% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ImageStatus.java rename to labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeContextBuilder.java index 3853022f0d..d0ad203eff 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ImageStatus.java +++ b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeContextBuilder.java @@ -16,32 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.domain; +package org.jclouds.hpcloud.compute; + +import java.util.List; +import java.util.Properties; + +import org.jclouds.hpcloud.compute.config.HPCloudComputeServiceContextModule; +import org.jclouds.openstack.nova.v1_1.NovaContextBuilder; + +import com.google.inject.Module; /** - * In-flight images will have the status attribute set to SAVING and the - * conditional progress element (0-100% completion) will also be returned. Other - * possible values for the status attribute include: UNKNOWN, ACTIVE, SAVING, - * ERROR, and DELETED. Images with an ACTIVE status are available for install. - * The optional minDisk and minRam attributes set the minimum disk and RAM - * requirements needed to create a server with the image. * * @author Adrian Cole */ -public enum ImageStatus { +public class HPCloudComputeContextBuilder extends NovaContextBuilder { - UNRECOGNIZED, UNKNOWN, ACTIVE, SAVING, ERROR, DELETED; - - public String value() { - return name(); + public HPCloudComputeContextBuilder(Properties props) { + super(props); } - public static ImageStatus fromValue(String v) { - try { - return valueOf(v); - } catch (IllegalArgumentException e) { - return UNRECOGNIZED; - } + @Override + protected void addContextModule(List modules) { + modules.add(new HPCloudComputeServiceContextModule()); } - } diff --git a/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputePropertiesBuilder.java b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputePropertiesBuilder.java index e252d09b6f..1b8dbbfd6b 100644 --- a/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputePropertiesBuilder.java +++ b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputePropertiesBuilder.java @@ -20,11 +20,12 @@ package org.jclouds.hpcloud.compute; import static org.jclouds.Constants.PROPERTY_ENDPOINT; import static org.jclouds.Constants.PROPERTY_ISO3166_CODES; +import static org.jclouds.compute.reference.ComputeServiceConstants.PROPERTY_TIMEOUT_NODE_TERMINATED; +import static org.jclouds.openstack.nova.v1_1.reference.NovaConstants.PROPERTY_NOVA_AUTO_ALLOCATE_FLOATING_IPS; import java.util.Properties; import org.jclouds.openstack.nova.v1_1.NovaPropertiesBuilder; -import org.jclouds.openstack.nova.v1_1.reference.NovaConstants; /** * @@ -37,7 +38,9 @@ public class HPCloudComputePropertiesBuilder extends NovaPropertiesBuilder { Properties properties = super.defaultProperties(); properties.setProperty(PROPERTY_ISO3166_CODES, "US-NV"); properties.setProperty(PROPERTY_ENDPOINT, "https://region-a.geo-1.identity.hpcloudsvc.com:35357"); - properties.setProperty(NovaConstants.PROPERTY_NOVA_AUTO_ALLOCATE_FLOATING_IPS, "true"); + properties.setProperty(PROPERTY_NOVA_AUTO_ALLOCATE_FLOATING_IPS, "true"); + // deallocating ip addresses can take a while + properties.setProperty(PROPERTY_TIMEOUT_NODE_TERMINATED, 60 * 1000 + ""); return properties; } diff --git a/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeServiceAdapter.java b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeServiceAdapter.java new file mode 100644 index 0000000000..b5ddf595c0 --- /dev/null +++ b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/HPCloudComputeServiceAdapter.java @@ -0,0 +1,45 @@ +package org.jclouds.hpcloud.compute; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.location.Zone; +import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.compute.NovaComputeServiceAdapter; +import org.jclouds.openstack.nova.v1_1.compute.functions.RemoveFloatingIpFromNodeAndDeallocate; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.Iterables; + +/** + * + * @author Adrian Cole + */ +public class HPCloudComputeServiceAdapter extends NovaComputeServiceAdapter { + + @Inject + public HPCloudComputeServiceAdapter(NovaClient novaClient, @Zone Supplier> zoneIds, + RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate) { + super(novaClient, zoneIds, removeFloatingIpFromNodeAndDeallocate); + } + + @Override + public Iterable listImages() { + return Iterables.filter(super.listImages(), new Predicate() { + + @Override + public boolean apply(ImageInZone arg0) { + String imageName = arg0.getImage().getName(); + return imageName.indexOf("Kernel") == -1 && imageName.indexOf("Ramdisk") == -1; + } + + @Override + public String toString() { + return "notKernelOrRamdisk"; + } + }); + } +} diff --git a/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/config/HPCloudComputeServiceContextModule.java b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/config/HPCloudComputeServiceContextModule.java new file mode 100644 index 0000000000..8b8ad4acee --- /dev/null +++ b/labs/hpcloud-compute/src/main/java/org/jclouds/hpcloud/compute/config/HPCloudComputeServiceContextModule.java @@ -0,0 +1,37 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.hpcloud.compute.config; + +import org.jclouds.hpcloud.compute.HPCloudComputeServiceAdapter; +import org.jclouds.openstack.nova.v1_1.compute.NovaComputeServiceAdapter; +import org.jclouds.openstack.nova.v1_1.compute.config.NovaComputeServiceContextModule; + +/** + * + * @author Adrian Cole + */ +public class HPCloudComputeServiceContextModule extends NovaComputeServiceContextModule { + + @Override + protected void configure() { + super.configure(); + bind(NovaComputeServiceAdapter.class).to(HPCloudComputeServiceAdapter.class); + } + +} \ No newline at end of file diff --git a/labs/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeTemplateBuilderLiveTest.java b/labs/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeTemplateBuilderLiveTest.java index 07f6ed4dc7..4f3c760046 100644 --- a/labs/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeTemplateBuilderLiveTest.java +++ b/labs/hpcloud-compute/src/test/java/org/jclouds/hpcloud/compute/compute/HPCloudComputeTemplateBuilderLiveTest.java @@ -73,6 +73,7 @@ public class HPCloudComputeTemplateBuilderLiveTest extends BaseTemplateBuilderLi assertEquals(defaultTemplate.getImage().getOperatingSystem().is64Bit(), true); assertEquals(defaultTemplate.getImage().getOperatingSystem().getVersion(), "11.10"); assertEquals(defaultTemplate.getImage().getOperatingSystem().getFamily(), OsFamily.UBUNTU); + assertEquals(defaultTemplate.getImage().getName(), "Ubuntu Oneiric 11.10 Server 64-bit 20111212"); assertEquals(defaultTemplate.getLocation().getId(), "az-1.region-a.geo-1"); assertEquals(defaultTemplate.getOptions().as(NovaTemplateOptions.class).shouldAutoAssignFloatingIp(), true); assertEquals(getCores(defaultTemplate.getHardware()), 1.0d); diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java index d404df1229..e1a8dc1367 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java @@ -45,7 +45,6 @@ public class NovaPropertiesBuilder extends PropertiesBuilder { properties.setProperty(PROPERTY_API_VERSION, "1.1"); properties.setProperty(PROPERTY_NOVA_AUTO_ALLOCATE_FLOATING_IPS, "false"); properties.setProperty(PROPERTY_NOVA_TIMEOUT_SECURITYGROUP_PRESENT, "500"); - return properties; } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java new file mode 100644 index 0000000000..32af8e9fee --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeService.java @@ -0,0 +1,147 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.compute; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.collect.Memoized; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.callables.RunScriptOnNode; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.internal.BaseComputeService; +import org.jclouds.compute.internal.PersistNodeCredentials; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts; +import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet; +import org.jclouds.compute.strategy.DestroyNodeStrategy; +import org.jclouds.compute.strategy.GetNodeMetadataStrategy; +import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap; +import org.jclouds.compute.strategy.ListNodesStrategy; +import org.jclouds.compute.strategy.RebootNodeStrategy; +import org.jclouds.compute.strategy.ResumeNodeStrategy; +import org.jclouds.compute.strategy.SuspendNodeStrategy; +import org.jclouds.domain.Credentials; +import org.jclouds.domain.Location; +import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; +import org.jclouds.openstack.nova.v1_1.predicates.SecurityGroupPredicates; +import org.jclouds.scriptbuilder.functions.InitAdminAccess; + +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.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +/** + * @author Adrian Cole + */ +@Singleton +public class NovaComputeService extends BaseComputeService { + private final NovaClient novaClient; + private final LoadingCache securityGroupMap; + private final Function, Multimap> orphanedGroupsByZoneId; + + @Inject + protected NovaComputeService(ComputeServiceContext context, Map credentialStore, + @Memoized Supplier> images, @Memoized Supplier> sizes, + @Memoized Supplier> locations, ListNodesStrategy listNodesStrategy, + GetNodeMetadataStrategy getNodeMetadataStrategy, + CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy, + DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy, + SuspendNodeStrategy stopNodeStrategy, Provider templateBuilderProvider, + Provider templateOptionsProvider, + @Named("NODE_RUNNING") Predicate> nodeRunning, + @Named("NODE_TERMINATED") Predicate> nodeTerminated, + @Named("NODE_SUSPENDED") Predicate> nodeSuspended, + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, + RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, + PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, + @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, NovaClient novaClient, + LoadingCache securityGroupMap, + Function, Multimap> orphanedGroupsByZoneId) { + super(context, credentialStore, images, sizes, locations, listNodesStrategy, getNodeMetadataStrategy, + runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy, startNodeStrategy, + stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning, nodeTerminated, + nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory, persistNodeCredentials, + timeouts, executor); + this.novaClient = checkNotNull(novaClient, "novaClient"); + this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap"); + this.orphanedGroupsByZoneId = checkNotNull(orphanedGroupsByZoneId, "orphanedGroupsByZoneId"); + } + + @Override + protected void cleanUpIncidentalResourcesOfDeadNodes(Set deadNodes) { + Multimap zoneToZoneAndGroupNames = orphanedGroupsByZoneId.apply(deadNodes); + for (String zoneId : zoneToZoneAndGroupNames.keySet()) { + cleanOrphanedGroupsInZone(ImmutableSet.copyOf(zoneToZoneAndGroupNames.get(zoneId)), zoneId); + } + } + + protected void cleanOrphanedGroupsInZone(Set groups, String zoneId) { + cleanupOrphanedSecurityGroupsInZone(groups, zoneId); + } + + private void cleanupOrphanedSecurityGroupsInZone(Set groups, String zoneId) { + Optional securityGroupClient = novaClient.getSecurityGroupExtensionForZone(zoneId); + if (securityGroupClient.isPresent()) { + for (String group : groups) { + for (SecurityGroup securityGroup : Iterables.filter(securityGroupClient.get().listSecurityGroups(), + SecurityGroupPredicates.nameEquals("jclouds#" + group))) { + ZoneAndName zoneAndName = ZoneAndName.fromZoneAndName(zoneId, securityGroup.getName()); + logger.debug(">> deleting securityGroup(%s)", zoneAndName); + securityGroupClient.get().deleteSecurityGroup(securityGroup.getId()); + // TODO: test this clear happens + securityGroupMap.invalidate(zoneAndName); + logger.debug("<< deleted securityGroup(%s)", zoneAndName); + } + } + } + } + + /** + * returns template options, except of type {@link NovaTemplateOptions}. + */ + @Override + public NovaTemplateOptions templateOptions() { + return NovaTemplateOptions.class.cast(super.templateOptions()); + } + +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java index 45c258b779..0287c63a83 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapter.java @@ -34,10 +34,6 @@ import org.jclouds.domain.LoginCredentials; import org.jclouds.location.Zone; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.FlavorInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ImageInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ServerInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.compute.functions.RemoveFloatingIpFromNodeAndDeallocate; import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v1_1.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet; @@ -45,12 +41,17 @@ import org.jclouds.openstack.nova.v1_1.domain.Flavor; import org.jclouds.openstack.nova.v1_1.domain.Image; import org.jclouds.openstack.nova.v1_1.domain.RebootType; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; +import org.jclouds.openstack.nova.v1_1.predicates.ImagePredicates; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; +import static com.google.common.collect.Iterables.*; import com.google.common.collect.ImmutableSet.Builder; /** @@ -103,7 +104,7 @@ public class NovaComputeServiceAdapter implements public Iterable listHardwareProfiles() { Builder builder = ImmutableSet. builder(); for (final String zoneId : zoneIds.get()) { - builder.addAll(Iterables.transform(novaClient.getFlavorClientForZone(zoneId).listFlavorsInDetail(), + builder.addAll(transform(novaClient.getFlavorClientForZone(zoneId).listFlavorsInDetail(), new Function() { @Override @@ -120,15 +121,15 @@ public class NovaComputeServiceAdapter implements public Iterable listImages() { Builder builder = ImmutableSet. builder(); for (final String zoneId : zoneIds.get()) { - builder.addAll(Iterables.transform(novaClient.getImageClientForZone(zoneId).listImagesInDetail(), - new Function() { + builder.addAll(transform(filter(novaClient.getImageClientForZone(zoneId).listImagesInDetail(), ImagePredicates + .statusEquals(Image.Status.ACTIVE)), new Function() { - @Override - public ImageInZone apply(Image arg0) { - return new ImageInZone(arg0, zoneId); - } + @Override + public ImageInZone apply(Image arg0) { + return new ImageInZone(arg0, zoneId); + } - })); + })); } return builder.build(); } @@ -137,7 +138,7 @@ public class NovaComputeServiceAdapter implements public Iterable listNodes() { Builder builder = ImmutableSet. builder(); for (final String zoneId : zoneIds.get()) { - builder.addAll(Iterables.transform(novaClient.getServerClientForZone(zoneId).listServersInDetail(), + builder.addAll(transform(novaClient.getServerClientForZone(zoneId).listServersInDetail(), new Function() { @Override diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java index 0c09fefea2..016c210835 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/config/NovaComputeServiceContextModule.java @@ -27,6 +27,7 @@ import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.collect.Memoized; +import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.config.ComputeServiceAdapterContextModule; import org.jclouds.compute.domain.Hardware; @@ -39,23 +40,25 @@ import org.jclouds.domain.Location; import org.jclouds.functions.IdentityFunction; import org.jclouds.openstack.nova.v1_1.NovaAsyncClient; import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.compute.NovaComputeService; import org.jclouds.openstack.nova.v1_1.compute.NovaComputeServiceAdapter; -import org.jclouds.openstack.nova.v1_1.compute.domain.FlavorInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ImageInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ServerInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndName; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneSecurityGroupNameAndPorts; -import org.jclouds.openstack.nova.v1_1.compute.functions.CreateSecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.compute.functions.CreateSecurityGroupIfNeeded; import org.jclouds.openstack.nova.v1_1.compute.functions.FlavorInZoneToHardware; import org.jclouds.openstack.nova.v1_1.compute.functions.ImageInZoneToImage; import org.jclouds.openstack.nova.v1_1.compute.functions.NovaImageToOperatingSystem; +import org.jclouds.openstack.nova.v1_1.compute.functions.OrphanedGroupsByZoneId; import org.jclouds.openstack.nova.v1_1.compute.functions.ServerInZoneToNodeMetadata; -import org.jclouds.openstack.nova.v1_1.compute.loaders.CreateOrUpdateSecurityGroupAsNeeded; +import org.jclouds.openstack.nova.v1_1.compute.loaders.FindSecurityGroupOrCreate; import org.jclouds.openstack.nova.v1_1.compute.loaders.LoadFloatingIpsForInstance; import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v1_1.compute.strategy.ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v1_1.predicates.FindSecurityGroupWithNameAndReturnTrue; import org.jclouds.openstack.nova.v1_1.reference.NovaConstants; import org.jclouds.predicates.RetryablePredicate; @@ -68,6 +71,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provides; @@ -92,10 +96,15 @@ public class NovaComputeServiceContextModule super.configure(); bind(new TypeLiteral>() { }).to(NovaComputeServiceAdapter.class); - + + bind(ComputeService.class).to(NovaComputeService.class); + bind(new TypeLiteral>() { }).to(ServerInZoneToNodeMetadata.class); + bind(new TypeLiteral, Multimap>>() { + }).to(OrphanedGroupsByZoneId.class); + bind(new TypeLiteral>() { }).to(ImageInZoneToImage.class); bind(new TypeLiteral>() { @@ -112,12 +121,12 @@ public class NovaComputeServiceContextModule bind(new TypeLiteral>>() { }).annotatedWith(Names.named("FLOATINGIP")).to(LoadFloatingIpsForInstance.class); - + bind(new TypeLiteral>() { - }).to(CreateSecurityGroupInZone.class); - - bind(new TypeLiteral>() { - }).to(CreateOrUpdateSecurityGroupAsNeeded.class); + }).to(CreateSecurityGroupIfNeeded.class); + + bind(new TypeLiteral>() { + }).to(FindSecurityGroupOrCreate.class); bind(CreateNodesWithGroupEncodedIntoNameThenAddToSet.class).to( ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.class); @@ -141,8 +150,8 @@ public class NovaComputeServiceContextModule @Provides @Singleton - protected LoadingCache securityGroupMap( - CacheLoader in) { + protected LoadingCache securityGroupMap( + CacheLoader in) { return CacheBuilder.newBuilder().build(in); } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNode.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNode.java index f0a8b15844..6f14e4db0e 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNode.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNode.java @@ -34,8 +34,8 @@ import org.jclouds.compute.domain.NodeMetadataBuilder; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import org.jclouds.rest.InsufficientResourcesException; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupInZone.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupIfNeeded.java similarity index 70% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupInZone.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupIfNeeded.java index c4fe094be9..b079c618fd 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupInZone.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/CreateSecurityGroupIfNeeded.java @@ -20,6 +20,8 @@ package org.jclouds.openstack.nova.v1_1.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.v1_1.predicates.SecurityGroupPredicates.nameEquals; import javax.annotation.Resource; import javax.inject.Inject; @@ -29,11 +31,11 @@ import javax.inject.Singleton; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v1_1.domain.Ingress; import org.jclouds.openstack.nova.v1_1.domain.IpProtocol; import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import com.google.common.base.Function; @@ -44,14 +46,14 @@ import com.google.common.base.Optional; * @author Adrian Cole */ @Singleton -public class CreateSecurityGroupInZone implements Function { +public class CreateSecurityGroupIfNeeded implements Function { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; protected final NovaClient novaClient; @Inject - public CreateSecurityGroupInZone(NovaClient novaClient) { + public CreateSecurityGroupIfNeeded(NovaClient novaClient) { this.novaClient = checkNotNull(novaClient, "novaClient"); } @@ -62,16 +64,24 @@ public class CreateSecurityGroupInZone implements Function client = novaClient.getSecurityGroupExtensionForZone(zoneId); checkArgument(client.isPresent(), "Security groups are required, but the extension is not available!"); - logger.debug(">> creating securityGroup %s", zoneSecurityGroupNameAndPorts); - SecurityGroup securityGroup = client.get().createSecurityGroupWithNameAndDescription( - zoneSecurityGroupNameAndPorts.getName(), zoneSecurityGroupNameAndPorts.getName()); + try { - logger.debug("<< created securityGroup(%s)", securityGroup); - for (int port : zoneSecurityGroupNameAndPorts.getPorts()) { - authorizeGroupToItselfAndAllIPsToTCPPort(client.get(), securityGroup, port); + SecurityGroup securityGroup = client.get().createSecurityGroupWithNameAndDescription( + zoneSecurityGroupNameAndPorts.getName(), zoneSecurityGroupNameAndPorts.getName()); + + logger.debug("<< created securityGroup(%s)", securityGroup); + for (int port : zoneSecurityGroupNameAndPorts.getPorts()) { + authorizeGroupToItselfAndAllIPsToTCPPort(client.get(), securityGroup, port); + } + return new SecurityGroupInZone(client.get().getSecurityGroup(securityGroup.getId()), zoneId); + } catch (IllegalStateException e) { + logger.trace("<< trying to find securityGroup(%s): %s", zoneSecurityGroupNameAndPorts, e.getMessage()); + SecurityGroup group = find(client.get().listSecurityGroups(), nameEquals(zoneSecurityGroupNameAndPorts + .getName())); + logger.debug("<< reused securityGroup(%s)", group.getId()); + return new SecurityGroupInZone(group, zoneId); } - return new SecurityGroupInZone(client.get().getSecurityGroup(securityGroup.getId()), zoneId); } private void authorizeGroupToItselfAndAllIPsToTCPPort(SecurityGroupClient securityGroupClient, diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardware.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardware.java index f854dc2409..703369ca21 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardware.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardware.java @@ -30,8 +30,8 @@ import org.jclouds.compute.domain.HardwareBuilder; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.internal.VolumeImpl; import org.jclouds.domain.Location; -import org.jclouds.openstack.nova.v1_1.compute.domain.FlavorInZone; import org.jclouds.openstack.nova.v1_1.domain.Flavor; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone; import com.google.common.base.Function; import com.google.common.base.Supplier; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImage.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImage.java index c4ef091053..68f6d03c62 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImage.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImage.java @@ -29,7 +29,7 @@ import org.jclouds.compute.domain.Image; import org.jclouds.compute.domain.ImageBuilder; import org.jclouds.compute.domain.OperatingSystem; import org.jclouds.domain.Location; -import org.jclouds.openstack.nova.v1_1.compute.domain.ImageInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; import com.google.common.base.Function; import com.google.common.base.Supplier; @@ -55,8 +55,8 @@ public class ImageInZoneToImage implements Function { Location location = locationIndex.get().get(imageInZone.getZone()); checkState(location != null, "location %s not in locationIndex: %s", imageInZone.getZone(), locationIndex.get()); org.jclouds.openstack.nova.v1_1.domain.Image image = imageInZone.getImage(); - return new ImageBuilder() - .id(imageInZone.slashEncode()).providerId(image.getId()).name(image.getName()).operatingSystem( - imageToOs.apply(image)).description(image.getName()).location(location).build(); + return new ImageBuilder().id(imageInZone.slashEncode()).providerId(image.getId()).name(image.getName()) + .userMetadata(image.getMetadata()).operatingSystem(imageToOs.apply(image)).description(image.getName()) + .location(location).build(); } } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneId.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneId.java new file mode 100644 index 0000000000..26a914bb1e --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneId.java @@ -0,0 +1,79 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.compute.functions; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.filter; + +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.predicates.NodePredicates; +import org.jclouds.domain.LocationScope; +import org.jclouds.openstack.nova.v1_1.compute.predicates.AllNodesInGroupTerminated; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +/** + * + * @author Adrian Cole + */ +public class OrphanedGroupsByZoneId implements Function, Multimap> { + private final Predicate allNodesInGroupTerminated; + + @Inject + protected OrphanedGroupsByZoneId(ComputeService computeService) { + this(new AllNodesInGroupTerminated(checkNotNull(computeService, "computeService"))); + } + + @VisibleForTesting + OrphanedGroupsByZoneId(Predicate allNodesInGroupTerminated) { + this.allNodesInGroupTerminated = checkNotNull(allNodesInGroupTerminated, "allNodesInGroupTerminated"); + } + + public Multimap apply(Set deadNodes) { + Iterable nodesWithGroup = filter(deadNodes, NodePredicates.hasGroup()); + Set zoneAndGroupNames = ImmutableSet.copyOf(filter(transform(nodesWithGroup, + new Function() { + + @Override + public ZoneAndName apply(NodeMetadata input) { + String zoneId = input.getLocation().getScope() == LocationScope.HOST ? input.getLocation() + .getParent().getId() : input.getLocation().getId(); + return ZoneAndName.fromZoneAndName(zoneId, input.getGroup()); + } + + }), allNodesInGroupTerminated)); + Multimap zoneToZoneAndGroupNames = Multimaps.transformValues(Multimaps.index(zoneAndGroupNames, + ZoneAndName.ZONE_FUNCTION), ZoneAndName.NAME_FUNCTION); + return zoneToZoneAndGroupNames; + } + +} \ No newline at end of file diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/RemoveFloatingIpFromNodeAndDeallocate.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/RemoveFloatingIpFromNodeAndDeallocate.java index 2332f504df..85e7effd23 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/RemoveFloatingIpFromNodeAndDeallocate.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/RemoveFloatingIpFromNodeAndDeallocate.java @@ -27,7 +27,7 @@ import javax.inject.Named; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import com.google.common.base.Function; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadata.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadata.java index 4972a729f8..f5701da0d8 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadata.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadata.java @@ -20,8 +20,13 @@ package org.jclouds.openstack.nova.v1_1.compute.functions; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.find; +import static com.google.common.collect.Iterables.transform; import static org.jclouds.compute.util.ComputeServiceUtils.parseGroupFromName; +import java.net.Inet4Address; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -42,16 +47,16 @@ import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; import org.jclouds.logging.Logger; -import org.jclouds.openstack.nova.v1_1.compute.domain.ServerInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.Address; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.LoadingCache; -import com.google.common.collect.Iterables; +import com.google.common.net.InetAddresses; /** * A function for transforming a nova-specific Server into a generic NodeMetadata object. @@ -97,15 +102,29 @@ public class ServerInZoneToNodeMetadata implements Function isInet4Address = new Predicate() { + @Override + public boolean apply(String input) { + try { + // Note we can do this, as InetAddress is now on the white list + return (InetAddresses.forString(input) instanceof Inet4Address); + } catch (IllegalArgumentException e) { + // could be a hostname + return true; + } + } + + }; + protected Iterable getPublicAddresses(ServerInZone serverInZone) { - return Iterables.concat(Iterables.transform(serverInZone.getServer().getPublicAddresses(), + return concat(transform(serverInZone.getServer().getPublicAddresses(), AddressToStringTransformationFunction.INSTANCE), floatingIpCache.getUnchecked(serverInZone)); } @@ -131,7 +150,7 @@ public class ServerInZoneToNodeMetadata implements Function T findObjectOfTypeForServerOrNull(Set supply, String type, final String objectId, final ZoneAndId serverInZone) { try { - return Iterables.find(supply, new Predicate() { + return find(supply, new Predicate() { @Override public boolean apply(T input) { return input.getId().equals(ZoneAndId.fromZoneAndId(serverInZone.getZone(), objectId).slashEncode()); diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/CreateOrUpdateSecurityGroupAsNeeded.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/CreateOrUpdateSecurityGroupAsNeeded.java deleted file mode 100644 index cfe550f816..0000000000 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/CreateOrUpdateSecurityGroupAsNeeded.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds 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.v1_1.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.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import org.jclouds.compute.reference.ComputeServiceConstants; -import org.jclouds.logging.Logger; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndName; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneSecurityGroupNameAndPorts; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.cache.CacheLoader; - -/** - * - * @author Adrian Cole - */ -@Singleton -public class CreateOrUpdateSecurityGroupAsNeeded extends - CacheLoader { - @Resource - @Named(ComputeServiceConstants.COMPUTE_LOGGER) - protected Logger logger = Logger.NULL; - protected final Predicate> securityGroupEventualConsistencyDelay; - protected final Function groupCreator; - - @Inject - public CreateOrUpdateSecurityGroupAsNeeded( - @Named("SECURITY") Predicate> securityGroupEventualConsistencyDelay, - Function groupCreator) { - this.securityGroupEventualConsistencyDelay = checkNotNull(securityGroupEventualConsistencyDelay, - "securityGroupEventualConsistencyDelay"); - this.groupCreator = checkNotNull(groupCreator, "groupCreator"); - } - - @Override - public SecurityGroupInZone load(ZoneSecurityGroupNameAndPorts zoneSecurityGroupNameAndPorts) { - checkNotNull(zoneSecurityGroupNameAndPorts, "zoneSecurityGroupNameAndPorts"); - - AtomicReference securityGroupInZoneRef = new AtomicReference( - zoneSecurityGroupNameAndPorts); - - if (securityGroupEventualConsistencyDelay.apply(securityGroupInZoneRef)) { - ZoneAndName securityGroupInZone = securityGroupInZoneRef.get(); - checkState( - securityGroupInZone instanceof SecurityGroupInZone, - "programming error: predicate %s should update the atomic reference to the actual security group found", - securityGroupEventualConsistencyDelay); - // TODO: check ports are actually present! - return SecurityGroupInZone.class.cast(securityGroupInZone); - } else { - return groupCreator.apply(zoneSecurityGroupNameAndPorts); - } - } - -} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreate.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreate.java new file mode 100644 index 0000000000..6e0933c0a5 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreate.java @@ -0,0 +1,88 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.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.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.cache.CacheLoader; + +/** + * + * @author Adrian Cole + */ +public class FindSecurityGroupOrCreate extends CacheLoader { + + protected final Predicate> returnSecurityGroupExistsInZone; + protected final Function groupCreator; + + @Inject + public FindSecurityGroupOrCreate( + @Named("SECURITY") Predicate> returnSecurityGroupExistsInZone, + Function groupCreator) { + this.returnSecurityGroupExistsInZone = checkNotNull(returnSecurityGroupExistsInZone, + "returnSecurityGroupExistsInZone"); + this.groupCreator = checkNotNull(groupCreator, "groupCreator"); + } + + @Override + public SecurityGroupInZone load(ZoneAndName in) { + AtomicReference securityGroupInZoneRef = new AtomicReference(checkNotNull(in, + "zoneSecurityGroupNameAndPorts")); + if (returnSecurityGroupExistsInZone.apply(securityGroupInZoneRef)) { + return returnExistingSecurityGroup(securityGroupInZoneRef); + } else { + return createNewSecurityGroup(in); + } + } + + private SecurityGroupInZone returnExistingSecurityGroup(AtomicReference securityGroupInZoneRef) { + ZoneAndName securityGroupInZone = securityGroupInZoneRef.get(); + checkState(securityGroupInZone instanceof SecurityGroupInZone, + "programming error: predicate %s should update the atomic reference to the actual security group found", + returnSecurityGroupExistsInZone); + return SecurityGroupInZone.class.cast(securityGroupInZone); + } + + private SecurityGroupInZone createNewSecurityGroup(ZoneAndName in) { + checkState( + checkNotNull(in, "zoneSecurityGroupNameAndPorts") instanceof ZoneSecurityGroupNameAndPorts, + "programming error: when issuing get to this cacheloader, you need to pass an instance of ZoneSecurityGroupNameAndPorts, not %s", + in); + ZoneSecurityGroupNameAndPorts zoneSecurityGroupNameAndPorts = ZoneSecurityGroupNameAndPorts.class.cast(in); + return groupCreator.apply(zoneSecurityGroupNameAndPorts); + } + + @Override + public String toString() { + return "returnExistingSecurityGroupInZoneOrCreateAsNeeded()"; + } + +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstance.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstance.java index 4a608c5e6c..c78177ee9e 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstance.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstance.java @@ -22,8 +22,8 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import com.google.common.base.Function; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/AllNodesInGroupTerminated.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/AllNodesInGroupTerminated.java new file mode 100644 index 0000000000..014150cc58 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/predicates/AllNodesInGroupTerminated.java @@ -0,0 +1,58 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.compute.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.and; +import static com.google.common.collect.Iterables.all; +import static org.jclouds.compute.predicates.NodePredicates.TERMINATED; +import static org.jclouds.compute.predicates.NodePredicates.inGroup; +import static org.jclouds.compute.predicates.NodePredicates.locationId; +import static org.jclouds.compute.predicates.NodePredicates.parentLocationId; + +import javax.inject.Inject; + +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.domain.ComputeMetadata; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +/** + * @author Adrian Cole + */ +public class AllNodesInGroupTerminated implements Predicate { + private final ComputeService computeService; + + + //TODO: TESTME + @Inject + public AllNodesInGroupTerminated(ComputeService computeService) { + this.computeService = checkNotNull(computeService, "computeService"); + } + + @Override + public boolean apply(ZoneAndName input) { + // new nodes can have the zone as their location, existing nodes, the parent is the + // location + return all(computeService.listNodesDetailsMatching(Predicates. or(locationId(input.getZone()), + parentLocationId(input.getZone()))), and(inGroup(input.getName()), TERMINATED)); + } +} \ No newline at end of file diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 8a416bb5a5..6af9d9f26f 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -21,20 +21,24 @@ package org.jclouds.openstack.nova.v1_1.compute.strategy; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; 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.Template; +import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; import org.jclouds.compute.strategy.ListNodesStrategy; @@ -43,8 +47,14 @@ import org.jclouds.concurrent.Futures; import org.jclouds.openstack.nova.v1_1.NovaClient; import org.jclouds.openstack.nova.v1_1.compute.functions.AllocateAndAddFloatingIpToNode; import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; +import com.google.common.base.Throwables; +import com.google.common.cache.LoadingCache; import com.google.common.collect.Multimap; +import com.google.common.primitives.Ints; /** * @@ -55,7 +65,9 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT CreateNodesWithGroupEncodedIntoNameThenAddToSet { private final AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode; + private final LoadingCache securityGroupCache; private final NovaClient novaClient; + private final Provider templateBuilderProvider; @Inject protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet( @@ -64,9 +76,13 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT @Named("NAMING_CONVENTION") String nodeNamingConvention, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService executor, - AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode, NovaClient novaClient) { + Provider templateBuilderProvider, + AllocateAndAddFloatingIpToNode allocateAndAddFloatingIpToNode, + LoadingCache securityGroupCache, NovaClient novaClient) { super(addNodeWithTagStrategy, listNodesStrategy, nodeNamingConvention, executor, customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); + this.templateBuilderProvider = checkNotNull(templateBuilderProvider, "templateBuilderProvider"); + this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache"); this.allocateAndAddFloatingIpToNode = checkNotNull(allocateAndAddFloatingIpToNode, "allocateAndAddFloatingIpToNode"); this.novaClient = checkNotNull(novaClient, "novaClient"); @@ -75,23 +91,38 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT @Override public Map> execute(String group, int count, Template template, Set goodNodes, Map badNodes, Multimap customizationResponses) { + // ensure we don't mutate the input template + Template mutableTemplate = templateBuilderProvider.get().fromTemplate(template).build(); + // TODO: make NovaTemplateOptions with the following: // security group, key pair - NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions()); + NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(mutableTemplate.getOptions()); - String zone = template.getLocation().getId(); + String zone = mutableTemplate.getLocation().getId(); if (templateOptions.shouldAutoAssignFloatingIp()) { checkArgument(novaClient.getFloatingIPExtensionForZone(zone).isPresent(), - "Floating IPs are required by options, but the extension is not available! options: %s", templateOptions); + "Floating IPs are required by options, but the extension is not available! options: %s", + templateOptions); } - + + boolean securityGroupExensionPresent = novaClient.getSecurityGroupExtensionForZone(zone).isPresent(); + List inboundPorts = Ints.asList(templateOptions.getInboundPorts()); if (templateOptions.getSecurityGroupNames().size() > 0) { checkArgument(novaClient.getSecurityGroupExtensionForZone(zone).isPresent(), - "Security groups are required by options, but the extension is not available! options: %s", templateOptions); + "Security groups are required by options, but the extension is not available! options: %s", + templateOptions); + } else if (securityGroupExensionPresent && inboundPorts.size() > 0) { + String securityGroupName = "jclouds#" + group; + try { + securityGroupCache.get(new ZoneSecurityGroupNameAndPorts(zone, securityGroupName, inboundPorts)); + } catch (ExecutionException e) { + throw Throwables.propagate(e.getCause()); + } + templateOptions.securityGroupNames(securityGroupName); } - - return super.execute(group, count, template, goodNodes, badNodes, customizationResponses); + + return super.execute(group, count, mutableTemplate, goodNodes, badNodes, customizationResponses); } @Override diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java index e9e025bd8a..132bd9ae53 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Image.java @@ -27,20 +27,47 @@ import java.util.Set; import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.gson.annotations.SerializedName; /** - * An image is a collection of files you use to create or rebuild a server. - * Operators provide pre-built OS images by default. You may also create custom - * images. + * An image is a collection of files you use to create or rebuild a server. Operators provide + * pre-built OS images by default. You may also create custom images. * * @author Jeremy Daggett - * @see */ public class Image extends Resource { + /** + * In-flight images will have the status attribute set to SAVING and the conditional progress + * element (0-100% completion) will also be returned. Other possible values for the status + * attribute include: UNKNOWN, ACTIVE, SAVING, ERROR, and DELETED. Images with an ACTIVE status + * are available for install. The optional minDisk and minRam attributes set the minimum disk and + * RAM requirements needed to create a server with the image. + * + * @author Adrian Cole + */ + public static enum Status { + + UNRECOGNIZED, UNKNOWN, ACTIVE, SAVING, ERROR, DELETED; + + public String value() { + return name(); + } + + public static Status fromValue(String v) { + try { + return valueOf(v); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + + } + public static Builder builder() { return new Builder(); } @@ -55,12 +82,12 @@ public class Image extends Resource { private Date created; private String tenantId; private String userId; - private ImageStatus status; + private Status status; private int progress; private int minDisk; private int minRam; private Resource server; - private Map metadata = Maps.newHashMap(); + private Map metadata = Maps.newLinkedHashMap(); public Builder updated(Date updated) { this.updated = updated; @@ -82,7 +109,7 @@ public class Image extends Resource { return this; } - public Builder status(ImageStatus status) { + public Builder status(Status status) { this.status = status; return this; } @@ -114,12 +141,12 @@ public class Image extends Resource { public Image build() { return new Image(id, name, links, updated, created, tenantId, userId, status, progress, minDisk, minRam, - server, metadata); + server, metadata); } public Builder fromImage(Image in) { - return fromResource(in).status(in.getStatus()).updated(in.getUpdated()).created(in.getCreated()) - .progress(in.getProgress()).server(in.getServer()).metadata(in.getMetadata()); + return fromResource(in).status(in.getStatus()).updated(in.getUpdated()).created(in.getCreated()).progress( + in.getProgress()).server(in.getServer()).metadata(in.getMetadata()); } /** @@ -155,21 +182,21 @@ public class Image extends Resource { } } - private Date updated; - private Date created; + private final Date updated; + private final Date created; @SerializedName("tenant_id") - private String tenantId; + private final String tenantId; @SerializedName("user_id") - private String userId; - private ImageStatus status; - private int progress; - private int minDisk; - private int minRam; - private Resource server; - private Map metadata = Maps.newHashMap(); + private final String userId; + private final Status status; + private final int progress; + private final int minDisk; + private final int minRam; + private final Resource server; + private final Map metadata; protected Image(String id, String name, Set links, Date updated, Date created, String tenantId, String userId, - ImageStatus status, int progress, int minDisk, int minRam, Resource server, Map metadata) { + Status status, int progress, int minDisk, int minRam, Resource server, Map metadata) { super(id, name, links); this.updated = updated; this.created = created; @@ -180,7 +207,7 @@ public class Image extends Resource { this.minDisk = minDisk; this.minRam = minRam; this.server = server; - this.metadata = metadata; + this.metadata = ImmutableMap. copyOf(metadata); } public Date getUpdated() { @@ -199,7 +226,7 @@ public class Image extends Resource { return this.userId; } - public ImageStatus getStatus() { + public Status getStatus() { return this.status; } @@ -220,15 +247,16 @@ public class Image extends Resource { } public Map getMetadata() { - return this.metadata; + // in case this was assigned in gson + return ImmutableMap.copyOf(Maps.filterValues(this.metadata, Predicates.notNull())); } @Override public String toString() { - return toStringHelper("").add("id", id).add("name", name).add("links", links).add("updated", updated) - .add("created", created).add("tenantId", tenantId).add("userId", userId).add("status", status) - .add("progress", progress).add("minDisk", minDisk).add("minRam", minRam).add("server", server) - .add("metadata", metadata).toString(); + return toStringHelper("").add("id", id).add("name", name).add("links", links).add("updated", updated).add( + "created", created).add("tenantId", tenantId).add("userId", userId).add("status", status).add( + "progress", progress).add("minDisk", minDisk).add("minRam", minRam).add("server", server).add( + "metadata", metadata).toString(); } } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java index 01666727f3..e1fb863361 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java @@ -26,22 +26,19 @@ import java.util.Date; import java.util.Map; import java.util.Set; +import org.jclouds.compute.domain.NodeState; import org.jclouds.javax.annotation.Nullable; import org.jclouds.openstack.domain.Link; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.nova.v1_1.domain.Address.Type; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; -import org.jclouds.util.InetAddresses2; import org.jclouds.util.Multimaps2; -import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; @@ -57,6 +54,47 @@ import com.google.gson.annotations.SerializedName; * /> */ public class Server extends Resource { + + /** + * Servers contain a status attribute that can be used as an indication of the current server + * state. Servers with an ACTIVE status are available for use. + * + * Other possible values for the status attribute include: BUILD, REBUILD, SUSPENDED, RESIZE, + * VERIFY_RESIZE, REVERT_RESIZE, PASSWORD, REBOOT, HARD_REBOOT, DELETED, UNKNOWN, and ERROR. + * + * @author Adrian Cole + */ + public static enum Status { + + ACTIVE(NodeState.RUNNING), BUILD(NodeState.PENDING), REBUILD(NodeState.PENDING), SUSPENDED(NodeState.SUSPENDED), RESIZE( + NodeState.PENDING), VERIFY_RESIZE(NodeState.PENDING), REVERT_RESIZE(NodeState.PENDING), PASSWORD( + NodeState.PENDING), REBOOT(NodeState.PENDING), HARD_REBOOT(NodeState.PENDING), DELETED( + NodeState.TERMINATED), UNKNOWN(NodeState.UNRECOGNIZED), ERROR(NodeState.ERROR), UNRECOGNIZED( + NodeState.UNRECOGNIZED); + + private final NodeState nodeState; + + Status(NodeState nodeState) { + this.nodeState = nodeState; + } + + public String value() { + return name(); + } + + public static Status fromValue(String v) { + try { + return valueOf(v.replaceAll("\\(.*", "")); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + + public NodeState getNodeState() { + return nodeState; + } + } + public static Builder builder() { return new Builder(); } @@ -74,7 +112,7 @@ public class Server extends Resource { private String hostId; private String accessIPv4; private String accessIPv6; - private ServerStatus status; + private Status status; private String configDrive; private Resource image; private Resource flavor; @@ -151,7 +189,7 @@ public class Server extends Resource { /** * @see Server#getStatus() */ - public Builder status(ServerStatus status) { + public Builder status(Status status) { this.status = status; return this; } @@ -309,7 +347,7 @@ public class Server extends Resource { protected final String hostId; protected final String accessIPv4; protected final String accessIPv6; - protected final ServerStatus status; + protected final Status status; protected final Resource image; protected final Resource flavor; protected final String adminPass; @@ -323,8 +361,8 @@ public class Server extends Resource { protected Server(String id, String name, Set links, @Nullable String uuid, String tenantId, String userId, Date updated, Date created, @Nullable String hostId, @Nullable String accessIPv4, - @Nullable String accessIPv6, ServerStatus status, @Nullable String configDrive, Resource image, - Resource flavor, String adminPass, @Nullable String keyName, Multimap addresses, + @Nullable String accessIPv6, Status status, @Nullable String configDrive, Resource image, Resource flavor, + String adminPass, @Nullable String keyName, Multimap addresses, Map metadata) { super(id, name, links); this.uuid = uuid; // TODO: see what version this came up in @@ -390,7 +428,7 @@ public class Server extends Resource { return Strings.emptyToNull(this.accessIPv6); } - public ServerStatus getStatus() { + public Status getStatus() { return this.status; } @@ -408,7 +446,8 @@ public class Server extends Resource { } public Map getMetadata() { - return this.metadata; + // in case this was assigned in gson + return ImmutableMap.copyOf(Maps.filterValues(this.metadata, Predicates.notNull())); } /** @@ -439,29 +478,7 @@ public class Server extends Resource { * @return the ip addresses assigned to the server */ public Multimap getAddresses() { - ImmutableSetMultimap.Builder returnMapBuilder = new ImmutableSetMultimap.Builder(); - - Set
publicAddresses = addresses.get(Address.Type.PUBLIC); - Set
privateAddresses = addresses.get(Address.Type.PRIVATE); - if (publicAddresses != null) { - returnMapBuilder.putAll(Address.Type.PUBLIC, Iterables.filter(publicAddresses, Predicates - .not(IsPrivateAddress.INSTANCE))); - } - if (privateAddresses != null) { - returnMapBuilder.putAll(Address.Type.PRIVATE, Iterables.filter(privateAddresses, IsPrivateAddress.INSTANCE)); - returnMapBuilder.putAll(Address.Type.PUBLIC, Iterables.filter(privateAddresses, Predicates - .not(IsPrivateAddress.INSTANCE))); - } - ImmutableSetMultimap returnMap = returnMapBuilder.build(); - - return returnMap.size() > 0 ? returnMap : Multimaps2.fromOldSchool(addresses); - } - - private static enum IsPrivateAddress implements Predicate
{ - INSTANCE; - public boolean apply(Address in) { - return InetAddresses2.IsPrivateIPAddress.INSTANCE.apply(in.getAddr()); - } + return Multimaps2.fromOldSchool(addresses); } /** diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java deleted file mode 100644 index 9cb3563390..0000000000 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds 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.v1_1.domain; - -import org.jclouds.compute.domain.NodeState; - -/** - * Servers contain a status attribute that can be used as an indication of the - * current server state. Servers with an ACTIVE status are available for use. - * - * Other possible values for the status attribute include: BUILD, REBUILD, - * SUSPENDED, RESIZE, VERIFY_RESIZE, REVERT_RESIZE, PASSWORD, REBOOT, - * HARD_REBOOT, DELETED, UNKNOWN, and ERROR. - * - * @author Adrian Cole - */ -public enum ServerStatus { - - ACTIVE(NodeState.RUNNING), BUILD(NodeState.PENDING), REBUILD(NodeState.PENDING), SUSPENDED(NodeState.SUSPENDED), RESIZE( - NodeState.PENDING), VERIFY_RESIZE(NodeState.PENDING), REVERT_RESIZE(NodeState.PENDING), PASSWORD( - NodeState.PENDING), REBOOT(NodeState.PENDING), HARD_REBOOT(NodeState.PENDING), DELETED(NodeState.TERMINATED), UNKNOWN( - NodeState.UNRECOGNIZED), ERROR(NodeState.ERROR), UNRECOGNIZED(NodeState.UNRECOGNIZED); - - private final NodeState nodeState; - - ServerStatus(NodeState nodeState) { - this.nodeState = nodeState; - } - - public String value() { - return name(); - } - - public static ServerStatus fromValue(String v) { - try { - return valueOf(v.replaceAll("\\(.*","")); - } catch (IllegalArgumentException e) { - return UNRECOGNIZED; - } - } - - public NodeState getNodeState() { - return nodeState; - } -} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/FlavorInZone.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/FlavorInZone.java similarity index 96% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/FlavorInZone.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/FlavorInZone.java index 769f92fdad..73968a3bdc 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/FlavorInZone.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/FlavorInZone.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ImageInZone.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ImageInZone.java similarity index 79% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ImageInZone.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ImageInZone.java index 4d84525f36..e0648fcb94 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ImageInZone.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ImageInZone.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Preconditions.checkNotNull; @@ -26,15 +26,15 @@ import org.jclouds.openstack.nova.v1_1.domain.Image; * @author Adrian Cole */ public class ImageInZone extends ZoneAndId { - protected final Image server; + protected final Image image; - public ImageInZone(Image server, String zoneId) { - super(zoneId, checkNotNull(server, "server").getId()); - this.server = server; + public ImageInZone(Image image, String zoneId) { + super(zoneId, checkNotNull(image, "image").getId()); + this.image = image; } public Image getImage() { - return server; + return image; } // superclass hashCode/equals are good enough, and help us use ZoneAndId and ImageInZone @@ -42,7 +42,7 @@ public class ImageInZone extends ZoneAndId { @Override public String toString() { - return "[server=" + server + ", zoneId=" + zoneId + "]"; + return "[image=" + image + ", zoneId=" + zoneId + "]"; } } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/SecurityGroupInZone.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/SecurityGroupInZone.java similarity index 96% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/SecurityGroupInZone.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/SecurityGroupInZone.java index 0271f9bdcc..a24b7c10ad 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/SecurityGroupInZone.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/SecurityGroupInZone.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ServerInZone.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ServerInZone.java similarity index 96% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ServerInZone.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ServerInZone.java index b8d4527a56..6d28dab9ed 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ServerInZone.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ServerInZone.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndId.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndId.java similarity index 97% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndId.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndId.java index 60dba75c44..760a89cf7b 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndId.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndId.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndName.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndName.java similarity index 83% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndName.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndName.java index 11f4cc7dba..1bc8b5a631 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneAndName.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneAndName.java @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Objects.equal; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Splitter; import com.google.common.base.Objects.ToStringHelper; @@ -33,6 +34,25 @@ import com.google.common.collect.Iterables; * @author Adrian Cole */ public class ZoneAndName { + + public final static Function NAME_FUNCTION = new Function(){ + + @Override + public String apply(ZoneAndName input) { + return input.getName(); + } + + }; + + public final static Function ZONE_FUNCTION = new Function(){ + + @Override + public String apply(ZoneAndName input) { + return input.getZone(); + } + + }; + public static ZoneAndName fromSlashEncoded(String name) { Iterable parts = Splitter.on('/').split(checkNotNull(name, "name")); checkArgument(Iterables.size(parts) == 2, "name must be in format zoneId/name"); diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneSecurityGroupNameAndPorts.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneSecurityGroupNameAndPorts.java similarity index 84% rename from labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneSecurityGroupNameAndPorts.java rename to labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneSecurityGroupNameAndPorts.java index 56143f9dfc..d91552eb3d 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/compute/domain/ZoneSecurityGroupNameAndPorts.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/zonescoped/ZoneSecurityGroupNameAndPorts.java @@ -16,29 +16,29 @@ * specific language governing permissions and limitations * under the License. */ -package org.jclouds.openstack.nova.v1_1.compute.domain; +package org.jclouds.openstack.nova.v1_1.domain.zonescoped; import static com.google.common.base.Objects.equal; import static com.google.common.base.Preconditions.checkNotNull; -import java.util.List; +import java.util.Set; import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * @author Adrian Cole */ public class ZoneSecurityGroupNameAndPorts extends ZoneAndName { - protected final List ports; + protected final Set ports; - public ZoneSecurityGroupNameAndPorts(String zoneId, String name, List ports) { + public ZoneSecurityGroupNameAndPorts(String zoneId, String name, Iterable ports) { super(zoneId, name); - this.ports = ImmutableList. copyOf(checkNotNull(ports, "ports")); + this.ports = ImmutableSet. copyOf(checkNotNull(ports, "ports")); } - public List getPorts() { + public Set getPorts() { return ports; } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java index 2f3c1bca50..ef2c150cc1 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java @@ -55,6 +55,8 @@ public class NovaErrorHandler implements HttpErrorHandler { exception = new InsufficientResourcesException(message, exception); else if (message.indexOf("has no fixed_ips") != -1) exception = new IllegalStateException(message, exception); + else if (message.indexOf("already exists") != -1) + exception = new IllegalStateException(message, exception); break; case 401: case 403: diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/FindSecurityGroupWithNameAndReturnTrue.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/FindSecurityGroupWithNameAndReturnTrue.java index 4d94210e79..164db18322 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/FindSecurityGroupWithNameAndReturnTrue.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/FindSecurityGroupWithNameAndReturnTrue.java @@ -29,9 +29,9 @@ import javax.inject.Singleton; import org.jclouds.logging.Logger; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndName; import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.rest.ResourceNotFoundException; diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicates.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicates.java new file mode 100644 index 0000000000..1c58e46414 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicates.java @@ -0,0 +1,57 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.openstack.nova.v1_1.domain.Image; +import org.jclouds.openstack.nova.v1_1.domain.Image.Status; + +import com.google.common.base.Predicate; + +/** + * Predicates handy when working with Images + * + * @author Adrian Cole + */ + +public class ImagePredicates { + + /** + * matches status of the given image + * + * @param status + * @return predicate that matches status + */ + public static Predicate statusEquals(final Status status) { + checkNotNull(status, "status must be defined"); + + return new Predicate() { + @Override + public boolean apply(Image image) { + return status.equals(image.getStatus()); + } + + @Override + public String toString() { + return "statusEquals(" + status + ")"; + } + }; + } +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicates.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicates.java new file mode 100644 index 0000000000..08f8f9913d --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicates.java @@ -0,0 +1,56 @@ +/* + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; + +import com.google.common.base.Predicate; + +/** + * Predicates handy when working with SecurityGroups + * + * @author Adrian Cole + */ + +public class SecurityGroupPredicates { + + /** + * matches name of the given extension + * + * @param name + * @return predicate that matches name + */ + public static Predicate nameEquals(final String name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @Override + public boolean apply(SecurityGroup ext) { + return name.equals(ext.getName()); + } + + @Override + public String toString() { + return "nameEquals(" + name + ")"; + } + }; + } +} diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java index 2e3132a2a5..ec82fd4e6a 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java @@ -65,6 +65,17 @@ public class NovaErrorHandlerTest { IllegalStateException.class); } + @Test + public void test400MakesIllegalStateExceptionOnAlreadyExists() { + assertCodeMakes( + "POST", + URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/servers"), + 400, + "HTTP/1.1 400 Bad Request", + "{\"badRequest\": {\"message\": \"Server with the name 'test' already exists\", \"code\": 400}}", + IllegalStateException.class); + } + @Test public void test400MakesInsufficientResourcesExceptionOnQuotaExceeded() { diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapterExpectTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapterExpectTest.java index 467891a798..0999e5a6fd 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapterExpectTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/NovaComputeServiceAdapterExpectTest.java @@ -29,8 +29,8 @@ import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; -import org.jclouds.openstack.nova.v1_1.compute.domain.ServerInZone; import org.jclouds.openstack.nova.v1_1.compute.options.NovaTemplateOptions; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaComputeServiceContextExpectTest; import org.testng.annotations.Test; diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java similarity index 98% rename from labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeTest.java rename to labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java index 6e90c3268a..28dbcb948a 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/AllocateAndAddFloatingIpToNodeExpectTest.java @@ -46,7 +46,7 @@ import com.google.common.collect.ImmutableSet; * @author Matt Stephenson */ @Test(groups = "unit", testName = "AllocateAndAddFloatingIpToNodeTest") -public class AllocateAndAddFloatingIpToNodeTest extends BaseNovaComputeServiceExpectTest { +public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeServiceExpectTest { final Location provider = new LocationBuilder().scope(LocationScope.PROVIDER).id("openstack-nova").description( "openstack-nova").build(); final Location zone = new LocationBuilder().id("az-1.region-a.geo-1").description("az-1.region-a.geo-1").scope( diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardwareTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardwareTest.java index 8537d59b2c..fe7980332a 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardwareTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/FlavorInZoneToHardwareTest.java @@ -29,8 +29,8 @@ import org.jclouds.compute.domain.Hardware; import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; -import org.jclouds.openstack.nova.v1_1.compute.domain.FlavorInZone; import org.jclouds.openstack.nova.v1_1.domain.Flavor; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.FlavorInZone; import org.testng.annotations.Test; import com.google.common.base.Supplier; diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImageTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImageTest.java index f8490da89c..a8fa43194c 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImageTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ImageInZoneToImageTest.java @@ -30,8 +30,8 @@ import org.jclouds.compute.domain.OsFamily; import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; -import org.jclouds.openstack.nova.v1_1.compute.domain.ImageInZone; import org.jclouds.openstack.nova.v1_1.domain.Image; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ImageInZone; import org.testng.annotations.Test; import com.google.common.base.Function; diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneIdTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneIdTest.java new file mode 100644 index 0000000000..02d79418b2 --- /dev/null +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/OrphanedGroupsByZoneIdTest.java @@ -0,0 +1,108 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.compute.functions; + +import static org.testng.Assert.assertEquals; + +import java.util.Map; +import java.util.Set; + +import org.easymock.classextension.EasyMock; +import org.jclouds.compute.domain.Hardware; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.domain.Location; +import org.jclouds.domain.LocationBuilder; +import org.jclouds.domain.LocationScope; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.parse.ParseCreatedServerTest; +import org.jclouds.openstack.nova.v1_1.parse.ParseServerTest; +import org.testng.annotations.Test; + +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; + +/** + * + * + * @author Adrian Cole + */ +@Test(testName = "OrphanedGroupsByZoneIdTest") +public class OrphanedGroupsByZoneIdTest { + + Location provider = new LocationBuilder().scope(LocationScope.PROVIDER).id("openstack-nova").description( + "openstack-nova").build(); + Location zone = new LocationBuilder().id("az-1.region-a.geo-1").description("az-1.region-a.geo-1").scope( + LocationScope.ZONE).parent(provider).build(); + Supplier> locationIndex = Suppliers.> ofInstance(ImmutableMap + . of("az-1.region-a.geo-1", zone)); + + @Test + public void testWhenComputeServiceSaysAllNodesAreDeadBothGroupsAreReturned() { + + ServerInZone withoutHost = new ServerInZone(new ParseCreatedServerTest().expected(), "az-1.region-a.geo-1"); + ServerInZone withHost = new ServerInZone(new ParseServerTest().expected(), "az-1.region-a.geo-1"); + + LoadingCache> mockLoadingCache = EasyMock.createMock(LoadingCache.class); + EasyMock.expect(mockLoadingCache.getUnchecked(withoutHost)).andReturn(ImmutableSet. of()); + EasyMock.expect(mockLoadingCache.getUnchecked(withHost)).andReturn(ImmutableSet. of()); + EasyMock.replay(mockLoadingCache); + + ServerInZoneToNodeMetadata converter = new ServerInZoneToNodeMetadata(locationIndex, Suppliers + .> ofInstance(ImmutableSet. of()), Suppliers + .> ofInstance(ImmutableSet. of()), mockLoadingCache); + + Set set = ImmutableSet.of(converter.apply(withHost), converter.apply(withoutHost)); + + assertEquals(new OrphanedGroupsByZoneId(Predicates. alwaysTrue()).apply(set), ImmutableMultimap + . builder().putAll("az-1.region-a.geo-1", "sample", "test").build()); + + EasyMock.verify(mockLoadingCache); + } + + @Test + public void testWhenComputeServiceSaysAllNodesAreDeadNoGroupsAreReturned() { + + ServerInZone withoutHost = new ServerInZone(new ParseCreatedServerTest().expected(), "az-1.region-a.geo-1"); + ServerInZone withHost = new ServerInZone(new ParseServerTest().expected(), "az-1.region-a.geo-1"); + + LoadingCache> mockLoadingCache = EasyMock.createMock(LoadingCache.class); + EasyMock.expect(mockLoadingCache.getUnchecked(withoutHost)).andReturn(ImmutableSet. of()); + EasyMock.expect(mockLoadingCache.getUnchecked(withHost)).andReturn(ImmutableSet. of()); + EasyMock.replay(mockLoadingCache); + + ServerInZoneToNodeMetadata converter = new ServerInZoneToNodeMetadata(locationIndex, Suppliers + .> ofInstance(ImmutableSet. of()), Suppliers + .> ofInstance(ImmutableSet. of()), mockLoadingCache); + + Set set = ImmutableSet.of(converter.apply(withHost), converter.apply(withoutHost)); + + assertEquals(new OrphanedGroupsByZoneId(Predicates. alwaysFalse()).apply(set), ImmutableMultimap + . of()); + + EasyMock.verify(mockLoadingCache); + } +} diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadataTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadataTest.java index 56ba9395e5..6034534e93 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadataTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/functions/ServerInZoneToNodeMetadataTest.java @@ -35,9 +35,9 @@ import org.jclouds.compute.domain.OsFamily; import org.jclouds.domain.Location; import org.jclouds.domain.LocationBuilder; import org.jclouds.domain.LocationScope; -import org.jclouds.openstack.nova.v1_1.compute.domain.ServerInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ServerInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.parse.ParseCreatedServerTest; import org.jclouds.openstack.nova.v1_1.parse.ParseServerTest; import org.testng.annotations.Test; @@ -134,8 +134,8 @@ public class ServerInZoneToNodeMetadataTest { assertEquals(convertedNodeMetadata.getPrivateAddresses(), ImmutableSet.of("10.176.42.16")); assertNotNull(convertedNodeMetadata.getPublicAddresses()); - assertEquals(convertedNodeMetadata.getPublicAddresses(), ImmutableSet.of("67.23.10.132", "::babe:67.23.10.132", - "67.23.10.131", "::babe:4317:0A83", "::babe:10.176.42.16")); + // note jclouds doesn't yet support ipv6 b/c not tested yet + assertEquals(convertedNodeMetadata.getPublicAddresses(), ImmutableSet.of("67.23.10.132", "67.23.10.131")); assertNotNull(convertedNodeMetadata.getUserMetadata()); assertEquals(convertedNodeMetadata.getUserMetadata(), ImmutableMap. of("Server Label", diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreateTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreateTest.java new file mode 100644 index 0000000000..5ada8c5a9b --- /dev/null +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/FindSecurityGroupOrCreateTest.java @@ -0,0 +1,145 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.compute.loaders; + +import static org.easymock.EasyMock.createMock; +import static org.testng.Assert.assertEquals; + +import java.util.concurrent.atomic.AtomicReference; + +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; +import org.testng.annotations.Test; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", singleThreaded = true, testName = "FindSecurityGroupOrCreateTest") +public class FindSecurityGroupOrCreateTest { + + @Test + public void testWhenNotFoundCreatesANewSecurityGroup() throws Exception { + Predicate> returnSecurityGroupExistsInZone = Predicates.alwaysFalse(); + + SecurityGroupInZone securityGroupInZone = createMock(SecurityGroupInZone.class); + + ZoneSecurityGroupNameAndPorts input = new ZoneSecurityGroupNameAndPorts("zone", "groupName", ImmutableSet + . of(22, 8080)); + + Function groupCreator = Functions.forMap(ImmutableMap + . of(input, securityGroupInZone)); + + FindSecurityGroupOrCreate parser = new FindSecurityGroupOrCreate( + returnSecurityGroupExistsInZone, groupCreator); + + assertEquals(parser.load(input), securityGroupInZone); + + } + + @Test + public void testWhenFoundReturnsSecurityGroupFromAtomicReferenceValueUpdatedDuringPredicateCheck() throws Exception { + final SecurityGroupInZone securityGroupInZone = createMock(SecurityGroupInZone.class); + + Predicate> returnSecurityGroupExistsInZone = new Predicate>(){ + + @Override + public boolean apply(AtomicReference input) { + input.set(securityGroupInZone); + return true; + } + + }; + + ZoneAndName input = ZoneAndName.fromZoneAndName("zone", "groupName"); + + Function groupCreator = new Function() { + + @Override + public SecurityGroupInZone apply(ZoneSecurityGroupNameAndPorts input) { + assert false; + return null; + } + + }; + + FindSecurityGroupOrCreate parser = new FindSecurityGroupOrCreate( + returnSecurityGroupExistsInZone, groupCreator); + + assertEquals(parser.load(input), securityGroupInZone); + + } + + + @Test(expectedExceptions = IllegalStateException.class) + public void testWhenFoundPredicateMustUpdateAtomicReference() throws Exception { + + Predicate> returnSecurityGroupExistsInZone = Predicates.alwaysTrue(); + + ZoneAndName input = ZoneAndName.fromZoneAndName("zone", "groupName"); + + Function groupCreator = new Function() { + + @Override + public SecurityGroupInZone apply(ZoneSecurityGroupNameAndPorts input) { + assert false; + return null; + } + + }; + + FindSecurityGroupOrCreate parser = new FindSecurityGroupOrCreate( + returnSecurityGroupExistsInZone, groupCreator); + + parser.load(input); + + } + + + + @Test(expectedExceptions = IllegalStateException.class) + public void testWhenNotFoundInputMustBeZoneSecurityGroupNameAndPorts() throws Exception { + Predicate> returnSecurityGroupExistsInZone = Predicates.alwaysFalse(); + + ZoneAndName input = ZoneAndName.fromZoneAndName("zone", "groupName"); + + Function groupCreator = new Function() { + + @Override + public SecurityGroupInZone apply(ZoneSecurityGroupNameAndPorts input) { + assert false; + return null; + } + + }; + + FindSecurityGroupOrCreate parser = new FindSecurityGroupOrCreate( + returnSecurityGroupExistsInZone, groupCreator); + + parser.load(input); + + } +} diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstanceTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstanceTest.java index 0030afbc31..eb9f828e14 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstanceTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/compute/loaders/LoadFloatingIpsForInstanceTest.java @@ -26,8 +26,8 @@ import static org.testng.Assert.assertEquals; import static org.testng.AssertJUnit.assertFalse; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndId; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndId; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import org.testng.annotations.Test; diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java index 1312299e20..95c3c27ce4 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/extensions/FloatingIPClientLiveTest.java @@ -28,7 +28,7 @@ import java.util.Set; import org.jclouds.openstack.nova.v1_1.domain.Address; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.ServerStatus; +import org.jclouds.openstack.nova.v1_1.domain.Server.Status; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; import org.jclouds.openstack.nova.v1_1.features.ServerClient; @@ -136,7 +136,7 @@ public class FloatingIPClientLiveTest extends BaseNovaClientLiveTest { private void blockUntilServerActive(String serverId, ServerClient client) throws InterruptedException { Server currentDetails = null; - for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != ServerStatus.ACTIVE; currentDetails = client + for (currentDetails = client.getServer(serverId); currentDetails.getStatus() != Status.ACTIVE; currentDetails = client .getServer(serverId)) { System.out.printf("blocking on status active%n%s%n", currentDetails); Thread.sleep(5 * 1000); diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupInZoneExpectTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupIfNeededTest.java similarity index 69% rename from labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupInZoneExpectTest.java rename to labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupIfNeededTest.java index ed3dd87a91..84f12b2488 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupInZoneExpectTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/CreateSecurityGroupIfNeededTest.java @@ -25,9 +25,9 @@ import java.net.URI; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneSecurityGroupNameAndPorts; -import org.jclouds.openstack.nova.v1_1.compute.functions.CreateSecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.compute.functions.CreateSecurityGroupIfNeeded; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneSecurityGroupNameAndPorts; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; import org.jclouds.openstack.nova.v1_1.parse.ParseComputeServiceTypicalSecurityGroupTest; import org.testng.annotations.Test; @@ -35,32 +35,32 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableMap.Builder; /** * * @author Adrian Cole */ -@Test(groups = "unit", testName = "CreateSecurityGroupInZoneExpectTest") -public class CreateSecurityGroupInZoneExpectTest extends BaseNovaClientExpectTest { +@Test(groups = "unit", testName = "CreateSecurityGroupIfNeededTest") +public class CreateSecurityGroupIfNeededTest extends BaseNovaClientExpectTest { + HttpRequest createSecurityGroup = HttpRequest.builder().method("POST").endpoint( + URI.create("https://compute.north.host/v1.1/3456/os-security-groups")).headers( + ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", + authToken).build()) + .payload( + payloadFromStringWithContentType( + "{\"security_group\":{\"name\":\"jclouds#mygroup\",\"description\":\"jclouds#mygroup\"}}", + "application/json")).build(); - public void testUpdateReferenceWhenSecurityGroupListContainsGroupName() throws Exception { + public void testCreateNewGroup() throws Exception { Builder builder = ImmutableMap.builder(); builder.put(keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess); builder.put(extensionsOfNovaRequest, extensionsOfNovaResponse); - - HttpRequest createSecurityGroup = HttpRequest.builder().method("POST").endpoint( - URI.create("https://compute.north.host/v1.1/3456/os-security-groups")).headers( - ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", - authToken).build()) - .payload( - payloadFromStringWithContentType( - "{\"security_group\":{\"name\":\"jclouds#mygroup\",\"description\":\"jclouds#mygroup\"}}", - "application/json")).build(); int groupId = 2769; - + HttpResponse createSecurityGroupResponse = HttpResponse.builder().statusCode(200) .payload( payloadFromStringWithContentType( @@ -68,7 +68,7 @@ public class CreateSecurityGroupInZoneExpectTest extends BaseNovaClientExpectTes "application/json; charset=UTF-8")).build(); builder.put(createSecurityGroup, createSecurityGroupResponse); - + int ruleId = 10331; for (int port : ImmutableList.of(22,8080)){ @@ -110,25 +110,61 @@ public class CreateSecurityGroupInZoneExpectTest extends BaseNovaClientExpectTes } HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint( - URI.create("https://compute.north.host/v1.1/3456/os-security-groups/"+groupId)).headers( + URI.create("https://compute.north.host/v1.1/3456/os-security-groups/" + groupId)).headers( ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", authToken).build()).build(); HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload( payloadFromResource("/securitygroup_details_computeservice_typical.json")).build(); - + builder.put(getSecurityGroup, getSecurityGroupResponse); - NovaClient clientWhenSecurityGroupsExist = requestsSendResponses(builder.build()); + NovaClient clientCanCreateSecurityGroup = requestsSendResponses(builder.build()); - CreateSecurityGroupInZone fn = new CreateSecurityGroupInZone(clientWhenSecurityGroupsExist); + CreateSecurityGroupIfNeeded fn = new CreateSecurityGroupIfNeeded(clientCanCreateSecurityGroup); // we can find it assertEquals(fn.apply( - new ZoneSecurityGroupNameAndPorts("az-1.region-a.geo-1", "jclouds#mygroup", ImmutableList.of(22, 8080))) + new ZoneSecurityGroupNameAndPorts("az-1.region-a.geo-1", "jclouds#mygroup", ImmutableSet.of(22, 8080))) .toString(), new SecurityGroupInZone(new ParseComputeServiceTypicalSecurityGroupTest().expected(), "az-1.region-a.geo-1").toString()); } + public void testReturnExistingGroupOnAlreadyExists() throws Exception { + + Builder builder = ImmutableMap.builder(); + + builder.put(keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess); + builder.put(extensionsOfNovaRequest, extensionsOfNovaResponse); + + HttpResponse createSecurityGroupResponse = HttpResponse.builder().statusCode(400) + .payload( + payloadFromStringWithContentType( + "{\"badRequest\": {\"message\": \"Security group test already exists\", \"code\": 400}}", + "application/json; charset=UTF-8")).build(); + + builder.put(createSecurityGroup, createSecurityGroupResponse); + + HttpRequest listSecurityGroups = HttpRequest.builder().method("GET").endpoint( + URI.create("https://compute.north.host/v1.1/3456/os-security-groups")).headers( + ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", + authToken).build()).build(); + + HttpResponse listSecurityGroupsResponse = HttpResponse.builder().statusCode(200).payload( + payloadFromResource("/securitygroup_list_details_computeservice_typical.json")).build(); + + builder.put(listSecurityGroups, listSecurityGroupsResponse); + + NovaClient clientWhenSecurityGroupsExist = requestsSendResponses(builder.build()); + + CreateSecurityGroupIfNeeded fn = new CreateSecurityGroupIfNeeded(clientWhenSecurityGroupsExist); + + // we can find it + assertEquals(fn.apply( + new ZoneSecurityGroupNameAndPorts("az-1.region-a.geo-1", "jclouds#mygroup", ImmutableSet.of(22, 8080))) + .toString(), new SecurityGroupInZone(new ParseComputeServiceTypicalSecurityGroupTest().expected(), + "az-1.region-a.geo-1").toString()); + + } } diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/FindSecurityGroupWithNameAndReturnTrueExpectTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/FindSecurityGroupWithNameAndReturnTrueExpectTest.java index 11953300ef..d06f9ce5a8 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/FindSecurityGroupWithNameAndReturnTrueExpectTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/functions/FindSecurityGroupWithNameAndReturnTrueExpectTest.java @@ -28,8 +28,8 @@ import java.util.concurrent.atomic.AtomicReference; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.openstack.nova.v1_1.NovaClient; -import org.jclouds.openstack.nova.v1_1.compute.domain.SecurityGroupInZone; -import org.jclouds.openstack.nova.v1_1.compute.domain.ZoneAndName; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.SecurityGroupInZone; +import org.jclouds.openstack.nova.v1_1.domain.zonescoped.ZoneAndName; import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest; import org.jclouds.openstack.nova.v1_1.parse.ParseSecurityGroupListTest; import org.jclouds.openstack.nova.v1_1.predicates.FindSecurityGroupWithNameAndReturnTrue; diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseCreatedServerTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseCreatedServerTest.java index f14cddc225..b322aad8e5 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseCreatedServerTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseCreatedServerTest.java @@ -31,7 +31,7 @@ import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.domain.Link.Relation; import org.jclouds.openstack.nova.v1_1.config.NovaParserModule; import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.ServerStatus; +import org.jclouds.openstack.nova.v1_1.domain.Server.Status; import org.jclouds.rest.annotations.SelectJson; import org.testng.annotations.Test; @@ -62,7 +62,7 @@ public class ParseCreatedServerTest extends BaseItemParserTest { .name("test-e92") .updated(new SimpleDateFormatDateService().iso8601SecondsDateParse("2012-03-19T06:21:13Z")) .created(new SimpleDateFormatDateService().iso8601SecondsDateParse("2012-03-19T06:21:13Z")) - .status(ServerStatus.BUILD) + .status(Status.BUILD) .adminPass("ZWuHcmTMQ7eXoHeM") .image( Resource diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseImageTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseImageTest.java index 2d15812c45..c7579ee71d 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseImageTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseImageTest.java @@ -27,11 +27,11 @@ import org.jclouds.date.internal.SimpleDateFormatDateService; import org.jclouds.json.BaseItemParserTest; import org.jclouds.json.config.GsonModule; import org.jclouds.openstack.domain.Link; -import org.jclouds.openstack.domain.Link.Relation; import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.domain.Link.Relation; import org.jclouds.openstack.nova.v1_1.config.NovaParserModule; import org.jclouds.openstack.nova.v1_1.domain.Image; -import org.jclouds.openstack.nova.v1_1.domain.ImageStatus; +import org.jclouds.openstack.nova.v1_1.domain.Image.Status; import org.jclouds.rest.annotations.SelectJson; import org.testng.annotations.Test; @@ -63,7 +63,7 @@ public class ParseImageTest extends BaseItemParserTest { .created(new SimpleDateFormatDateService().iso8601SecondsDateParse("2010-08-10T12:00:00Z")) .tenantId("12345") .userId("joe") - .status(ImageStatus.SAVING) + .status(Status.SAVING) .progress(80) .minDisk(5) .minRam(256) diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerTest.java index 41487ebc5b..f249f3bdb1 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerTest.java @@ -27,12 +27,12 @@ import org.jclouds.date.internal.SimpleDateFormatDateService; import org.jclouds.json.BaseItemParserTest; import org.jclouds.json.config.GsonModule; import org.jclouds.openstack.domain.Link; -import org.jclouds.openstack.domain.Link.Relation; import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.domain.Link.Relation; import org.jclouds.openstack.nova.v1_1.config.NovaParserModule; import org.jclouds.openstack.nova.v1_1.domain.Address; import org.jclouds.openstack.nova.v1_1.domain.Server; -import org.jclouds.openstack.nova.v1_1.domain.ServerStatus; +import org.jclouds.openstack.nova.v1_1.domain.Server.Status; import org.jclouds.rest.annotations.SelectJson; import org.testng.annotations.Test; @@ -66,7 +66,7 @@ public class ParseServerTest extends BaseItemParserTest { .hostId("e4d909c290d0fb1ca068ffaddf22cbd0") .accessIPv4("67.23.10.132") .accessIPv6("::babe:67.23.10.132") - .status(ServerStatus.BUILD) + .status(Status.BUILD) .image( Resource .builder() diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicatesTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicatesTest.java new file mode 100644 index 0000000000..074657a6a5 --- /dev/null +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/ImagePredicatesTest.java @@ -0,0 +1,46 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.predicates; + +import static org.jclouds.openstack.nova.v1_1.predicates.ImagePredicates.statusEquals; + +import org.jclouds.openstack.nova.v1_1.domain.Image; +import org.jclouds.openstack.nova.v1_1.domain.Image.Status; +import org.jclouds.openstack.nova.v1_1.parse.ParseImageTest; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ImagePredicatesTest") +public class ImagePredicatesTest { + Image ref = new ParseImageTest().expected(); + + @Test + public void teststatusEqualsWhenEqual() { + assert statusEquals(Status.SAVING).apply(ref); + } + + @Test + public void teststatusEqualsWhenNotEqual() { + assert !statusEquals(Status.DELETED).apply(ref); + } + +} diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicatesTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicatesTest.java new file mode 100644 index 0000000000..bf22d10205 --- /dev/null +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/predicates/SecurityGroupPredicatesTest.java @@ -0,0 +1,44 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.v1_1.predicates; + +import static org.jclouds.openstack.nova.v1_1.predicates.SecurityGroupPredicates.nameEquals; + +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "SecurityGroupPredicatesTest") +public class SecurityGroupPredicatesTest { + SecurityGroup ref = SecurityGroup.builder().name("jclouds").description("description").build(); + + @Test + public void testnameEqualsWhenEqual() { + assert nameEquals("jclouds").apply(ref); + } + + @Test + public void testnameEqualsWhenNotEqual() { + assert !nameEquals("foo").apply(ref); + } + +} diff --git a/labs/openstack-nova/src/test/resources/securitygroup_list_details_computeservice_typical.json b/labs/openstack-nova/src/test/resources/securitygroup_list_details_computeservice_typical.json new file mode 100644 index 0000000000..cd1e2394a8 --- /dev/null +++ b/labs/openstack-nova/src/test/resources/securitygroup_list_details_computeservice_typical.json @@ -0,0 +1,53 @@ +{ + "security_groups":[ + { + "rules": [{ + "from_port": 22, + "group": {}, + "ip_protocol": "tcp", + "to_port": 22, + "parent_group_id": 2769, + "ip_range": { + "cidr": "0.0.0.0/0" + }, + "id": 10331 + }, { + "from_port": 22, + "group": { + "tenant_id": "37936628937291", + "name": "jclouds#mygroup" + }, + "ip_protocol": "tcp", + "to_port": 22, + "parent_group_id": 2769, + "ip_range": {}, + "id": 10332 + }, { + "from_port": 8080, + "group": {}, + "ip_protocol": "tcp", + "to_port": 8080, + "parent_group_id": 2769, + "ip_range": { + "cidr": "0.0.0.0/0" + }, + "id": 10333 + }, { + "from_port": 8080, + "group": { + "tenant_id": "37936628937291", + "name": "jclouds#mygroup" + }, + "ip_protocol": "tcp", + "to_port": 8080, + "parent_group_id": 2769, + "ip_range": {}, + "id": 10334 + }], + "tenant_id": "37936628937291", + "id": 2769, + "name": "jclouds#mygroup", + "description": "jclouds#mygroup" + } + ] +} \ No newline at end of file