You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2017/12/22 08:04:25 UTC

jclouds-labs git commit: Implement GetOrCreateNetworkDomainThenCreateNodes Strategy.

Repository: jclouds-labs
Updated Branches:
  refs/heads/master 9c74d22bb -> 5a3b59916


Implement GetOrCreateNetworkDomainThenCreateNodes Strategy.


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/5a3b5991
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/5a3b5991
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/5a3b5991

Branch: refs/heads/master
Commit: 5a3b59916865eef4076f8b708bc57b30e21cebf5
Parents: 9c74d22
Author: Trevor Flanagan <tr...@itaas.dimensiondata.com>
Authored: Wed Dec 20 17:22:58 2017 +0000
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri Dec 22 08:34:32 2017 +0100

----------------------------------------------------------------------
 ...imensionDataCloudControlTemplateOptions.java | 153 ++++++++++++++++
 ...GetOrCreateNetworkDomainThenCreateNodes.java | 177 ++++++++++++++++++
 .../strategy/TemplateWithNetworkIds.java        |  64 +++++++
 ...sionDataCloudControlTemplateOptionsTest.java |  57 ++++++
 ...rCreateNetworkDomainThenCreateNodesTest.java | 178 +++++++++++++++++++
 5 files changed, 629 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5a3b5991/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptions.java
----------------------------------------------------------------------
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptions.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptions.java
new file mode 100644
index 0000000..0d40b38
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptions.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.options;
+
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.javax.annotation.Nullable;
+
+public class DimensionDataCloudControlTemplateOptions extends TemplateOptions implements Cloneable {
+
+   public static final String DEFAULT_NETWORK_DOMAIN_NAME = "JCLOUDS_NETWORK_DOMAIN";
+   public static final String DEFAULT_VLAN_NAME = "JCLOUDS_VLAN";
+   public static final String DEFAULT_PRIVATE_IPV4_BASE_ADDRESS = "10.0.0.0";
+   public static final Integer DEFAULT_PRIVATE_IPV4_PREFIX_SIZE = 24;
+
+   private String networkDomainName;
+   private String defaultPrivateIPv4BaseAddress;
+   private Integer defaultPrivateIPv4PrefixSize;
+
+   public String getNetworkDomainName() {
+      return networkDomainName;
+   }
+
+   public String getDefaultPrivateIPv4BaseAddress() {
+      return defaultPrivateIPv4BaseAddress;
+   }
+
+   public Integer getDefaultPrivateIPv4PrefixSize() {
+      return defaultPrivateIPv4PrefixSize;
+   }
+
+   public DimensionDataCloudControlTemplateOptions networkDomainName(@Nullable String networkDomainName) {
+      this.networkDomainName = networkDomainName;
+      return this;
+   }
+
+   public DimensionDataCloudControlTemplateOptions defaultPrivateIPv4BaseAddress(
+         @Nullable String defaultPrivateIPv4BaseAddress) {
+      this.defaultPrivateIPv4BaseAddress = defaultPrivateIPv4BaseAddress;
+      return this;
+   }
+
+   public DimensionDataCloudControlTemplateOptions defaultPrivateIPv4PrefixSize(
+         @Nullable Integer defaultPrivateIPv4PrefixSize) {
+      this.defaultPrivateIPv4PrefixSize = defaultPrivateIPv4PrefixSize;
+      return this;
+   }
+
+   @Override
+   public DimensionDataCloudControlTemplateOptions clone() {
+      final DimensionDataCloudControlTemplateOptions options = new DimensionDataCloudControlTemplateOptions();
+      copyTo(options);
+      return options;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o) {
+         return true;
+      }
+      if (!(o instanceof DimensionDataCloudControlTemplateOptions)) {
+         return false;
+      }
+      if (!super.equals(o)) {
+         return false;
+      }
+
+      DimensionDataCloudControlTemplateOptions that = (DimensionDataCloudControlTemplateOptions) o;
+
+      if (networkDomainName != null ?
+            !networkDomainName.equals(that.networkDomainName) :
+            that.networkDomainName != null) {
+         return false;
+      }
+
+      if (defaultPrivateIPv4BaseAddress != null ?
+            !defaultPrivateIPv4BaseAddress.equals(that.defaultPrivateIPv4BaseAddress) :
+            that.defaultPrivateIPv4BaseAddress != null) {
+         return false;
+      }
+      return defaultPrivateIPv4PrefixSize != null ?
+            defaultPrivateIPv4PrefixSize.equals(that.defaultPrivateIPv4PrefixSize) :
+            that.defaultPrivateIPv4PrefixSize == null;
+
+   }
+
+   @Override
+   public int hashCode() {
+      int result = super.hashCode();
+      result = 31 * result + (networkDomainName != null ? networkDomainName.hashCode() : 0);
+      result = 31 * result + (defaultPrivateIPv4BaseAddress != null ? defaultPrivateIPv4BaseAddress.hashCode() : 0);
+      result = 31 * result + (defaultPrivateIPv4PrefixSize != null ? defaultPrivateIPv4PrefixSize.hashCode() : 0);
+      return result;
+   }
+
+   @Override
+   public String toString() {
+      return "DimensionDataCloudControlTemplateOptions{ networkDomainName='" + networkDomainName
+            + "', defaultPrivateIPv4BaseAddress='" + defaultPrivateIPv4BaseAddress + "', defaultPrivateIPv4PrefixSize='"
+            + defaultPrivateIPv4PrefixSize + "'}";
+   }
+
+   public static class Builder {
+
+      /**
+       * @see #networkDomainName
+       */
+      public static DimensionDataCloudControlTemplateOptions networkDomainName(final String networkDomainName) {
+         final DimensionDataCloudControlTemplateOptions options = new DimensionDataCloudControlTemplateOptions();
+         return options.networkDomainName(networkDomainName);
+      }
+
+      /**
+       * @see #defaultPrivateIPv4BaseAddress
+       */
+      public static DimensionDataCloudControlTemplateOptions defaultPrivateIPv4BaseAddress(
+            final String defaultPrivateIPv4BaseAddress) {
+         final DimensionDataCloudControlTemplateOptions options = new DimensionDataCloudControlTemplateOptions();
+         return options.defaultPrivateIPv4BaseAddress(defaultPrivateIPv4BaseAddress);
+      }
+
+      /**
+       * @see #defaultPrivateIPv4PrefixSize
+       */
+      public static DimensionDataCloudControlTemplateOptions defaultPrivateIPv4PrefixSize(
+            final Integer defaultPrivateIPv4PrefixSize) {
+         final DimensionDataCloudControlTemplateOptions options = new DimensionDataCloudControlTemplateOptions();
+         return options.defaultPrivateIPv4PrefixSize(defaultPrivateIPv4PrefixSize);
+      }
+
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public DimensionDataCloudControlTemplateOptions nodeNames(Iterable<String> nodeNames) {
+      return DimensionDataCloudControlTemplateOptions.class.cast(super.nodeNames(nodeNames));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5a3b5991/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodes.java
----------------------------------------------------------------------
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodes.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodes.java
new file mode 100644
index 0000000..a427ed4
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodes.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.strategy;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.dimensiondata.cloudcontrol.DimensionDataCloudControlApi;
+import org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NetworkDomain;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Vlan;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static java.lang.String.format;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_NETWORK_DOMAIN_NAME;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_VLAN_NAME;
+import static org.jclouds.dimensiondata.cloudcontrol.config.DimensionDataCloudControlComputeServiceContextModule.NETWORK_DOMAIN_NORMAL_PREDICATE;
+import static org.jclouds.dimensiondata.cloudcontrol.config.DimensionDataCloudControlComputeServiceContextModule.VLAN_NORMAL_PREDICATE;
+
+@Singleton
+public class GetOrCreateNetworkDomainThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet {
+
+   private final DimensionDataCloudControlApi api;
+   private final ComputeServiceConstants.Timeouts timeouts;
+   private final Predicate<String> networkDomainNormalPredicate;
+   private final Predicate<String> vlanNormalPredicate;
+
+   @Inject
+   protected GetOrCreateNetworkDomainThenCreateNodes(final CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy,
+         final ListNodesStrategy listNodesStrategy, final GroupNamingConvention.Factory namingConvention,
+         final ListeningExecutorService userExecutor,
+         final CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
+         final DimensionDataCloudControlApi api, final ComputeServiceConstants.Timeouts timeouts,
+         @Named(NETWORK_DOMAIN_NORMAL_PREDICATE) final Predicate<String> networkDomainNormalPredicate,
+         @Named(VLAN_NORMAL_PREDICATE) final Predicate<String> vlanNormalPredicate) {
+      super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor,
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
+      this.api = api;
+      this.timeouts = timeouts;
+      this.networkDomainNormalPredicate = networkDomainNormalPredicate;
+      this.vlanNormalPredicate = vlanNormalPredicate;
+   }
+
+   @Override
+   public Map<?, ListenableFuture<Void>> execute(final String group, final int count, final Template template,
+         final Set<NodeMetadata> goodNodes, final Map<NodeMetadata, Exception> badNodes,
+         final Multimap<NodeMetadata, CustomizationResponse> customizationResponses) {
+
+      final DimensionDataCloudControlTemplateOptions templateOptions = template.getOptions()
+            .as(DimensionDataCloudControlTemplateOptions.class);
+
+      String networkDomainName = firstNonNull(templateOptions.getNetworkDomainName(), DEFAULT_NETWORK_DOMAIN_NAME);
+      String vlanName = firstNonNull(
+            templateOptions.getNetworks().isEmpty() ? null : templateOptions.getNetworks().iterator().next(),
+            DEFAULT_VLAN_NAME);
+      templateOptions.networkDomainName(networkDomainName);
+      String networkDomainId = tryCreateOrGetExistingNetworkDomainId(template.getLocation().getId(), networkDomainName);
+      String vlanId = tryCreateOrGetExistingVlanId(networkDomainId, vlanName, templateOptions);
+      templateOptions.networks(vlanName);
+      return super
+            .execute(group, count, new TemplateWithNetworkIds(template, networkDomainId, vlanId), goodNodes, badNodes,
+                  customizationResponses);
+   }
+
+   private String tryCreateOrGetExistingNetworkDomainId(final String datacenterId, final String networkDomainName) {
+      String networkDomainId = getExistingNetworkDomainId(datacenterId, networkDomainName).orNull();
+      if (networkDomainId != null) {
+         logger.debug("Found a suitable existing network domain %s", networkDomainId);
+      } else {
+         networkDomainId = deployNeworkDomain(datacenterId, networkDomainName);
+      }
+      return networkDomainId;
+   }
+
+   private String deployNeworkDomain(final String datacenter, final String networkDomainName) {
+      logger.debug("Creating a network domain '%s' in Datacenter '%s' ...", networkDomainName, datacenter);
+      String networkDomainId = api.getNetworkApi()
+            .deployNetworkDomain(datacenter, networkDomainName, "network domain created by jclouds",
+                  NetworkDomain.Type.ESSENTIALS.name());
+      String message = format("networkDomain(%s) is not ready within %d ms.", networkDomainId, timeouts.nodeRunning);
+
+      if (!networkDomainNormalPredicate.apply(networkDomainId)) {
+         throw new IllegalStateException(message);
+      }
+      return networkDomainId;
+   }
+
+   private Optional<String> getExistingNetworkDomainId(final String datacenterId, final String networkDomainName) {
+      Optional<NetworkDomain> networkDomainOptional = api.getNetworkApi()
+            .listNetworkDomainsWithDatacenterIdAndName(datacenterId, networkDomainName).concat().first();
+      if (networkDomainOptional.isPresent()) {
+         return Optional.of(networkDomainOptional.get().id());
+      } else {
+         return Optional.<String>absent();
+      }
+   }
+
+   private String tryCreateOrGetExistingVlanId(final String networkDomainId, final String vlanName,
+         final DimensionDataCloudControlTemplateOptions templateOptions) {
+
+      String vlanId = getExistingVlan(networkDomainId, vlanName).orNull();
+      if (vlanId != null) {
+         logger.debug("Found a suitable existing vlan %s", vlanId);
+      } else {
+         vlanId = deployVlan(networkDomainId, vlanName, templateOptions);
+      }
+      return vlanId;
+
+   }
+
+   private String deployVlan(final String networkDomainId, final String vlanName,
+         final DimensionDataCloudControlTemplateOptions templateOptions) {
+      logger.debug("Creating a vlan %s in network domain '%s' ...", vlanName, networkDomainId);
+      String defaultPrivateIPv4BaseAddress = firstNonNull(templateOptions.getDefaultPrivateIPv4BaseAddress(),
+            DimensionDataCloudControlTemplateOptions.DEFAULT_PRIVATE_IPV4_BASE_ADDRESS);
+      Integer defaultPrivateIPv4PrefixSize = firstNonNull(templateOptions.getDefaultPrivateIPv4PrefixSize(),
+            DimensionDataCloudControlTemplateOptions.DEFAULT_PRIVATE_IPV4_PREFIX_SIZE);
+
+      String vlanId = api.getNetworkApi()
+            .deployVlan(networkDomainId, vlanName, "vlan created by jclouds", defaultPrivateIPv4BaseAddress,
+                  defaultPrivateIPv4PrefixSize);
+      if (!vlanNormalPredicate.apply(vlanId)) {
+         String message = format("vlan(%s) is not ready within %d ms.", vlanId, timeouts.nodeRunning);
+         throw new IllegalStateException(message);
+      }
+      return vlanId;
+   }
+
+   private Optional<String> getExistingVlan(final String networkDomainId, final String vlanName) {
+      FluentIterable<Vlan> vlans = api.getNetworkApi().listVlans(networkDomainId).concat();
+      final Optional<Vlan> vlan = vlans.firstMatch(new Predicate<Vlan>() {
+         @Override
+         public boolean apply(final Vlan input) {
+            return input.name().equals(vlanName);
+         }
+      });
+
+      if (vlan.isPresent()) {
+         return Optional.of(vlan.get().id());
+      } else {
+         return Optional.absent();
+      }
+   }
+}
+

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5a3b5991/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/TemplateWithNetworkIds.java
----------------------------------------------------------------------
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/TemplateWithNetworkIds.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/TemplateWithNetworkIds.java
new file mode 100644
index 0000000..496a652
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/TemplateWithNetworkIds.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.strategy;
+
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.domain.Location;
+
+/**
+ * Extend the default {@link Template} object with extra identifier information about where the nodes must be created.
+ */
+public class TemplateWithNetworkIds implements Template {
+
+   private final Template delegate;
+   private final String networkDomainId;
+   private final String vlanId;
+
+   public TemplateWithNetworkIds(Template template, String networkDomainId, String vlanId) {
+      this.delegate = template;
+      this.networkDomainId = networkDomainId;
+      this.vlanId = vlanId;
+   }
+
+   @Override
+   public Image getImage() {
+      return delegate.getImage();
+   }
+
+   @Override
+   public Hardware getHardware() {
+      return delegate.getHardware();
+   }
+
+   @Override
+   public Location getLocation() {
+      return delegate.getLocation();
+   }
+
+   @Override
+   public TemplateOptions getOptions() {
+      return delegate.getOptions();
+   }
+
+   @Override
+   public Template clone() {
+      return new TemplateWithNetworkIds(delegate.clone(), networkDomainId, vlanId);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5a3b5991/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptionsTest.java
----------------------------------------------------------------------
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptionsTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptionsTest.java
new file mode 100644
index 0000000..02cfbc5
--- /dev/null
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/options/DimensionDataCloudControlTemplateOptionsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.options;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+
+@Test(groups = "unit", testName = "DimensionDataCloudControlTemplateOptionsTest")
+public class DimensionDataCloudControlTemplateOptionsTest {
+
+   private DimensionDataCloudControlTemplateOptions templateOptions;
+   private String networkDomainName;
+   private String defaultPrivateIPv4BaseAddress;
+   private int defaultPrivateIPv4PrefixSize;
+
+   @BeforeMethod
+   public void setUp() throws Exception {
+      networkDomainName = "networkDomainName";
+      defaultPrivateIPv4BaseAddress = "defaultPrivateIPv4BaseAddress";
+      defaultPrivateIPv4PrefixSize = 100;
+      templateOptions = DimensionDataCloudControlTemplateOptions.Builder.networkDomainName(networkDomainName)
+            .defaultPrivateIPv4BaseAddress(defaultPrivateIPv4BaseAddress)
+            .defaultPrivateIPv4PrefixSize(defaultPrivateIPv4PrefixSize);
+   }
+
+   @Test
+   public void testBuilder() throws Exception {
+      assertEquals(networkDomainName, templateOptions.getNetworkDomainName());
+      assertEquals(defaultPrivateIPv4BaseAddress, templateOptions.getDefaultPrivateIPv4BaseAddress());
+      assertEquals(defaultPrivateIPv4PrefixSize, templateOptions.getDefaultPrivateIPv4PrefixSize().intValue());
+   }
+
+   @Test
+   public void testEquals() throws Exception {
+      assertTrue(templateOptions.equals(
+            DimensionDataCloudControlTemplateOptions.Builder.networkDomainName(networkDomainName)
+                  .defaultPrivateIPv4BaseAddress(defaultPrivateIPv4BaseAddress)
+                  .defaultPrivateIPv4PrefixSize(defaultPrivateIPv4PrefixSize)));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/5a3b5991/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodesTest.java
----------------------------------------------------------------------
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodesTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodesTest.java
new file mode 100644
index 0000000..0fef94d
--- /dev/null
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/strategy/GetOrCreateNetworkDomainThenCreateNodesTest.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.strategy;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.easymock.EasyMock;
+import org.jclouds.collect.IterableWithMarkers;
+import org.jclouds.collect.PagedIterables;
+import org.jclouds.compute.config.CustomizationResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
+import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.dimensiondata.cloudcontrol.DimensionDataCloudControlApi;
+import org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions;
+import org.jclouds.dimensiondata.cloudcontrol.domain.IpRange;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NetworkDomain;
+import org.jclouds.dimensiondata.cloudcontrol.domain.State;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Vlan;
+import org.jclouds.dimensiondata.cloudcontrol.features.NetworkApi;
+import org.jclouds.domain.Location;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Collections;
+import java.util.Date;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMockSupport.injectMocks;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_NETWORK_DOMAIN_NAME;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_PRIVATE_IPV4_BASE_ADDRESS;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_PRIVATE_IPV4_PREFIX_SIZE;
+import static org.jclouds.dimensiondata.cloudcontrol.compute.options.DimensionDataCloudControlTemplateOptions.DEFAULT_VLAN_NAME;
+import static org.testng.AssertJUnit.assertEquals;
+
+@Test(groups = "unit", testName = "GetOrCreateNetworkDomainThenCreateNodesTest")
+public class GetOrCreateNetworkDomainThenCreateNodesTest {
+
+   private GetOrCreateNetworkDomainThenCreateNodes getOrCreateNetworkDomainThenCreateNodes;
+   private NetworkApi networkApi;
+   private DimensionDataCloudControlApi api;
+   private Template template;
+   private DimensionDataCloudControlTemplateOptions templateOptions;
+   private CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy;
+   private ListNodesStrategy listNodesStrategy;
+   private GroupNamingConvention.Factory namingConvention;
+   private ListeningExecutorService userExecutor;
+   private CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory;
+   private org.jclouds.compute.reference.ComputeServiceConstants.Timeouts timeouts;
+   private Location location;
+   private String datacenterId;
+   private NetworkDomain networkDomain;
+   private Vlan vlan;
+
+   @BeforeMethod
+   public void setUp() throws Exception {
+      networkApi = EasyMock.createMock(NetworkApi.class);
+      api = EasyMock.createMock(DimensionDataCloudControlApi.class);
+      template = EasyMock.createNiceMock(Template.class);
+      addNodeWithGroupStrategy = EasyMock.createNiceMock(CreateNodeWithGroupEncodedIntoName.class);
+      listNodesStrategy = EasyMock.createNiceMock(ListNodesStrategy.class);
+      namingConvention = EasyMock.createNiceMock(GroupNamingConvention.Factory.class);
+      userExecutor = EasyMock.createNiceMock(ListeningExecutorService.class);
+      customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory = EasyMock
+            .createNiceMock(CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory.class);
+      location = createNiceMock(Location.class);
+      templateOptions = new DimensionDataCloudControlTemplateOptions();
+      templateOptions.nodeNames(Sets.newHashSet("node1"));
+      datacenterId = "datacenterId";
+
+      timeouts = new ComputeServiceConstants.Timeouts();
+
+      final Predicate<String> alwaysTrue = new Predicate<String>() {
+         @Override
+         public boolean apply(final String input) {
+            return true;
+         }
+      };
+
+      getOrCreateNetworkDomainThenCreateNodes = new GetOrCreateNetworkDomainThenCreateNodes(addNodeWithGroupStrategy,
+            listNodesStrategy, namingConvention, userExecutor,
+            customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, api, timeouts, alwaysTrue, alwaysTrue);
+
+      networkDomain = NetworkDomain.builder().id("690de302-bb80-49c6-b401-8c02bbefb945")
+            .name(DEFAULT_NETWORK_DOMAIN_NAME).build();
+      vlan = Vlan.builder().networkDomain(networkDomain).id("vlanId").name(DEFAULT_VLAN_NAME).description("")
+            .privateIpv4Range(IpRange.create("10.0.0.0", 24))
+            .ipv6Range(IpRange.create("2607:f480:111:1575:0:0:0:0", 64)).ipv4GatewayAddress("10.0.0.1")
+            .ipv6GatewayAddress("2607:f480:111:1575:0:0:0:1").createTime(new Date()).state(State.NORMAL)
+            .datacenterId("NA9").build();
+
+      injectMocks(api);
+      injectMocks(template);
+      injectMocks(networkApi);
+      expect(template.getOptions()).andReturn(templateOptions).anyTimes();
+      expect(template.getLocation()).andReturn(location);
+      expect(location.getId()).andReturn(datacenterId);
+
+      expect(api.getNetworkApi()).andReturn(networkApi).anyTimes();
+   }
+
+   @Test
+   public void testExecute() throws Exception {
+      expect(networkApi.listNetworkDomainsWithDatacenterIdAndName(datacenterId, DEFAULT_NETWORK_DOMAIN_NAME))
+            .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(networkDomain))));
+
+      expect(networkApi.listVlans(networkDomain.id()))
+            .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.newArrayList(vlan))));
+
+      replay(networkApi, api, template, location);
+
+      executeAndAssert();
+   }
+
+   @Test(dependsOnMethods = "testExecute")
+   public void testExecute_deployNetworkDomain_deployVlan() throws Exception {
+      expect(networkApi.listNetworkDomainsWithDatacenterIdAndName(datacenterId, DEFAULT_NETWORK_DOMAIN_NAME))
+            .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<NetworkDomain>newArrayList())));
+
+      final String deployedNetworkDomainId = "deployedNetworkDomainId";
+      final String networkDomainDescription = "network domain created by jclouds";
+
+      final NetworkDomain deployedNetworkDomain = NetworkDomain.builder().id(deployedNetworkDomainId)
+            .description(networkDomainDescription).name(DEFAULT_NETWORK_DOMAIN_NAME).state(State.NORMAL).build();
+      expect(networkApi.deployNetworkDomain(datacenterId, DEFAULT_NETWORK_DOMAIN_NAME, networkDomainDescription,
+            NetworkDomain.Type.ESSENTIALS.name())).andReturn(deployedNetworkDomainId);
+      expect(networkApi.getNetworkDomain(deployedNetworkDomainId)).andReturn(deployedNetworkDomain);
+
+      final String deployedVlanId = "deployedVlanId";
+      final String deployedVlanDescription = "vlan created by jclouds";
+      Vlan deployedVlan = Vlan.builder().networkDomain(deployedNetworkDomain).id("deployedVlanId")
+            .name(DEFAULT_VLAN_NAME).description("").privateIpv4Range(IpRange.create("10.0.0.0", 24))
+            .ipv6Range(IpRange.create("2607:f480:111:1575:0:0:0:0", 64)).ipv4GatewayAddress("10.0.0.1")
+            .ipv6GatewayAddress("2607:f480:111:1575:0:0:0:1").createTime(new Date()).state(State.NORMAL)
+            .datacenterId(datacenterId).build();
+      expect(networkApi.listVlans(deployedNetworkDomain.id()))
+            .andReturn(PagedIterables.onlyPage(IterableWithMarkers.from(Lists.<Vlan>newArrayList())));
+      expect(networkApi.deployVlan(deployedNetworkDomainId, DEFAULT_VLAN_NAME, deployedVlanDescription,
+            DEFAULT_PRIVATE_IPV4_BASE_ADDRESS, DEFAULT_PRIVATE_IPV4_PREFIX_SIZE)).andReturn(deployedVlanId);
+      expect(networkApi.getVlan(deployedVlanId)).andReturn(deployedVlan);
+
+      replay(networkApi, api, template, location);
+
+      executeAndAssert();
+   }
+
+   private void executeAndAssert() {
+      getOrCreateNetworkDomainThenCreateNodes.execute("group", 0, template, Collections.<NodeMetadata>emptySet(),
+            Collections.<NodeMetadata, Exception>emptyMap(),
+            ArrayListMultimap.<NodeMetadata, CustomizationResponse>create());
+
+      assertEquals(DEFAULT_NETWORK_DOMAIN_NAME, templateOptions.getNetworkDomainName());
+      assertEquals(DEFAULT_VLAN_NAME, templateOptions.getNetworks().iterator().next());
+   }
+}