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 2014/10/27 18:25:49 UTC

[2/3] JCLOUDS-292: Added CloudSigma2 ComputeService

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/ServerInfoToNodeMetadata.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/ServerInfoToNodeMetadata.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/ServerInfoToNodeMetadata.java
new file mode 100644
index 0000000..2cfc1b0
--- /dev/null
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/ServerInfoToNodeMetadata.java
@@ -0,0 +1,135 @@
+/*
+ * 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.cloudsigma2.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import org.jclouds.cloudsigma2.CloudSigma2Api;
+import org.jclouds.cloudsigma2.domain.ServerDrive;
+import org.jclouds.cloudsigma2.domain.ServerInfo;
+import org.jclouds.cloudsigma2.domain.ServerStatus;
+import org.jclouds.cloudsigma2.domain.Tag;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.location.suppliers.all.JustProvider;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.Iterables.transform;
+
+@Singleton
+public class ServerInfoToNodeMetadata implements Function<ServerInfo, NodeMetadata> {
+
+   private final ServerDriveToVolume serverDriveToVolume;
+   private final NICToAddress nicToAddress;
+   private final Map<ServerStatus, NodeMetadata.Status> serverStatusToNodeStatus;
+   private final GroupNamingConvention groupNamingConventionWithPrefix;
+   private final GroupNamingConvention groupNamingConventionWithoutPrefix;
+   private final Map<String, Credentials> credentialStore;
+   private final JustProvider locations;
+   private final CloudSigma2Api api;
+
+   @Inject
+   public ServerInfoToNodeMetadata(ServerDriveToVolume serverDriveToVolume, NICToAddress nicToAddress,
+                                   Map<ServerStatus, NodeMetadata.Status> serverStatusToNodeStatus,
+                                   GroupNamingConvention.Factory groupNamingConvention,
+                                   Map<String, Credentials> credentialStore,
+                                   JustProvider locations, CloudSigma2Api api) {
+      this.serverDriveToVolume = checkNotNull(serverDriveToVolume, "serverDriveToVolume");
+      this.nicToAddress = checkNotNull(nicToAddress, "nicToAddress");
+      this.serverStatusToNodeStatus = checkNotNull(serverStatusToNodeStatus, "serverStatusToNodeStatus");
+      this.groupNamingConventionWithPrefix = checkNotNull(groupNamingConvention, "groupNamingConvention").create();
+      this.groupNamingConventionWithoutPrefix = groupNamingConvention.createWithoutPrefix();
+      this.credentialStore = checkNotNull(credentialStore, "credentialStore");
+      this.locations = checkNotNull(locations, "locations");
+      this.api = checkNotNull(api, "api");
+   }
+
+   @Override
+   public NodeMetadata apply(ServerInfo serverInfo) {
+      NodeMetadataBuilder builder = new NodeMetadataBuilder();
+
+      builder.ids(serverInfo.getUuid());
+      builder.name(serverInfo.getName());
+      builder.group(groupNamingConventionWithoutPrefix.extractGroup(serverInfo.getName()));
+      builder.location(getOnlyElement(locations.get()));
+
+      builder.hardware(new HardwareBuilder().ids(serverInfo.getUuid()).processor(new Processor(1, serverInfo.getCpu()))
+            .ram(serverInfo.getMemory().intValue())
+            .volumes(Iterables.transform(serverInfo.getDrives(), serverDriveToVolume)).build());
+
+      builder.tags(readTags(serverInfo));
+      builder.userMetadata(serverInfo.getMeta());
+      builder.imageId(extractImageId(serverInfo));
+      builder.status(serverStatusToNodeStatus.get(serverInfo.getStatus()));
+      builder.publicAddresses(filter(transform(serverInfo.getNics(), nicToAddress), notNull()));
+
+      // CloudSigma does not provide a way to get the credentials.
+      // Try to return them from the credential store
+      Credentials credentials = credentialStore.get("node#" + serverInfo.getUuid());
+      if (credentials instanceof LoginCredentials) {
+         builder.credentials(LoginCredentials.class.cast(credentials));
+      }
+
+      return builder.build();
+   }
+
+   private static String extractImageId(ServerInfo serverInfo) {
+      String imageId = serverInfo.getMeta().get("image_id");
+
+      if (imageId == null) {
+         ServerDrive serverBootDrive = null;
+         for (ServerDrive serverDrive : serverInfo.getDrives()) {
+            if (serverDrive.getBootOrder() != null
+                  && (serverBootDrive == null || serverDrive.getBootOrder() < serverBootDrive.getBootOrder())) {
+               serverBootDrive = serverDrive;
+            }
+         }
+         if (serverBootDrive != null) {
+            imageId = serverBootDrive.getDriveUuid();
+         }
+      }
+
+      return imageId;
+   }
+
+   private Iterable<String> readTags(ServerInfo serverInfo) {
+      return transform(serverInfo.getTags(), new Function<Tag, String>() {
+         @Override
+         public String apply(Tag input) {
+            Tag tag = api.getTagInfo(input.getUuid());
+            if (tag.getName() == null) {
+               return input.getUuid();
+            }
+            String tagWithoutPrefix = groupNamingConventionWithPrefix.groupInSharedNameOrNull(tag.getName());
+            return tagWithoutPrefix != null ? tagWithoutPrefix : tag.getName();
+         }
+      });
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
new file mode 100644
index 0000000..077917b
--- /dev/null
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/functions/TemplateOptionsToStatementWithoutPublicKey.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cloudsigma2.compute.functions;
+
+import com.google.common.collect.ImmutableList;
+import org.jclouds.compute.functions.TemplateOptionsToStatement;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.scriptbuilder.InitScript;
+import org.jclouds.scriptbuilder.domain.Statement;
+import org.jclouds.scriptbuilder.domain.StatementList;
+import org.jclouds.scriptbuilder.statements.ssh.InstallRSAPrivateKey;
+
+import javax.inject.Singleton;
+
+/**
+ * Convert the template options into a statement, but ignoring the public key.
+ * <p/>
+ * The {@link org.jclouds.cloudsigma2.compute.strategy.CloudSigma2ComputeServiceAdapter} already takes care of
+ * installing it using the server metadata.
+ */
+@Singleton
+public class TemplateOptionsToStatementWithoutPublicKey extends TemplateOptionsToStatement {
+
+   @Override
+   public Statement apply(TemplateOptions options) {
+      ImmutableList.Builder<Statement> builder = ImmutableList.builder();
+      if (options.getRunScript() != null) {
+         builder.add(options.getRunScript());
+      }
+      if (options.getPrivateKey() != null) {
+         builder.add(new InstallRSAPrivateKey(options.getPrivateKey()));
+      }
+
+      ImmutableList<Statement> bootstrap = builder.build();
+      if (bootstrap.isEmpty()) {
+         return null;
+      }
+
+      if (options.getTaskName() == null && !(options.getRunScript() instanceof InitScript)) {
+         options.nameTask("bootstrap");
+      }
+      return bootstrap.size() == 1 ? bootstrap.get(0) : new StatementList(bootstrap);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/options/CloudSigma2TemplateOptions.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/options/CloudSigma2TemplateOptions.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/options/CloudSigma2TemplateOptions.java
new file mode 100644
index 0000000..db43a7d
--- /dev/null
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/options/CloudSigma2TemplateOptions.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.cloudsigma2.compute.options;
+
+import com.google.common.base.Objects.ToStringHelper;
+import org.jclouds.cloudsigma2.domain.DeviceEmulationType;
+import org.jclouds.cloudsigma2.domain.Model;
+import org.jclouds.compute.options.TemplateOptions;
+
+public class CloudSigma2TemplateOptions extends TemplateOptions {
+
+   private DeviceEmulationType deviceEmulationType = DeviceEmulationType.VIRTIO;
+   private Model nicModel = Model.VIRTIO;
+   private String vncPassword;
+
+   /**
+    * Configures the device emulation type.
+    */
+   public CloudSigma2TemplateOptions deviceEmulationType(DeviceEmulationType deviceEmulationType) {
+      this.deviceEmulationType = deviceEmulationType;
+      return this;
+   }
+
+   /**
+    * Configures the type of NICs to create.
+    */
+   public CloudSigma2TemplateOptions nicModel(Model nicModel) {
+      this.nicModel = nicModel;
+      return this;
+   }
+
+   /**
+    * Configures the vnc password.
+    */
+   public CloudSigma2TemplateOptions vncPassword(String vncPassword) {
+      this.vncPassword = vncPassword;
+      return this;
+   }
+
+   public DeviceEmulationType getDeviceEmulationType() {
+      return deviceEmulationType;
+   }
+
+   public Model getNicModel() {
+      return nicModel;
+   }
+
+   public String getVncPassword() {
+      return vncPassword;
+   }
+
+   @Override
+   public TemplateOptions clone() {
+      CloudSigma2TemplateOptions options = new CloudSigma2TemplateOptions();
+      copyTo(options);
+      return options;
+   }
+
+   @Override
+   public void copyTo(TemplateOptions to) {
+      super.copyTo(to);
+      if (to instanceof CloudSigma2TemplateOptions) {
+         CloudSigma2TemplateOptions eTo = CloudSigma2TemplateOptions.class.cast(to);
+         eTo.deviceEmulationType(deviceEmulationType);
+         eTo.nicModel(nicModel);
+         eTo.vncPassword(vncPassword);
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      final int prime = 31;
+      int result = super.hashCode();
+      result = prime * result + ((deviceEmulationType == null) ? 0 : deviceEmulationType.hashCode());
+      result = prime * result + ((nicModel == null) ? 0 : nicModel.hashCode());
+      result = prime * result + ((vncPassword == null) ? 0 : vncPassword.hashCode());
+      return result;
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (!super.equals(obj))
+         return false;
+      if (getClass() != obj.getClass())
+         return false;
+      CloudSigma2TemplateOptions other = (CloudSigma2TemplateOptions) obj;
+      if (deviceEmulationType != other.deviceEmulationType)
+         return false;
+      if (nicModel != other.nicModel)
+         return false;
+      if (vncPassword == null) {
+         if (other.vncPassword != null)
+            return false;
+      } else if (!vncPassword.equals(other.vncPassword))
+         return false;
+      return true;
+   }
+
+   @Override
+   public ToStringHelper string() {
+      ToStringHelper toString = super.string().omitNullValues();
+      toString.add("deviceEmulationType", deviceEmulationType);
+      toString.add("nicModel", nicModel);
+      toString.add("vncPassword", vncPassword);
+      return toString;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see CloudSigma2TemplateOptions#deviceEmulationType(DeviceEmulationType)
+       */
+      public CloudSigma2TemplateOptions deviceEmulationType(DeviceEmulationType deviceEmulationType) {
+         CloudSigma2TemplateOptions options = new CloudSigma2TemplateOptions();
+         options.deviceEmulationType(deviceEmulationType);
+         return options;
+      }
+
+      /**
+       * @see CloudSigma2TemplateOptions#nicModel(Model)
+       */
+      public CloudSigma2TemplateOptions nicModel(Model nicModel) {
+         CloudSigma2TemplateOptions options = new CloudSigma2TemplateOptions();
+         options.nicModel(nicModel);
+         return options;
+      }
+
+      /**
+       * @see CloudSigma2TemplateOptions#vncPassword(String)
+       */
+      public CloudSigma2TemplateOptions vncPassword(String vncPassword) {
+         CloudSigma2TemplateOptions options = new CloudSigma2TemplateOptions();
+         options.vncPassword(vncPassword);
+         return options;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/strategy/CloudSigma2ComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/strategy/CloudSigma2ComputeServiceAdapter.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/strategy/CloudSigma2ComputeServiceAdapter.java
new file mode 100644
index 0000000..69decb2
--- /dev/null
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/compute/strategy/CloudSigma2ComputeServiceAdapter.java
@@ -0,0 +1,407 @@
+/*
+ * 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.cloudsigma2.compute.strategy;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+
+import org.jclouds.Constants;
+import org.jclouds.cloudsigma2.CloudSigma2Api;
+import org.jclouds.cloudsigma2.compute.options.CloudSigma2TemplateOptions;
+import org.jclouds.cloudsigma2.domain.DriveInfo;
+import org.jclouds.cloudsigma2.domain.DriveStatus;
+import org.jclouds.cloudsigma2.domain.FirewallAction;
+import org.jclouds.cloudsigma2.domain.FirewallDirection;
+import org.jclouds.cloudsigma2.domain.FirewallIpProtocol;
+import org.jclouds.cloudsigma2.domain.FirewallPolicy;
+import org.jclouds.cloudsigma2.domain.FirewallRule;
+import org.jclouds.cloudsigma2.domain.IPConfiguration;
+import org.jclouds.cloudsigma2.domain.IPConfigurationType;
+import org.jclouds.cloudsigma2.domain.LibraryDrive;
+import org.jclouds.cloudsigma2.domain.MediaType;
+import org.jclouds.cloudsigma2.domain.NIC;
+import org.jclouds.cloudsigma2.domain.ServerDrive;
+import org.jclouds.cloudsigma2.domain.ServerInfo;
+import org.jclouds.cloudsigma2.domain.ServerStatus;
+import org.jclouds.cloudsigma2.domain.Tag;
+import org.jclouds.cloudsigma2.domain.VLANInfo;
+import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.HardwareBuilder;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.Volume;
+import org.jclouds.compute.domain.internal.VolumeImpl;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.logging.Logger;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Lists.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+import static org.jclouds.cloudsigma2.config.CloudSigma2Properties.PROPERTY_DELETE_DRIVES;
+import static org.jclouds.cloudsigma2.config.CloudSigma2Properties.PROPERTY_VNC_PASSWORD;
+import static org.jclouds.cloudsigma2.config.CloudSigma2Properties.TIMEOUT_DRIVE_CLONED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+
+@Singleton
+public class CloudSigma2ComputeServiceAdapter implements
+      ComputeServiceAdapter<ServerInfo, Hardware, LibraryDrive, Location> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final CloudSigma2Api api;
+   private final ListeningExecutorService userExecutor;
+   private final String defaultVncPassword;
+   private final Predicate<DriveInfo> driveCloned;
+   private final Predicate<String> serverStopped;
+   private final boolean destroyDrives;
+   private final GroupNamingConvention groupNamingConvention;
+
+   @Inject
+   public CloudSigma2ComputeServiceAdapter(CloudSigma2Api api,
+                                           @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService
+                                                 userExecutor,
+                                           @Named(PROPERTY_VNC_PASSWORD) String defaultVncPassword,
+                                           @Named(TIMEOUT_DRIVE_CLONED) Predicate<DriveInfo> driveCloned,
+                                           @Named(TIMEOUT_NODE_SUSPENDED) Predicate<String> serverStopped,
+                                           @Named(PROPERTY_DELETE_DRIVES) boolean destroyDrives,
+                                           GroupNamingConvention.Factory groupNamingConvention) {
+      this.api = checkNotNull(api, "api");
+      this.userExecutor = checkNotNull(userExecutor, "userExecutor");
+      this.defaultVncPassword = checkNotNull(defaultVncPassword, "defaultVncPassword");
+      this.driveCloned = checkNotNull(driveCloned, "driveCloned");
+      this.serverStopped = checkNotNull(serverStopped, "serverStopped");
+      this.destroyDrives = destroyDrives;
+      this.groupNamingConvention = checkNotNull(groupNamingConvention, "groupNamingConvention").create();
+   }
+
+   @Override
+   public NodeAndInitialCredentials<ServerInfo> createNodeWithGroupEncodedIntoName(String group, String name,
+                                                                                   Template template) {
+      CloudSigma2TemplateOptions options = template.getOptions().as(CloudSigma2TemplateOptions.class);
+      Image image = template.getImage();
+      Hardware hardware = template.getHardware();
+
+      DriveInfo drive = api.getLibraryDrive(image.getProviderId());
+
+      if (!drive.getMedia().equals(MediaType.CDROM)) {
+         logger.debug(">> cloning library drive %s...", image.getProviderId());
+
+         drive = api.cloneLibraryDrive(image.getProviderId(), null);
+         driveCloned.apply(drive);
+
+         // Refresh the drive object and verify the clone operation didn't time out
+         drive = api.getDriveInfo(drive.getUuid());
+         DriveStatus status = drive.getStatus();
+
+         if (DriveStatus.UNMOUNTED != status) {
+            if (destroyDrives) {
+               // Rollback the cloned drive, if needed
+               logger.error(">> clone operation failed. Rolling back drive (%s)...", drive);
+               destroyDrives(ImmutableList.of(drive.getUuid()));
+            }
+            throw new IllegalStateException("Resource is in invalid status: " + status);
+         }
+
+         logger.debug(">> drive cloned (%s)...", drive);
+      }
+
+      ImmutableList.Builder<FirewallRule> firewallRulesBuilder = ImmutableList.builder();
+      for (int port : options.getInboundPorts()) {
+         firewallRulesBuilder.add(new FirewallRule.Builder().action(FirewallAction.ACCEPT)
+               .ipProtocol(FirewallIpProtocol.TCP).direction(FirewallDirection.IN).destinationPort("" + port).build());
+      }
+
+      List<NIC> nics = null;
+      try {
+         logger.debug(">> creating firewall policies...");
+         FirewallPolicy firewallPolicy = api.createFirewallPolicy(new FirewallPolicy.Builder().rules(
+               firewallRulesBuilder.build()).build());
+         nics = configureNICs(options, firewallPolicy);
+      } catch (Exception ex) {
+         if (destroyDrives) {
+            logger.debug(">> rolling back the cloned drive...", drive.getUuid());
+            destroyDrives(ImmutableList.of(drive.getUuid()));
+         }
+         throw propagate(ex);
+      }
+
+      List<Tag> tagIds = configureTags(options);
+
+      // Cloud init images expect the public key in the server metadata
+      Map<String, String> metadata = Maps.newLinkedHashMap();
+      metadata.put("image_id", image.getProviderId());
+      if (!Strings.isNullOrEmpty(options.getPublicKey())) {
+         metadata.put("ssh_public_key", options.getPublicKey());
+      }
+      metadata.putAll(options.getUserMetadata());
+
+      ServerInfo serverInfo = null;
+      try {
+         logger.debug(">> creating server...");
+
+         serverInfo = api.createServer(new ServerInfo.Builder()
+               .name(name)
+               .cpu((int) hardware.getProcessors().get(0).getSpeed())
+               .memory(BigInteger.valueOf(hardware.getRam()).multiply(BigInteger.valueOf(1024 * 1024)))
+               .drives(ImmutableList.of(drive.toServerDrive(1, "0:1", options.getDeviceEmulationType())))
+               .nics(nics)
+               .meta(metadata)
+               .tags(tagIds)
+               .vncPassword(Optional.fromNullable(options.getVncPassword()).or(defaultVncPassword)).build());
+
+         api.startServer(serverInfo.getUuid());
+
+         return new NodeAndInitialCredentials<ServerInfo>(serverInfo, serverInfo.getUuid(), LoginCredentials.builder()
+               .build());
+      } catch (Exception ex) {
+         try {
+            if (serverInfo != null) {
+               logger.debug(">> rolling back the server...");
+               api.deleteServer(serverInfo.getUuid());
+            }
+         } finally {
+            try {
+               if (destroyDrives) {
+                  logger.debug(">> rolling back the cloned drive...");
+                  destroyDrives(ImmutableList.of(drive.getUuid()));
+               }
+            } finally {
+               deleteTags(tagIds);
+            }
+         }
+         throw propagate(ex);
+      }
+   }
+
+   @Override
+   public Iterable<Hardware> listHardwareProfiles() {
+      // Return a hardcoded list of hardware profiles until
+      // https://issues.apache.org/jira/browse/JCLOUDS-482 is fixed
+      Builder<Hardware> hardware = ImmutableSet.builder();
+      Builder<Integer> ramSetBuilder = ImmutableSet.builder();
+      Builder<Double> cpuSetBuilder = ImmutableSet.builder();
+      for (int i = 1; i < 65; i++) {
+         ramSetBuilder.add(i * 1024);
+      }
+      for (int i = 1; i < 41; i++) {
+         cpuSetBuilder.add((double) i * 1000);
+      }
+      for (int ram : ramSetBuilder.build()) {
+         for (double cpu : cpuSetBuilder.build()) {
+            hardware.add(new HardwareBuilder().ids(String.format("cpu=%f,ram=%d", cpu, ram))
+                  .processor(new Processor(1, cpu)).ram(ram)
+                  .volumes(ImmutableList.<Volume>of(new VolumeImpl(null, true, false))).build());
+         }
+      }
+      return hardware.build();
+   }
+
+   @Override
+   public Iterable<LibraryDrive> listImages() {
+      return api.listLibraryDrives().concat();
+   }
+
+   @Override
+   public LibraryDrive getImage(String uuid) {
+      return api.getLibraryDrive(uuid);
+   }
+
+   @Override
+   public Iterable<Location> listLocations() {
+      // Nothing to return here. Each provider will configure the locations
+      return ImmutableSet.<Location>of();
+   }
+
+   @Override
+   public ServerInfo getNode(String uuid) {
+      return api.getServerInfo(uuid);
+   }
+
+   @Override
+   public void destroyNode(String uuid) {
+      ServerInfo server = api.getServerInfo(uuid);
+
+      if (ServerStatus.RUNNING == server.getStatus()) {
+         api.stopServer(uuid);
+         waitUntilServerIsStopped(uuid);
+      }
+
+      deleteTags(server.getTags());
+
+      List<String> driveIds = transform(server.getDrives(), new Function<ServerDrive, String>() {
+         @Override
+         public String apply(ServerDrive input) {
+            return input.getDriveUuid();
+         }
+      });
+
+      logger.debug(">> deleting server...");
+      api.deleteServer(uuid);
+
+      if (destroyDrives) {
+         logger.debug(">> deleting server drives...");
+         destroyDrives(driveIds);
+      }
+   }
+
+   @Override
+   public void rebootNode(String uuid) {
+      api.stopServer(uuid);
+      waitUntilServerIsStopped(uuid);
+      api.startServer(uuid);
+   }
+
+   @Override
+   public void resumeNode(String uuid) {
+      api.startServer(uuid);
+   }
+
+   @Override
+   public void suspendNode(String uuid) {
+      api.stopServer(uuid);
+   }
+
+   @Override
+   public Iterable<ServerInfo> listNodes() {
+      return api.listServersInfo().concat();
+   }
+
+   @Override
+   public Iterable<ServerInfo> listNodesByIds(final Iterable<String> uuids) {
+      // Only fetch the requested nodes. Do it in parallel.
+      ListenableFuture<List<ServerInfo>> futures = allAsList(transform(uuids,
+            new Function<String, ListenableFuture<ServerInfo>>() {
+               @Override
+               public ListenableFuture<ServerInfo> apply(final String input) {
+                  return userExecutor.submit(new Callable<ServerInfo>() {
+                     @Override
+                     public ServerInfo call() throws Exception {
+                        return api.getServerInfo(input);
+                     }
+                  });
+               }
+            }));
+
+      return getUnchecked(futures);
+   }
+
+   private void waitUntilServerIsStopped(String uuid) {
+      serverStopped.apply(uuid);
+      ServerInfo server = api.getServerInfo(uuid);
+      checkState(server.getStatus() == ServerStatus.STOPPED, "Resource is in invalid status: %s", server.getStatus());
+   }
+
+   private List<NIC> configureNICs(CloudSigma2TemplateOptions options, FirewallPolicy firewallPolicy) {
+      ImmutableList.Builder<NIC> nics = ImmutableList.builder();
+      for (String network : options.getNetworks()) {
+         VLANInfo vlan = api.getVLANInfo(network);
+         checkArgument(vlan != null, "network %s not found", network);
+         nics.add(new NIC.Builder().vlan(vlan).firewallPolicy(firewallPolicy).model(options.getNicModel()).build());
+      }
+
+      // If no network has been specified, assign an IP from the DHCP
+      if (options.getNetworks().isEmpty()) {
+         logger.debug(">> no networks configured. Will assign an IP from the DHCP...");
+         NIC nic = new NIC.Builder().firewallPolicy(firewallPolicy).model(options.getNicModel())
+               .ipV4Configuration(new IPConfiguration.Builder().configurationType(IPConfigurationType.DHCP).build())
+               .build();
+         nics.add(nic);
+      }
+
+      return nics.build();
+   }
+
+   private List<Tag> configureTags(CloudSigma2TemplateOptions options) {
+      ImmutableList.Builder<Tag> builder = ImmutableList.builder();
+      for (String tagName : options.getTags()) {
+         String nameWithPrefix = groupNamingConvention.sharedNameForGroup(tagName);
+         builder.add(new Tag.Builder().name(nameWithPrefix).build());
+      }
+
+      List<Tag> tags = builder.build();
+      builder = ImmutableList.builder();
+
+      if (!tags.isEmpty()) {
+         logger.debug(">> creating tags...");
+         builder.addAll(api.createTags(tags));
+      }
+
+      return builder.build();
+   }
+
+   private void deleteTags(List<Tag> tags) {
+      logger.debug(">> deleting server tags...");
+      Iterable<Tag> customTags = filter(tags, new Predicate<Tag>() {
+         @Override
+         public boolean apply(Tag input) {
+            // Only delete the tags jclouds has set
+            Tag tag = api.getTagInfo(input.getUuid());
+            return groupNamingConvention.groupInSharedNameOrNull(tag.getName()) != null;
+         }
+      });
+
+      for (Tag tag : customTags) {
+         try {
+            // Try to delete the tags but don't fail if the can't be deleted
+            api.deleteTag(tag.getUuid());
+         } catch (Exception ex) {
+            logger.warn(ex, ">> could not delete tag: %s", tag);
+         }
+      }
+   }
+
+   private void destroyDrives(List<String> driveIds) {
+      try {
+         // Try to delete the drives but don't fail if the can't be deleted, as the server has been already removed.
+         api.deleteDrives(driveIds);
+      } catch (Exception ex) {
+         logger.warn(ex, ">> could not delete drives: [%s]", Joiner.on(',').join(driveIds));
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/config/CloudSigma2Properties.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/config/CloudSigma2Properties.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/config/CloudSigma2Properties.java
index 66a35a7..b83ae0d 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/config/CloudSigma2Properties.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/config/CloudSigma2Properties.java
@@ -19,8 +19,19 @@ package org.jclouds.cloudsigma2.config;
 public class CloudSigma2Properties {
 
    /**
-    * default VNC password used on new machines
+    * Default VNC password used on new machines
     */
    public static final String PROPERTY_VNC_PASSWORD = "jclouds.cloudsigma.vnc-password";
-
+   
+   /**
+    * Time in milliseconds to wait for a drive to be cloned
+    * Default: 60000 
+    */
+   public static final String TIMEOUT_DRIVE_CLONED = "jclouds.cloudsigma.timeout.drive-cloned";
+   
+   /**
+    * Controls if the drives of a server should be destroyed when deleting the server
+    * Default: true 
+    */
+   public static final String PROPERTY_DELETE_DRIVES = "jclouds.cloudsigma.delete-drives";
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/Drive.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/Drive.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/Drive.java
index ec2ff76..14570d4 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/Drive.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/Drive.java
@@ -166,7 +166,7 @@ public class Drive extends Item {
     * @param deviceChannel       device channel in format {controller:unit} ex. 0:1, 0:2, etc.
     * @param deviceEmulationType device emulation type
     */
-   public ServerDrive toServerDrive(int bootOrder, String deviceChannel, DeviceEmulationType deviceEmulationType) {
+   public ServerDrive toServerDrive(Integer bootOrder, String deviceChannel, DeviceEmulationType deviceEmulationType) {
       return new ServerDrive(bootOrder, deviceChannel, deviceEmulationType, this.uuid);
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/DrivesListRequestFieldsGroup.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/DrivesListRequestFieldsGroup.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/DrivesListRequestFieldsGroup.java
index 9100050..56aad17 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/DrivesListRequestFieldsGroup.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/DrivesListRequestFieldsGroup.java
@@ -16,7 +16,7 @@
  */
 package org.jclouds.cloudsigma2.domain;
 
-import com.google.common.base.Joiner;
+import java.util.Iterator;
 
 public class DrivesListRequestFieldsGroup {
    private final Iterable<String> fields;
@@ -31,6 +31,18 @@ public class DrivesListRequestFieldsGroup {
 
    @Override
    public String toString() {
-      return Joiner.on(',').join(fields);
+      String returnString = "";
+
+      Iterator<?> iterator = fields.iterator();
+
+      while (iterator.hasNext()) {
+         returnString += iterator.next();
+
+         if (iterator.hasNext()) {
+            returnString += ",";
+         }
+      }
+
+      return returnString;
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/FirewallPolicy.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/FirewallPolicy.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/FirewallPolicy.java
index 50cf8ea..05ba638 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/FirewallPolicy.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/FirewallPolicy.java
@@ -30,6 +30,7 @@ public class FirewallPolicy extends Item {
       private Map<String, String> meta;
       private Owner owner;
       private List<FirewallRule> rules;
+      private List<Tag> tags;
       private List<Server> servers;
 
       /**
@@ -69,6 +70,15 @@ public class FirewallPolicy extends Item {
       }
 
       /**
+       * @param tags Many related resources. Can be either a list of URIs or list of individually nested resource data.
+       * @return
+       */
+      public Builder tags(List<Tag> tags) {
+         this.tags = ImmutableList.copyOf(tags);
+         return this;
+      }
+
+      /**
        * @param resourceUri Resource URI
        * @return
        */
@@ -98,25 +108,37 @@ public class FirewallPolicy extends Item {
          return this;
       }
 
+      public static Builder fromFirewallPolicy(FirewallPolicy firewallPolicy) {
+         return new Builder()
+               .resourceUri(firewallPolicy.getResourceUri())
+               .uuid(firewallPolicy.getUuid())
+               .rules(firewallPolicy.getRules())
+               .name(firewallPolicy.getName())
+               .meta(firewallPolicy.getMeta())
+               .tags(firewallPolicy.getTags());
+      }
+
       public FirewallPolicy build() {
-         return new FirewallPolicy(meta, name, owner, resourceUri, rules, servers, uuid);
+         return new FirewallPolicy(meta, name, owner, resourceUri, rules, servers, tags, uuid);
       }
    }
 
    private final Map<String, String> meta;
    private final Owner owner;
    private final List<FirewallRule> rules;
+   private final List<Tag> tags;
    private final List<Server> servers;
 
    @ConstructorProperties({
-         "meta", "name", "owner", "resource_uri", "rules", "servers", "uuid"
+         "meta", "name", "owner", "resource_uri", "rules", "servers", "tags", "uuid"
    })
    public FirewallPolicy(Map<String, String> meta, String name, Owner owner, URI resourceUri, List<FirewallRule> rules,
-                         List<Server> servers, String uuid) {
+                         List<Server> servers, List<Tag> tags, String uuid) {
       super(uuid, name, resourceUri);
       this.meta = meta;
       this.owner = owner;
       this.rules = rules == null ? new ArrayList<FirewallRule>() : rules;
+      this.tags = tags == null ? new ArrayList<Tag>() : tags;
       this.servers = servers == null ? new ArrayList<Server>() : servers;
    }
 
@@ -163,6 +185,13 @@ public class FirewallPolicy extends Item {
    }
 
    /**
+    * @return Related resources URI list.
+    */
+   public List<Tag> getTags() {
+      return tags;
+   }
+
+   /**
     * @return UUID of the policy
     */
    public String getUuid() {
@@ -172,7 +201,7 @@ public class FirewallPolicy extends Item {
    @Override
    public boolean equals(Object o) {
       if (this == o) return true;
-      if (!(o instanceof FirewallPolicy)) return false;
+      if (o == null || getClass() != o.getClass()) return false;
       if (!super.equals(o)) return false;
 
       FirewallPolicy that = (FirewallPolicy) o;
@@ -181,6 +210,7 @@ public class FirewallPolicy extends Item {
       if (owner != null ? !owner.equals(that.owner) : that.owner != null) return false;
       if (rules != null ? !rules.equals(that.rules) : that.rules != null) return false;
       if (servers != null ? !servers.equals(that.servers) : that.servers != null) return false;
+      if (tags != null ? !tags.equals(that.tags) : that.tags != null) return false;
 
       return true;
    }
@@ -191,6 +221,7 @@ public class FirewallPolicy extends Item {
       result = 31 * result + (meta != null ? meta.hashCode() : 0);
       result = 31 * result + (owner != null ? owner.hashCode() : 0);
       result = 31 * result + (rules != null ? rules.hashCode() : 0);
+      result = 31 * result + (tags != null ? tags.hashCode() : 0);
       result = 31 * result + (servers != null ? servers.hashCode() : 0);
       return result;
    }
@@ -199,12 +230,10 @@ public class FirewallPolicy extends Item {
    public String toString() {
       return "[" +
             "meta=" + meta +
-            ", name='" + name + '\'' +
             ", owner=" + owner +
-            ", resourceUri='" + resourceUri + '\'' +
             ", rules=" + rules +
+            ", tags=" + tags +
             ", servers=" + servers +
-            ", uuid='" + uuid + '\'' +
             "]";
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/LibraryDrive.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/LibraryDrive.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/LibraryDrive.java
index d479867..d7a0ca5 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/LibraryDrive.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/LibraryDrive.java
@@ -38,6 +38,7 @@ public class LibraryDrive extends DriveInfo {
       private String os;
       private boolean isPaid;
       private String url;
+      private String version;
 
       public Builder arch(String arch) {
          this.arch = arch;
@@ -84,6 +85,11 @@ public class LibraryDrive extends DriveInfo {
          return this;
       }
 
+      public Builder version(String version) {
+         this.version = version;
+         return this;
+      }
+
       /**
        * {@inheritDoc}
        */
@@ -226,7 +232,7 @@ public class LibraryDrive extends DriveInfo {
       public LibraryDrive build() {
          return new LibraryDrive(uuid, name, resourceUri, size, owner, status, allowMultimount, affinities, jobs,
                licenses, media, meta, mountedOn, tags, arch, category, description, isFavorite, imageType, installNotes,
-               os, isPaid, url);
+               os, isPaid, url, version);
       }
    }
 
@@ -243,18 +249,20 @@ public class LibraryDrive extends DriveInfo {
    @Named("paid")
    private final boolean isPaid;
    private final String url;
+   private final String version;
 
    @ConstructorProperties({
          "uuid", "name", "resource_uri", "size", "owner", "status",
          "allow_multimount", "affinities", "jobs", "licenses",
          "media", "meta", "mounted_on", "tags", "arch", "category",
-         "description", "favourite", "image_type", "install_notes", "os", "paid", "url"
+         "description", "favourite", "image_type", "install_notes",
+         "os", "paid", "url", "version"
    })
    public LibraryDrive(String uuid, String name, URI resourceUri, BigInteger size, Owner owner, DriveStatus status,
                        boolean allowMultimount, List<String> affinities, List<Job> jobs, List<DriveLicense> licenses,
                        MediaType media, Map<String, String> meta, List<Server> mountedOn, List<String> tags,
                        String arch, List<String> category, String description, boolean favorite, String imageType,
-                       String installNotes, String os, boolean paid, String url) {
+                       String installNotes, String os, boolean paid, String url, String version) {
       super(uuid, name, resourceUri, size, owner, status, allowMultimount, affinities, jobs, licenses, media, meta,
             mountedOn, tags);
       this.arch = arch;
@@ -266,6 +274,7 @@ public class LibraryDrive extends DriveInfo {
       this.os = os;
       this.isPaid = paid;
       this.url = url;
+      this.version = version;
    }
 
    /**
@@ -331,10 +340,17 @@ public class LibraryDrive extends DriveInfo {
       return url;
    }
 
+   /**
+    * @return Operating system version.
+    */
+   public String getVersion() {
+      return version;
+   }
+
    @Override
    public boolean equals(Object o) {
       if (this == o) return true;
-      if (!(o instanceof LibraryDrive)) return false;
+      if (o == null || getClass() != o.getClass()) return false;
       if (!super.equals(o)) return false;
 
       LibraryDrive that = (LibraryDrive) o;
@@ -348,6 +364,7 @@ public class LibraryDrive extends DriveInfo {
       if (installNotes != null ? !installNotes.equals(that.installNotes) : that.installNotes != null) return false;
       if (os != null ? !os.equals(that.os) : that.os != null) return false;
       if (url != null ? !url.equals(that.url) : that.url != null) return false;
+      if (version != null ? !version.equals(that.version) : that.version != null) return false;
 
       return true;
    }
@@ -364,15 +381,14 @@ public class LibraryDrive extends DriveInfo {
       result = 31 * result + (os != null ? os.hashCode() : 0);
       result = 31 * result + (isPaid ? 1 : 0);
       result = 31 * result + (url != null ? url.hashCode() : 0);
+      result = 31 * result + (version != null ? version.hashCode() : 0);
       return result;
    }
 
    @Override
    public String toString() {
-      return "[uuid=" + uuid + ", name=" + name + ", size=" + size + ", owner=" + owner + ", status=" + status
-            + ", affinities=" + affinities + ", jobs=" + jobs + ", licenses=" + licenses + ", media=" + media
-            + ", meta=" + meta + ", mountedOn=" + mountedOn + ", tags=" + tags +
-            ", arch='" + arch + '\'' +
+      return "LibraryDrive{" +
+            "arch='" + arch + '\'' +
             ", category=" + category +
             ", description='" + description + '\'' +
             ", isFavorite=" + isFavorite +
@@ -381,6 +397,7 @@ public class LibraryDrive extends DriveInfo {
             ", os='" + os + '\'' +
             ", isPaid=" + isPaid +
             ", url='" + url + '\'' +
-            "]";
+            ", version='" + version + '\'' +
+            '}';
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerAvailabilityGroup.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerAvailabilityGroup.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerAvailabilityGroup.java
index 4104f15..855e782 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerAvailabilityGroup.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerAvailabilityGroup.java
@@ -16,10 +16,9 @@
  */
 package org.jclouds.cloudsigma2.domain;
 
+import java.util.Iterator;
 import java.util.List;
 
-import com.google.common.base.Joiner;
-
 public class ServerAvailabilityGroup {
 
    private final List<String> uuids;
@@ -51,6 +50,18 @@ public class ServerAvailabilityGroup {
 
    @Override
    public String toString() {
-      return Joiner.on(',').join(uuids);
+      String returnString = "";
+
+      Iterator<?> iterator = uuids.iterator();
+
+      while (iterator.hasNext()) {
+         returnString += iterator.next();
+
+         if (iterator.hasNext()) {
+            returnString += ",";
+         }
+      }
+
+      return returnString;
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerDrive.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerDrive.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerDrive.java
index 7f69e35..cff05c1 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerDrive.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerDrive.java
@@ -22,12 +22,12 @@ import java.beans.ConstructorProperties;
 public class ServerDrive {
 
    public static class Builder {
-      private int bootOrder;
+      private Integer bootOrder;
       private String deviceChannel;
       private DeviceEmulationType deviceEmulationType;
       private Drive drive;
 
-      public Builder bootOrder(int bootOrder) {
+      public Builder bootOrder(Integer bootOrder) {
          this.bootOrder = bootOrder;
          return this;
       }
@@ -53,7 +53,7 @@ public class ServerDrive {
    }
 
    @Named("boot_order")
-   private final int bootOrder;
+   private final Integer bootOrder;
    @Named("dev_channel")
    private final String deviceChannel;
    @Named("device")
@@ -71,7 +71,7 @@ public class ServerDrive {
    @ConstructorProperties({
          "boot_order", "dev_channel", "device", "drive"
    })
-   public ServerDrive(int bootOrder, String deviceChannel, DeviceEmulationType deviceEmulationType, Drive drive) {
+   public ServerDrive(Integer bootOrder, String deviceChannel, DeviceEmulationType deviceEmulationType, Drive drive) {
       this.bootOrder = bootOrder;
       this.deviceChannel = deviceChannel;
       this.deviceEmulationType = deviceEmulationType;
@@ -96,7 +96,7 @@ public class ServerDrive {
    /**
     * @return drive boot order
     */
-   public int getBootOrder() {
+   public Integer getBootOrder() {
       return bootOrder;
    }
 
@@ -131,31 +131,37 @@ public class ServerDrive {
    @Override
    public boolean equals(Object o) {
       if (this == o) return true;
-      if (!(o instanceof ServerDrive)) return false;
+      if (o == null || getClass() != o.getClass()) return false;
 
       ServerDrive that = (ServerDrive) o;
 
-      if (bootOrder != that.bootOrder) return false;
-      if (deviceChannel != null ? !deviceChannel.equals(that.deviceChannel) : that.deviceChannel != null)
-         return false;
+      if (bootOrder != null ? !bootOrder.equals(that.bootOrder) : that.bootOrder != null) return false;
+      if (deviceChannel != null ? !deviceChannel.equals(that.deviceChannel) : that.deviceChannel != null) return false;
       if (deviceEmulationType != that.deviceEmulationType) return false;
       if (drive != null ? !drive.equals(that.drive) : that.drive != null) return false;
+      if (driveUuid != null ? !driveUuid.equals(that.driveUuid) : that.driveUuid != null) return false;
 
       return true;
    }
 
    @Override
    public int hashCode() {
-      int result = bootOrder;
+      int result = bootOrder != null ? bootOrder.hashCode() : 0;
       result = 31 * result + (deviceChannel != null ? deviceChannel.hashCode() : 0);
       result = 31 * result + (deviceEmulationType != null ? deviceEmulationType.hashCode() : 0);
       result = 31 * result + (drive != null ? drive.hashCode() : 0);
+      result = 31 * result + (driveUuid != null ? driveUuid.hashCode() : 0);
       return result;
    }
 
    @Override
    public String toString() {
-      return "[bootOrder=" + bootOrder + ", deviceChannel=" + deviceChannel
-            + ", deviceEmulationType=" + deviceEmulationType + ", drive=" + drive + "]";
+      return "ServerDrive{" +
+            "bootOrder=" + bootOrder +
+            ", deviceChannel='" + deviceChannel + '\'' +
+            ", deviceEmulationType=" + deviceEmulationType +
+            ", drive=" + drive +
+            ", driveUuid='" + driveUuid + '\'' +
+            '}';
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerInfo.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerInfo.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerInfo.java
index 062eae5..f3e4a00 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerInfo.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/domain/ServerInfo.java
@@ -39,7 +39,7 @@ public class ServerInfo extends Server {
       private Map<String, String> meta;
       private List<NIC> nics;
       private List<String> requirements;
-      private List<String> tags;
+      private List<Tag> tags;
       private String vncPassword;
       private int smp;
 
@@ -137,7 +137,7 @@ public class ServerInfo extends Server {
        * @param tags list of tags this server is associated with
        * @return ServerInfo Builder
        */
-      public Builder tags(List<String> tags) {
+      public Builder tags(List<Tag> tags) {
          this.tags = ImmutableList.copyOf(tags);
          return this;
       }
@@ -278,7 +278,7 @@ public class ServerInfo extends Server {
    private final Map<String, String> meta;
    private final List<NIC> nics;
    private final List<String> requirements;
-   private final List<String> tags;
+   private final List<Tag> tags;
    @Named("vnc_password")
    private final String vncPassword;
    private final int smp;
@@ -292,7 +292,7 @@ public class ServerInfo extends Server {
    public ServerInfo(String uuid, String name, URI resourceUri, Owner owner, ServerStatus status, ServerRuntime runtime,
                      int cpu, boolean cpusInsteadOfCores, List<ServerDrive> drives, boolean enableNuma,
                      boolean hvRelaxed, boolean hvTsc, BigInteger memory, Map<String, String> meta, List<NIC> nics,
-                     List<String> requirements, List<String> tags, String vncPassword, int smp) {
+                     List<String> requirements, List<Tag> tags, String vncPassword, int smp) {
       super(uuid, name, resourceUri, owner, status, runtime);
       this.cpu = cpu;
       this.cpusInsteadOfCores = cpusInsteadOfCores;
@@ -382,7 +382,7 @@ public class ServerInfo extends Server {
    /**
     * @return list of tags this server is associated with
     */
-   public List<String> getTags() {
+   public List<Tag> getTags() {
       return tags;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/functions/internal/ParseTags.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/functions/internal/ParseTags.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/functions/internal/ParseTags.java
index c7427b1..ff6c1a8 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/functions/internal/ParseTags.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/functions/internal/ParseTags.java
@@ -67,25 +67,4 @@ public class ParseTags extends ParseJson<ParseTags.Tags> {
          };
       }
    }
-
-   public static class ToPagedIterableInfo extends ArgsToPagedIterable<Tag, ToPagedIterable> {
-
-      private CloudSigma2Api api;
-
-      @Inject
-      public ToPagedIterableInfo(CloudSigma2Api api) {
-         this.api = api;
-      }
-
-      @Override
-      protected Function<Object, IterableWithMarker<Tag>> markerToNextForArgs(List<Object> args) {
-         return new Function<Object, IterableWithMarker<Tag>>() {
-            @Override
-            public IterableWithMarker<Tag> apply(Object input) {
-               PaginationOptions paginationOptions = PaginationOptions.class.cast(input);
-               return api.listTagsInfo(paginationOptions);
-            }
-         };
-      }
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/handlers/CloudSigmaErrorHandler.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/handlers/CloudSigmaErrorHandler.java b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/handlers/CloudSigmaErrorHandler.java
index a2912e7..f41e865 100644
--- a/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/handlers/CloudSigmaErrorHandler.java
+++ b/cloudsigma2/src/main/java/org/jclouds/cloudsigma2/handlers/CloudSigmaErrorHandler.java
@@ -16,8 +16,11 @@
  */
 package org.jclouds.cloudsigma2.handlers;
 
-import com.google.common.base.Throwables;
-import com.google.common.io.Closeables;
+import java.io.IOException;
+
+import javax.annotation.Resource;
+import javax.inject.Singleton;
+
 import org.jclouds.http.HttpCommand;
 import org.jclouds.http.HttpErrorHandler;
 import org.jclouds.http.HttpResponse;
@@ -27,9 +30,8 @@ import org.jclouds.rest.AuthorizationException;
 import org.jclouds.rest.ResourceNotFoundException;
 import org.jclouds.util.Strings2;
 
-import javax.annotation.Resource;
-import javax.inject.Singleton;
-import java.io.IOException;
+import com.google.common.base.Throwables;
+import com.google.common.io.Closeables;
 
 /**
  * This will parse and set an appropriate exception on the command object.

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiExpectTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiExpectTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiExpectTest.java
index e5ff7ad..1395eae 100644
--- a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiExpectTest.java
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiExpectTest.java
@@ -16,8 +16,16 @@
  */
 package org.jclouds.cloudsigma2;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
 import org.jclouds.cloudsigma2.domain.AccountBalance;
 import org.jclouds.cloudsigma2.domain.CalcSubscription;
 import org.jclouds.cloudsigma2.domain.CreateSubscriptionRequest;
@@ -52,14 +60,8 @@ import org.jclouds.http.HttpResponse;
 import org.jclouds.rest.internal.BaseRestApiExpectTest;
 import org.testng.annotations.Test;
 
-import javax.ws.rs.core.MediaType;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
 
 @Test(groups = "unit")
 public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2Api> {
@@ -323,7 +325,7 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
 
    @Test
    public void testCloneDrive() throws Exception {
-      String uuid = "e96f3c63-6f50-47eb-9401-a56c5ccf6b32";
+      String uuid = "92ca1450-417e-4cc1-983b-1015777e2591";
       CloudSigma2Api api = requestSendsResponse(
             postBuilder()
                   .payload(payloadFromResourceWithContentType("/drives-create-request.json",
@@ -331,7 +333,7 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
                   .endpoint(endpoint + "drives/" + uuid + "/action/?do=clone")
                   .build(),
             responseBuilder()
-                  .payload(payloadFromResourceWithContentType("/drives-detail.json", MediaType.APPLICATION_JSON))
+                  .payload(payloadFromResourceWithContentType("/drive-cloned.json", MediaType.APPLICATION_JSON))
                   .build());
 
       DriveInfo result = api.cloneDrive(uuid, new DriveInfo.Builder()
@@ -404,7 +406,7 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
 
    @Test
    public void testCloneLibraryDrive() throws Exception {
-      String uuid = "e96f3c63-6f50-47eb-9401-a56c5ccf6b32";
+      String uuid = "8c45d8d9-4efd-44ec-9833-8d52004b4298";
       CloudSigma2Api api = requestSendsResponse(
             postBuilder()
                   .payload(payloadFromResourceWithContentType("/libdrives-create-request.json",
@@ -412,7 +414,7 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
                   .endpoint(endpoint + "libdrives/" + uuid + "/action/?do=clone")
                   .build(),
             responseBuilder()
-                  .payload(payloadFromResourceWithContentType("/libdrives-single.json", MediaType.APPLICATION_JSON))
+                  .payload(payloadFromResourceWithContentType("/libdrives-cloned.json", MediaType.APPLICATION_JSON))
                   .build());
 
       DriveInfo result = api.cloneLibraryDrive(uuid, new LibraryDrive.Builder()
@@ -817,6 +819,27 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
    }
 
    @Test
+   public void testGetFirewallPolicy() throws Exception {
+      String uuid = "9001b532-857a-405a-8e50-54e342871e77";
+      CloudSigma2Api api = requestsSendResponses(
+            getBuilder()
+                  .endpoint(endpoint + "fwpolicies/" + uuid + "/")
+                  .build(),
+            responseBuilder()
+                  .payload(payloadFromResourceWithContentType("/fwpolicies-get-single.json",
+                        MediaType.APPLICATION_JSON))
+                  .build(),
+            getBuilder()
+                  .endpoint(endpoint + "fwpolicies/failure")
+                  .build(),
+            HttpResponse.builder()
+                  .statusCode(404)
+                  .build());
+
+      assertNotNull(api.getFirewallPolicy(uuid));
+   }
+
+   @Test
    public void testCreateFirewallPolicies() throws Exception {
       CloudSigma2Api api = requestSendsResponse(
             postBuilder()
@@ -1000,6 +1023,26 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
    }
 
    @Test
+   public void testDeleteFirewallPolicy() throws Exception {
+      String uuid = "9001b532-857a-405a-8e50-54e342871e77";
+
+      CloudSigma2Api api = requestsSendResponses(
+            deleteBuilder()
+                  .endpoint(endpoint + "fwpolicies/" + uuid + "/")
+                  .build(),
+            responseBuilder()
+                  .build(),
+            deleteBuilder()
+                  .endpoint(endpoint + "fwpolicies/failure")
+                  .build(),
+            HttpResponse.builder()
+                  .statusCode(404)
+                  .build());
+
+      api.deleteFirewallPolicy(uuid);
+   }
+
+   @Test
    public void testGetVLANInfo() throws Exception {
       String uuid = "96537817-f4b6-496b-a861-e74192d3ccb0";
       CloudSigma2Api api = requestSendsResponse(
@@ -1276,50 +1319,6 @@ public class CloudSigma2ApiExpectTest extends BaseRestApiExpectTest<CloudSigma2A
    }
 
    @Test
-   public void testListTagsInfo() throws Exception {
-      CloudSigma2Api api = requestsSendResponses(
-            getBuilder()
-                  .endpoint(endpoint + "tags/detail/")
-                  .build(),
-            responseBuilder()
-                  .payload(payloadFromResourceWithContentType("/tags-detail-first-page.json",
-                        MediaType.APPLICATION_JSON))
-                  .build(),
-            getBuilder()
-                  .endpoint(endpoint + "tags/detail/")
-                  .addQueryParam("limit", "1")
-                  .addQueryParam("offset", "1")
-                  .build(),
-            responseBuilder()
-                  .payload(payloadFromResourceWithContentType("/tags-detail-last-page.json",
-                        MediaType.APPLICATION_JSON))
-                  .build());
-
-
-      List<Tag> tags = api.listTagsInfo().concat().toList();
-
-      assertEquals(tags.size(), 2);
-      assertEquals(tags.get(0).getUuid(), "956e2ca0-dee3-4b3f-a1be-a6e86f90946f");
-      assertEquals(tags.get(1).getUuid(), "68bb0cfc-0c76-4f37-847d-7bb705c5ae46");
-   }
-
-
-   @Test
-   public void testListTagsInfoPaginatedCollection() throws Exception {
-      CloudSigma2Api api = requestSendsResponse(
-            getBuilder()
-                  .endpoint(endpoint + "tags/detail/?limit=2&offset=2")
-                  .build(),
-            responseBuilder()
-                  .payload(payloadFromResourceWithContentType("/tags-detail.json", MediaType.APPLICATION_JSON))
-                  .build());
-
-      for (Tag tag : api.listTagsInfo(new PaginationOptions.Builder().limit(2).offset(2).build())) {
-         assertNotNull(tag);
-      }
-   }
-
-   @Test
    public void testGetTagInfo() throws Exception {
       String uuid = "68bb0cfc-0c76-4f37-847d-7bb705c5ae46";
       CloudSigma2Api api = requestSendsResponse(

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiLiveTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiLiveTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiLiveTest.java
index 5fcf1bb..751dea8 100644
--- a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiLiveTest.java
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/CloudSigma2ApiLiveTest.java
@@ -70,10 +70,13 @@ import com.google.common.collect.Maps;
 public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
 
    private DriveInfo createdDrive;
+   private DriveInfo clonedDrive;
+   private LibraryDrive clonedLibraryDrive;
    private List<DriveInfo> createdDrives;
    private ServerInfo createdServer;
    private List<ServerInfo> createdServers;
    private FirewallPolicy createdFirewallPolicy;
+   private List<FirewallPolicy> createdFirewallPolicies;
    private Tag createdTag;
    private List<Tag> createdTags;
 
@@ -142,11 +145,23 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
       checkDrive(editedDrive, api.editDrive(createdDrive.getUuid(), editedDrive));
    }
 
+   @Test(dependsOnMethods = {"testCreateDrive"})
+   public void testCloneDrive() throws Exception {
+      clonedDrive = api.cloneDrive(createdDrive.getUuid(), null);
+      checkDrive(createdDrive, clonedDrive);
+   }
+
    @Test(dependsOnMethods = {"testEditDrive", "testCreateTag", "testEditTag"})
    public void testDeleteDrive() throws Exception {
       String uuid = createdDrive.getUuid();
       api.deleteDrive(uuid);
       assertNull(api.getDriveInfo(uuid));
+      String clonedDriveUuid = clonedDrive.getUuid();
+      api.deleteDrive(clonedDriveUuid);
+      assertNull(api.getDriveInfo(clonedDriveUuid));
+      String clonedLibraryDriveUuid = clonedLibraryDrive.getUuid();
+      api.deleteDrive(clonedLibraryDriveUuid);
+      assertNull(api.getDriveInfo(clonedLibraryDriveUuid));
    }
 
    @Test(dependsOnMethods = {"testCreateDrives"})
@@ -175,6 +190,13 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
       }
    }
 
+   @Test
+   public void testCloneLibraryDrive() throws Exception {
+      LibraryDrive libraryDrive = api.listLibraryDrives().concat().get(0);
+      clonedLibraryDrive = api.cloneLibraryDrive(libraryDrive.getUuid(), null);
+      checkLibraryDrive(libraryDrive, clonedLibraryDrive);
+   }
+
    @Test(dependsOnMethods = {"testCreateServers"})
    public void testListServers() throws Exception {
       assertNotNull(api.listServers());
@@ -273,6 +295,15 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
       assertNotNull(api.listFirewallPoliciesInfo());
    }
 
+   @Test(dependsOnMethods = {"testCreateFirewallPolicies"})
+   public void testGetFirewallPolicy() throws Exception {
+      for (FirewallPolicy firewallPolicy : api.listFirewallPoliciesInfo().concat()) {
+         FirewallPolicy receivedPolicy = api.getFirewallPolicy(firewallPolicy.getUuid());
+         checkFirewallPolicy(firewallPolicy, receivedPolicy);
+         assertEquals(firewallPolicy.getUuid(), receivedPolicy.getUuid());
+      }
+   }
+
    @Test
    public void testCreateFirewallPolicies() throws Exception {
       List<FirewallPolicy> newFirewallPolicies = ImmutableList.of(
@@ -329,7 +360,7 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
                         .build()))
                   .build());
 
-      List<FirewallPolicy> createdFirewallPolicies = api.createFirewallPolicies(newFirewallPolicies);
+      createdFirewallPolicies = api.createFirewallPolicies(newFirewallPolicies);
       assertEquals(newFirewallPolicies.size(), createdFirewallPolicies.size());
 
       for (int i = 0; i < newFirewallPolicies.size(); i++) {
@@ -403,6 +434,23 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
       checkFirewallPolicy(editedPolicy, api.editFirewallPolicy(createdFirewallPolicy.getUuid(), editedPolicy));
    }
 
+   @Test(dependsOnMethods = {"testEditFirewallPolicy", "testCreateFirewallPolicies"})
+   public void deleteFirewallPolicies() throws Exception {
+      ImmutableList.Builder<String> stringListBuilder = ImmutableList.builder();
+
+      stringListBuilder.add(createdFirewallPolicy.getUuid());
+      api.deleteFirewallPolicy(createdFirewallPolicy.getUuid());
+
+      for (FirewallPolicy firewallPolicy : createdFirewallPolicies) {
+         stringListBuilder.add(firewallPolicy.getUuid());
+         api.deleteFirewallPolicy(firewallPolicy.getUuid());
+      }
+
+      ImmutableList<String> uuids = stringListBuilder.build();
+      FluentIterable<FirewallPolicy> servers = api.listFirewallPolicies().concat();
+      assertFalse(any(transform(servers, extractUuid()), in(uuids)));
+   }
+
    @Test
    public void testListVLANs() throws Exception {
       assertNotNull(api.listVLANs());
@@ -429,7 +477,7 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
             .meta(meta)
             .build();
 
-      if (!api.listVLANs().isEmpty()) {
+      if (!api.listVLANs().concat().isEmpty()) {
          checkVlAN(vlanInfo, api.editVLAN(api.listVLANs().concat().get(0).getUuid(), vlanInfo));
       }
    }
@@ -460,7 +508,7 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
             .meta(meta)
             .build();
 
-      if (!api.listIPs().isEmpty()) {
+      if (!api.listIPs().concat().isEmpty()) {
          checkIP(ip, api.editIP(api.listIPs().concat().get(0).getUuid(), ip));
       }
    }
@@ -471,11 +519,6 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
    }
 
    @Test(dependsOnMethods = {"testCreateTags"})
-   public void testListTagsInfo() throws Exception {
-      assertNotNull(api.listTagsInfo());
-   }
-
-   @Test(dependsOnMethods = {"testCreateTags"})
    public void testGetTagInfo() throws Exception {
       for (Tag tag : api.listTags().concat()) {
          assertNotNull(api.getTagInfo(tag.getUuid()));
@@ -652,6 +695,18 @@ public class CloudSigma2ApiLiveTest extends BaseApiLiveTest<CloudSigma2Api> {
       assertEquals(newDrive.getMedia(), createdDrive.getMedia());
    }
 
+   private void checkLibraryDrive(LibraryDrive newDrive, LibraryDrive createdDrive) {
+      checkDrive(newDrive, createdDrive);
+      Map<String, String> meta = createdDrive.getMeta();
+
+      assertEquals(newDrive.getArch() == null ? "None" : newDrive.getArch(), meta.get("arch"));
+      assertEquals(newDrive.getDescription() == null ? "None" : newDrive.getDescription(), meta.get("description"));
+      assertEquals(newDrive.getImageType() == null ? "None" : newDrive.getImageType(), meta.get("image_type"));
+      assertEquals(newDrive.getInstallNotes() == null ? "None" : newDrive.getInstallNotes(), meta.get("install_notes"));
+      assertEquals(newDrive.getOs() == null ? "None" : newDrive.getOs(), meta.get("os"));
+      assertEquals(newDrive.getVersion() == null ? "None" : newDrive.getVersion(), meta.get("version"));
+   }
+
    private void checkServer(ServerInfo newServer, ServerInfo createdServer) {
       assertEquals(newServer.getName(), createdServer.getName());
       assertEquals(newServer.getMemory(), createdServer.getMemory());

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/CloudSigma2ComputeServiceLiveTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/CloudSigma2ComputeServiceLiveTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/CloudSigma2ComputeServiceLiveTest.java
new file mode 100644
index 0000000..d0b5a91
--- /dev/null
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/CloudSigma2ComputeServiceLiveTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cloudsigma2.compute;
+
+import com.google.inject.Module;
+import org.jclouds.compute.domain.ExecResponse;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.internal.BaseComputeServiceLiveTest;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "CloudSigma2ComputeServiceLiveTest")
+public class CloudSigma2ComputeServiceLiveTest extends BaseComputeServiceLiveTest {
+
+   public CloudSigma2ComputeServiceLiveTest() {
+      provider = "cloudsigma2";
+
+   }
+
+   @Override
+   protected Module getSshModule() {
+      return new SshjSshClientModule();
+   }
+
+   // CloudSigma templates require manual interaction to change the password on the first login.
+   // The only way to automatically authenticate to a server is to use an image that supports Cloud Init
+   // and provide the public key
+   @Override
+   protected Template buildTemplate(TemplateBuilder templateBuilder) {
+      Template template = super.buildTemplate(templateBuilder);
+      template.getOptions().authorizePublicKey(keyPair.get("public"));
+      return template;
+   }
+
+   @Override
+   protected void checkResponseEqualsHostname(ExecResponse execResponse, NodeMetadata node1) {
+      // CloudSigma does not return the hostname
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/DriveClonedPredicateTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/DriveClonedPredicateTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/DriveClonedPredicateTest.java
new file mode 100644
index 0000000..acabdb3
--- /dev/null
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/DriveClonedPredicateTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cloudsigma2.compute.config;
+
+import org.easymock.EasyMock;
+import org.jclouds.cloudsigma2.CloudSigma2Api;
+import org.jclouds.cloudsigma2.compute.config.CloudSigma2ComputeServiceContextModule.DriveClonedPredicate;
+import org.jclouds.cloudsigma2.domain.DriveInfo;
+import org.jclouds.cloudsigma2.domain.DriveStatus;
+import org.testng.annotations.Test;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Unit tests for the drive cloned predicate
+ */
+@Test(groups = "unit", testName = "DriveClonedPredicateTest")
+public class DriveClonedPredicateTest {
+
+   public void testDriveCloned() {
+      CloudSigma2Api api = EasyMock.createMock(CloudSigma2Api.class);
+
+      for (DriveStatus status : DriveStatus.values()) {
+         expect(api.getDriveInfo(status.name())).andReturn(mockDrive(status));
+      }
+
+      replay(api);
+
+      DriveClonedPredicate predicate = new DriveClonedPredicate(api);
+
+      assertFalse(predicate.apply(mockDrive(DriveStatus.COPYING)));
+      assertFalse(predicate.apply(mockDrive(DriveStatus.UNAVAILABLE)));
+      assertTrue(predicate.apply(mockDrive(DriveStatus.MOUNTED)));
+      assertTrue(predicate.apply(mockDrive(DriveStatus.UNMOUNTED)));
+
+      verify(api);
+   }
+
+   private static DriveInfo mockDrive(DriveStatus status) {
+      return new DriveInfo.Builder().uuid(status.name()).status(status).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/ServerStatusPredicatePredicateTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/ServerStatusPredicatePredicateTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/ServerStatusPredicatePredicateTest.java
new file mode 100644
index 0000000..ea17420
--- /dev/null
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/config/ServerStatusPredicatePredicateTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cloudsigma2.compute.config;
+
+import org.easymock.EasyMock;
+import org.jclouds.cloudsigma2.CloudSigma2Api;
+import org.jclouds.cloudsigma2.compute.config.CloudSigma2ComputeServiceContextModule.ServerStatusPredicate;
+import org.jclouds.cloudsigma2.domain.ServerInfo;
+import org.testng.annotations.Test;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.jclouds.cloudsigma2.domain.ServerStatus.STOPPED;
+import static org.jclouds.cloudsigma2.domain.ServerStatus.STOPPING;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Unit tests for the server status predicate.
+ */
+@Test(groups = "unit", testName = "ServerStatusPredicatePredicateTest")
+public class ServerStatusPredicatePredicateTest {
+
+   public void testServerStatus() {
+      CloudSigma2Api api = EasyMock.createMock(CloudSigma2Api.class);
+
+      expect(api.getServerInfo("one")).andReturn(new ServerInfo.Builder().status(STOPPED).build());
+      expect(api.getServerInfo("two")).andReturn(new ServerInfo.Builder().status(STOPPING).build());
+
+      replay(api);
+
+      ServerStatusPredicate predicate = new ServerStatusPredicate(api, STOPPED);
+      assertTrue(predicate.apply("one"));
+      assertFalse(predicate.apply("two"));
+
+      verify(api);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/a7dd1933/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/functions/LibraryDriveToImageTest.java
----------------------------------------------------------------------
diff --git a/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/functions/LibraryDriveToImageTest.java b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/functions/LibraryDriveToImageTest.java
new file mode 100644
index 0000000..313ea61
--- /dev/null
+++ b/cloudsigma2/src/test/java/org/jclouds/cloudsigma2/compute/functions/LibraryDriveToImageTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.cloudsigma2.compute.functions;
+
+import com.google.common.collect.ImmutableMap;
+import org.jclouds.cloudsigma2.domain.DriveStatus;
+import org.jclouds.cloudsigma2.domain.LibraryDrive;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.domain.OsFamily;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.jclouds.cloudsigma2.compute.config.CloudSigma2ComputeServiceContextModule.driveStatusToImageStatus;
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = "unit", testName = "LibraryDriveToImageTest")
+public class LibraryDriveToImageTest {
+
+   private LibraryDrive input;
+   private Image expected;
+
+   @BeforeMethod
+   public void setUp() throws Exception {
+      input = new LibraryDrive.Builder()
+            .uuid("0bc6b02c-7ea2-4c5c-bf07-41c4cec2797d")
+            .name("Debian 7.3 Server")
+            .description("Debian 7.3 Server - amd64 Pre-Installed English with Python, SSH and VirtIO support. " +
+                  "Last update 2014/02/15.")
+            .os("linux")
+            .arch("64")
+            .version("7.3")
+            .status(DriveStatus.UNMOUNTED)
+            .meta(ImmutableMap.of("test_key", "test_value",
+                  "sample key", "sample value"))
+            .build();
+
+      expected = new ImageBuilder()
+            .ids("0bc6b02c-7ea2-4c5c-bf07-41c4cec2797d")
+            .userMetadata(ImmutableMap.of("test_key", "test_value",
+                  "sample key", "sample value"))
+            .name("Debian 7.3 Server")
+            .description("Debian 7.3 Server - amd64 Pre-Installed English with Python, SSH and VirtIO support. " +
+                  "Last update 2014/02/15.")
+            .operatingSystem(OperatingSystem.builder()
+                  .name("Debian 7.3 Server")
+                  .arch("64")
+                  .family(OsFamily.LINUX)
+                  .version("7.3")
+                  .is64Bit(true)
+                  .description("Debian 7.3 Server - amd64 Pre-Installed English with Python, SSH and VirtIO support. " +
+                        "Last update 2014/02/15.")
+                  .build())
+            .status(Image.Status.UNRECOGNIZED)
+            .build();
+   }
+
+   public void testConvertLibraryDrive() {
+      LibraryDriveToImage function = new LibraryDriveToImage(driveStatusToImageStatus);
+      Image converted = function.apply(input);
+      assertEquals(converted, expected);
+      assertEquals(converted.getUserMetadata(), expected.getUserMetadata());
+      assertEquals(converted.getName(), expected.getName());
+      assertEquals(converted.getDescription(), expected.getDescription());
+      assertEquals(converted.getStatus(), expected.getStatus());
+      assertEquals(converted.getOperatingSystem(), expected.getOperatingSystem());
+   }
+}