Implement ServerWithNatRuleToNodeMetadata Function
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystem.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystem.java
new file mode 100644
index 0000000..b51d273
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystem.java
@@ -0,0 +1,48 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Function;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.jclouds.compute.domain.OperatingSystem;
+
+import javax.annotation.Nullable;
+
+@Singleton
+public class OperatingSystemToOperatingSystem
+      implements Function<org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem, OperatingSystem> {
+
+   private final OperatingSystemToOsFamily operatingSystemToOsFamily;
+
+   @Inject
+   OperatingSystemToOperatingSystem(final OperatingSystemToOsFamily operatingSystemToOsFamily) {
+      this.operatingSystemToOsFamily = operatingSystemToOsFamily;
+   }
+
+   @Nullable
+   @Override
+   public OperatingSystem apply(@Nullable org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem from) {
+
+      OperatingSystem.Builder builder = OperatingSystem.builder();
+      builder.name(from.displayName());
+      builder.family(operatingSystemToOsFamily.apply(from));
+      builder.is64Bit(from.id().endsWith("64"));
+      builder.description(from.family() + " " + from.displayName());
+      return builder.build();
+   }
+}
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardware.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardware.java
new file mode 100644
index 0000000..74eb367
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardware.java
@@ -0,0 +1,65 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.inject.Singleton;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.VolumeBuilder;
+import org.jclouds.dimensiondata.cloudcontrol.domain.CPU;
+import org.jclouds.dimensiondata.cloudcontrol.domain.CpuSpeed;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Disk;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Singleton
+public class ServerToHardware implements Function<Server, Hardware> {
+
+   private static final int GB_TO_MB_MULTIPLIER = 1024;
+
+   @Override
+   public Hardware apply(final Server from) {
+      HardwareBuilder builder = new HardwareBuilder().ids(from.id()).name(from.name()).hypervisor("vmx")
+            .processors(buildProcessorList(from.cpu())).ram(from.memoryGb() * GB_TO_MB_MULTIPLIER);
+
+      if (from.disks() != null) {
+         builder.volumes(FluentIterable.from(from.disks()).transform(new Function<Disk, Volume>() {
+            @Override
+            public Volume apply(final Disk disk) {
+               return new VolumeBuilder().id(disk.id()).device(String.valueOf(disk.scsiId()))
+                     .size(Float.valueOf(disk.sizeGb())).type(Volume.Type.LOCAL).build();
+            }
+         }).toSet());
+      }
+      return builder.build();
+   }
+
+   private List<Processor> buildProcessorList(final CPU cpu) {
+      final List<Processor> processorList = new ArrayList<Processor>();
+      final double speed = CpuSpeed.fromDimensionDataSpeed(cpu.speed()).getSpeed();
+      for (int count = 0; count < cpu.count(); count++) {
+         processorList.add(new Processor(cpu.coresPerSocket(), speed));
+      }
+      return processorList;
+   }
+}
diff --git a/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadata.java b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadata.java
new file mode 100644
index 0000000..6ffb9fe
--- /dev/null
+++ b/dimensiondata/src/main/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadata.java
@@ -0,0 +1,106 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NIC;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Server;
+import org.jclouds.dimensiondata.cloudcontrol.domain.State;
+import org.jclouds.dimensiondata.cloudcontrol.domain.internal.ServerWithExternalIp;
+import org.jclouds.domain.Location;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+import static com.google.common.collect.Iterables.find;
+import static org.jclouds.location.predicates.LocationPredicates.idEquals;
+
+@Singleton
+class ServerWithNatRuleToNodeMetadata implements Function<ServerWithExternalIp, NodeMetadata> {
+
+   private static final Map<State, NodeMetadata.Status> serverStateToNodeStatus = ImmutableMap.<State, NodeMetadata.Status>builder()
+         .put(State.PENDING_DELETE, NodeMetadata.Status.PENDING).put(State.PENDING_CHANGE, NodeMetadata.Status.PENDING)
+         .put(State.FAILED_ADD, NodeMetadata.Status.ERROR).put(State.FAILED_CHANGE, NodeMetadata.Status.ERROR)
+         .put(State.FAILED_DELETE, NodeMetadata.Status.ERROR).put(State.DELETED, NodeMetadata.Status.TERMINATED)
+         .put(State.NORMAL, NodeMetadata.Status.RUNNING).put(State.UNRECOGNIZED, NodeMetadata.Status.UNRECOGNIZED)
+         .build();
+
+   private final Supplier<Set<? extends Location>> locations;
+   private final GroupNamingConvention nodeNamingConvention;
+   private final ServerToHardware serverToHardware;
+   private final OperatingSystemToOperatingSystem operatingSystemToOperatingSystem;
+
+   @Inject
+   ServerWithNatRuleToNodeMetadata(@Memoized final Supplier<Set<? extends Location>> locations,
+         final GroupNamingConvention.Factory namingConvention, final ServerToHardware serverToHardware,
+         final OperatingSystemToOperatingSystem operatingSystemToOperatingSystem) {
+      this.nodeNamingConvention = checkNotNull(namingConvention, "namingConvention").createWithoutPrefix();
+      this.locations = checkNotNull(locations, "locations");
+      this.serverToHardware = checkNotNull(serverToHardware, "serverToHardware");
+      this.operatingSystemToOperatingSystem = checkNotNull(operatingSystemToOperatingSystem,
+            "operatingSystemToOperatingSystem");
+   }
+
+   @Override
+   public NodeMetadata apply(final ServerWithExternalIp serverWithExternalIp) {
+      NodeMetadataBuilder builder = new NodeMetadataBuilder();
+      Server server = serverWithExternalIp.server();
+      builder.ids(server.id());
+      builder.name(server.name());
+      builder.location(find(locations.get(), idEquals(nullToEmpty(server.datacenterId()))));
+      builder.group(nodeNamingConvention.groupInUniqueNameOrNull(server.name()));
+      builder.hardware(serverToHardware.apply(serverWithExternalIp.server()));
+      builder.imageId(server.sourceImageId());
+      builder.operatingSystem(operatingSystemToOperatingSystem.apply(server.guest().operatingSystem()));
+      builder.status(serverStateToNodeStatus.get(server.state()));
+
+      Set<String> privateAddresses = new HashSet<String>();
+      if (server.networkInfo() != null) {
+         if (server.networkInfo().primaryNic() != null && server.networkInfo().primaryNic().privateIpv4() != null) {
+            privateAddresses.add(server.networkInfo().primaryNic().privateIpv4());
+         }
+         if (!server.networkInfo().additionalNic().isEmpty()) {
+            privateAddresses.addAll(Sets.newHashSet(
+                  Iterables.transform(server.networkInfo().additionalNic(), new Function<NIC, String>() {
+                     @Override
+                     public String apply(NIC nic) {
+                        return nic.privateIpv4();
+                     }
+                  })));
+         }
+      }
+      builder.privateAddresses(privateAddresses);
+      if (serverWithExternalIp.externalIp() != null) {
+         builder.publicAddresses(ImmutableSet.of(serverWithExternalIp.externalIp()));
+      }
+      return builder.build();
+   }
+}
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/BaseImageToHardwareTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/BaseImageToHardwareTest.java
index bc09d1c..ca90da2 100644
--- a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/BaseImageToHardwareTest.java
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/BaseImageToHardwareTest.java
@@ -40,7 +40,7 @@
 
 import static org.testng.AssertJUnit.assertEquals;
 
-@Test(groups = "unit", testName = "ImageDescriptionToOsFamilyTest")
+@Test(groups = "unit", testName = "BaseImageToHardwareTest")
 public class BaseImageToHardwareTest {
 
    private BaseImageToHardware baseImageToHardware;
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystemTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystemTest.java
new file mode 100644
index 0000000..8fc8286
--- /dev/null
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/OperatingSystemToOperatingSystemTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.functions;
+
+import org.easymock.EasyMock;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.testng.annotations.Test;
+
+import static org.easymock.EasyMock.expect;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+@Test(testName = "OperatingSystemToOperatingSystemTest")
+public class OperatingSystemToOperatingSystemTest {
+
+   private OperatingSystemToOsFamily operatingSystemToOsFamily;
+
+   public void testApply(){
+
+      String id = "Windows10x64";
+      String name = "testWindowsOS";
+      String family = "Windows";
+
+      org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem operatingSystem = org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem.builder().id(id).displayName(name).family(family).build();
+
+      operatingSystemToOsFamily = EasyMock.createNiceMock(OperatingSystemToOsFamily.class);
+      expect(operatingSystemToOsFamily.apply(operatingSystem)).andReturn(OsFamily.WINDOWS);
+
+      EasyMock.replay(operatingSystemToOsFamily);
+
+      OperatingSystem result = new OperatingSystemToOperatingSystem(operatingSystemToOsFamily).apply(operatingSystem);
+
+      assertNotNull(result);
+      assertEquals(result.getName(), operatingSystem.displayName());
+      assertEquals(result.getFamily(), OsFamily.WINDOWS);
+   }
+}
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardwareTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardwareTest.java
new file mode 100644
index 0000000..c5d0426
--- /dev/null
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerToHardwareTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.dimensiondata.cloudcontrol.compute.functions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.dimensiondata.cloudcontrol.domain.CPU;
+import org.jclouds.dimensiondata.cloudcontrol.domain.CpuSpeed;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Disk;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Guest;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NIC;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NetworkInfo;
+import org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Server;
+import org.jclouds.dimensiondata.cloudcontrol.domain.State;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.xml.bind.DatatypeConverter;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+@Test(groups = "unit", testName = "ServerToHardwareTest")
+public class ServerToHardwareTest {
+
+   private ServerToHardware serverToHardware;
+
+   @BeforeMethod
+   public void setUp() throws Exception {
+      serverToHardware = new ServerToHardware();
+   }
+
+   @Test
+   public void testApplyServer() throws Exception {
+      final Server server = Server.builder().id("12ea8472-6e4e-4068-b2cb-f04ecacd3962").name("CentOS 5 64-bit")
+            .description("DRaaS CentOS Release 5.9 64-bit").guest(Guest.builder().osCustomization(false)
+                  .operatingSystem(
+                        OperatingSystem.builder().id("CENTOS564").displayName("CENTOS5/64").family("UNIX").build())
+                  .build()).cpu(CPU.builder().count(1).speed("STANDARD").coresPerSocket(1).build()).memoryGb(4)
+            .networkInfo(NetworkInfo.builder().primaryNic(
+                  NIC.builder().id("def96a04-d1ee-48b9-b07d-3993594724d2").privateIpv4("192.168.1.2")
+                        .vlanId("19737c24-259a-49e2-a5b7-a8a042a96108").build())
+                  .additionalNic(Lists.<NIC>newArrayList()).networkDomainId("testNetworkDomain").build()).disks(ImmutableList
+                  .of(Disk.builder().id("98299851-37a3-4ebe-9cf1-090da9ae42a0").scsiId(0).sizeGb(20).speed("STANDARD")
+                        .build())).softwareLabels(Lists.newArrayList())
+            .createTime(DatatypeConverter.parseDateTime("2016-06-09T17:36:31.000Z").getTime()).datacenterId("EU6")
+            .state(State.NORMAL).sourceImageId("1806fe4a-0400-46ad-a6ab-1fe3c9ebc947").started(false).deployed(true)
+            .build();
+      applyAndAssert(server);
+   }
+
+   private void applyAndAssert(Server server) {
+      final Hardware hardware = serverToHardware.apply(server);
+      assertEquals(server.memoryGb() * 1024, hardware.getRam());
+      assertEquals("vmx", hardware.getHypervisor());
+      assertEquals(server.id(), hardware.getId());
+      assertEquals(server.id(), hardware.getProviderId());
+      assertEquals(server.name(), hardware.getName());
+      assertEquals(server.disks().size(), hardware.getVolumes().size());
+      assertEquals(Float.valueOf(server.disks().get(0).sizeGb()), hardware.getVolumes().get(0).getSize());
+      assertEquals(Volume.Type.LOCAL, hardware.getVolumes().get(0).getType());
+      assertEquals(server.disks().get(0).id(), hardware.getVolumes().get(0).getId());
+      assertEquals(server.disks().get(0).scsiId().toString(), hardware.getVolumes().get(0).getDevice());
+      assertEquals(server.cpu().count(), hardware.getProcessors().size());
+      assertEquals(Double.valueOf(server.cpu().coresPerSocket()), hardware.getProcessors().get(0).getCores());
+      assertEquals(CpuSpeed.STANDARD.getSpeed(), hardware.getProcessors().get(0).getSpeed());
+   }
+
+}
diff --git a/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadataTest.java b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadataTest.java
new file mode 100644
index 0000000..49fd60c
--- /dev/null
+++ b/dimensiondata/src/test/java/org/jclouds/dimensiondata/cloudcontrol/compute/functions/ServerWithNatRuleToNodeMetadataTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.functions;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.easymock.EasyMock;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.dimensiondata.cloudcontrol.domain.CPU;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Guest;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NIC;
+import org.jclouds.dimensiondata.cloudcontrol.domain.NetworkInfo;
+import org.jclouds.dimensiondata.cloudcontrol.domain.OperatingSystem;
+import org.jclouds.dimensiondata.cloudcontrol.domain.Server;
+import org.jclouds.dimensiondata.cloudcontrol.domain.State;
+import org.jclouds.dimensiondata.cloudcontrol.domain.internal.ServerWithExternalIp;
+import org.jclouds.dimensiondata.cloudcontrol.features.ServerImageApi;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Set;
+
+import static org.easymock.EasyMock.expect;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = "unit", testName = "ServerWithNatRuleToNodeMetadataTest")
+public class ServerWithNatRuleToNodeMetadataTest {
+
+   private ServerWithNatRuleToNodeMetadata serverWithNatRuleToNodeMetadata;
+   private ServerWithExternalIp serverWithExternalIp;
+
+   private GroupNamingConvention nodeNamingConvention;
+   private OperatingSystemToOperatingSystem operatingSystemToOperatingSystem;
+   private ServerToHardware serverToHardware;
+   private ServerImageApi serverImageApi;
+   private Image image;
+   private Hardware hardware;
+   private OperatingSystem os;
+   private NIC nic;
+   private Location location;
+   private CPU cpu;
+   private Server server;
+   private org.jclouds.compute.domain.OperatingSystem operatingSystem;
+
+   private String imageId = "imageId";
+   private String externalIp = "10.20.122.1";
+   private String datacenterId = "NA01";
+   private String serverName = "serverName";
+   private String networkDomainId = "NetworkDomain1";
+
+   @BeforeMethod
+   public void setUp() throws Exception {
+
+      location = new LocationBuilder().scope(LocationScope.REGION).id(datacenterId)
+            .iso3166Codes(new ArrayList<String>())
+            .metadata(ImmutableMap.<String, Object>of("version", "MCP 2.0", "state", "AVAILABLE"))
+            .description("locationDescription")
+            .parent(new LocationBuilder().id("us").description("USA").scope(LocationScope.PROVIDER).build()).build();
+      Supplier<Set<? extends Location>> locations = new Supplier<Set<? extends Location>>() {
+         @Override
+         public Set<? extends Location> get() {
+            return ImmutableSet.of(location);
+         }
+      };
+
+      nic = EasyMock.createNiceMock(NIC.class);
+      cpu = EasyMock.createNiceMock(CPU.class);
+      os = EasyMock.createNiceMock(OperatingSystem.class);
+      hardware = EasyMock.createNiceMock(Hardware.class);
+      operatingSystem = EasyMock.createNiceMock(org.jclouds.compute.domain.OperatingSystem.class);
+      serverToHardware = EasyMock.createNiceMock(ServerToHardware.class);
+      operatingSystemToOperatingSystem = EasyMock.createNiceMock(OperatingSystemToOperatingSystem.class);
+
+      GroupNamingConvention.Factory conventionFactory = EasyMock.createNiceMock(GroupNamingConvention.Factory.class);
+      nodeNamingConvention = EasyMock.createNiceMock(GroupNamingConvention.class);
+      serverImageApi = EasyMock.createNiceMock(ServerImageApi.class);
+      image = EasyMock.createNiceMock(Image.class);
+
+      expect(conventionFactory.createWithoutPrefix()).andReturn(nodeNamingConvention);
+      EasyMock.replay(conventionFactory);
+
+      server = Server.builder().id("serverId").name(serverName).datacenterId(datacenterId)
+            .networkInfo(NetworkInfo.create(networkDomainId, nic, new ArrayList<NIC>())).cpu(cpu).deployed(true)
+            .state(State.NORMAL).sourceImageId("imageId").started(false).createTime(new Date()).memoryGb(1024)
+            .guest(Guest.builder().osCustomization(false).operatingSystem(os).build()).build();
+
+      serverWithNatRuleToNodeMetadata = new ServerWithNatRuleToNodeMetadata(locations, conventionFactory,
+            serverToHardware, operatingSystemToOperatingSystem);
+   }
+
+   @Test
+   public void testApply() {
+
+      serverWithExternalIp = ServerWithExternalIp.create(server, externalIp);
+
+      org.jclouds.compute.domain.OperatingSystem operatingSystem = org.jclouds.compute.domain.OperatingSystem.builder()
+            .description("Windows 10 x64").name("Win10x64").is64Bit(true).family(OsFamily.WINDOWS).build();
+
+      expect(image.getId()).andReturn("imageId");
+      expect(image.getOperatingSystem()).andReturn(operatingSystem);
+      expect(nic.privateIpv4()).andReturn("192.168.1.1").anyTimes();
+      expect(nodeNamingConvention.groupInUniqueNameOrNull(serverName)).andReturn("[" + serverName + "]").anyTimes();
+      expect(serverToHardware.apply(server)).andReturn(hardware);
+      expect(operatingSystemToOperatingSystem.apply(os)).andReturn(operatingSystem);
+
+      EasyMock.replay(nodeNamingConvention, serverImageApi, image, nic, serverToHardware, operatingSystemToOperatingSystem);
+
+      assertNodeMetadata(serverWithNatRuleToNodeMetadata.apply(serverWithExternalIp), operatingSystem,
+            serverWithExternalIp.server().sourceImageId(), NodeMetadata.Status.RUNNING,
+            ImmutableSet.of(nic.privateIpv4()), ImmutableSet.of(externalIp));
+   }
+
+   @Test(dependsOnMethods = "testApply")
+   public void testApplyWithNullables() {
+
+      server = Server.builder().id("serverId").name(serverName).datacenterId(datacenterId)
+            .networkInfo(NetworkInfo.create(networkDomainId, nic, new ArrayList<NIC>())).cpu(cpu).deployed(true)
+            .state(State.DELETED).sourceImageId("imageId").started(false).createTime(new Date()).memoryGb(1024)
+            .guest(Guest.builder().osCustomization(false).operatingSystem(os).build()).build();
+
+      serverWithExternalIp = ServerWithExternalIp.create(server, null);
+
+      expect(nic.privateIpv4()).andReturn(null).anyTimes();
+      expect(nodeNamingConvention.groupInUniqueNameOrNull(serverName)).andReturn("[" + serverName + "]").anyTimes();
+      expect(serverToHardware.apply(server)).andReturn(hardware);
+
+      EasyMock.replay(nodeNamingConvention, serverImageApi, image, nic, serverToHardware, operatingSystemToOperatingSystem);
+
+      assertNodeMetadata(serverWithNatRuleToNodeMetadata.apply(serverWithExternalIp), null, "imageId",
+            NodeMetadata.Status.TERMINATED, ImmutableSet.<String>of(), ImmutableSet.<String>of());
+   }
+
+   private void assertNodeMetadata(NodeMetadata result, org.jclouds.compute.domain.OperatingSystem os, String imageId,
+         NodeMetadata.Status status, ImmutableSet<String> privateIpAddresses, ImmutableSet<String> publicIpAddresses) {
+      assertNotNull(result);
+      assertEquals(result.getId(), serverWithExternalIp.server().id());
+      assertEquals(result.getName(), serverWithExternalIp.server().name());
+      assertEquals(result.getHostname(), serverWithExternalIp.server().description());
+      assertEquals(result.getGroup(), "[" + serverName + "]");
+      assertEquals(result.getHardware(), hardware);
+      assertEquals(result.getOperatingSystem(), os);
+      assertEquals(result.getLocation(), location);
+      assertEquals(result.getImageId(), imageId);
+      assertEquals(result.getStatus(), status);
+      assertEquals(result.getPrivateAddresses(), privateIpAddresses);
+      assertEquals(result.getPublicAddresses(), publicIpAddresses);
+   }
+}