You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ed...@apache.org on 2014/01/16 00:14:14 UTC

git commit: updated refs/heads/master to 753b563

Updated Branches:
  refs/heads/master cc2b1c496 -> 753b56394


NexentaStor iSCSI volume driver
This a NexentaStor iSCSI volume driver.

Now implemented only following functions:
  * create volume
  * delete volume

Currently delete volume still in progress.

Signed-off-by: Edison Su <su...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/753b5639
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/753b5639
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/753b5639

Branch: refs/heads/master
Commit: 753b5639475448b44641b3d182aa32f49b5bf1ae
Parents: cc2b1c4
Author: Victor Rodionov <vi...@nexenta.com>
Authored: Wed Jan 15 15:12:02 2014 -0800
Committer: Edison Su <su...@gmail.com>
Committed: Wed Jan 15 15:12:48 2014 -0800

----------------------------------------------------------------------
 plugins/pom.xml                                 |   1 +
 plugins/storage/volume/nexenta/pom.xml          |  47 +++
 .../module.properties                           |  18 +
 .../spring-storage-volume-nexenta-context.xml   |  32 ++
 .../driver/NexentaPrimaryDataStoreDriver.java   | 194 +++++++++
 .../NexentaPrimaryDataStoreLifeCycle.java       | 159 ++++++++
 .../datastore/provider/NexentaHostListener.java |  17 +
 .../NexentaPrimaryDataStoreProvider.java        |  81 ++++
 .../datastore/util/NexentaNmsClient.java        | 212 ++++++++++
 .../storage/datastore/util/NexentaNmsUrl.java   |  85 ++++
 .../datastore/util/NexentaStorAppliance.java    | 401 +++++++++++++++++++
 .../storage/datastore/util/NexentaUtil.java     | 242 +++++++++++
 .../util/NexentaStorApplianceTest.java          | 319 +++++++++++++++
 .../storage/datastore/util/NexentaUtilTest.java | 117 ++++++
 14 files changed, 1925 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 85a7bbe..37382de 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -62,6 +62,7 @@
     <module>storage/image/swift</module>
     <module>storage/image/default</module>
     <module>storage/image/sample</module>
+    <module>storage/volume/nexenta</module>
     <module>storage/volume/solidfire</module>
     <module>storage/volume/default</module>
     <module>storage/volume/sample</module>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/pom.xml
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/pom.xml b/plugins/storage/volume/nexenta/pom.xml
new file mode 100644
index 0000000..4db205f
--- /dev/null
+++ b/plugins/storage/volume/nexenta/pom.xml
@@ -0,0 +1,47 @@
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cloud-plugin-storage-volume-nexenta</artifactId>
+  <name>Apache CloudStack Plugin - Storage Volume Nexenta Provider</name>
+  <parent>
+    <groupId>org.apache.cloudstack</groupId>
+    <artifactId>cloudstack-plugins</artifactId>
+    <version>4.4.0-SNAPSHOT</version>
+    <relativePath>../../../pom.xml</relativePath>
+  </parent>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-engine-storage-volume</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>true</skipTests>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>integration-test</phase>
+            <goals>
+              <goal>test</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties b/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
new file mode 100644
index 0000000..c203600
--- /dev/null
+++ b/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/module.properties
@@ -0,0 +1,18 @@
+# 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.
+name=storage-volume-nexenta
+parent=storage
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml b/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
new file mode 100644
index 0000000..6c83c1a
--- /dev/null
+++ b/plugins/storage/volume/nexenta/resources/META-INF.cloudstack.storage-volume-solidfire/spring-storage-volume-nexenta-context.xml
@@ -0,0 +1,32 @@
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <bean id="nexentaStorDataStoreProvider"
+        class="org.apache.cloudstack.storage.datastore.provider.NexentaPrimaryDataStoreProvider" />
+
+</beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
new file mode 100644
index 0000000..70f4a4f
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java
@@ -0,0 +1,194 @@
+/*
+ * 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.apache.cloudstack.storage.datastore.driver;
+
+import static org.apache.cloudstack.storage.datastore.util.NexentaUtil.NexentaPluginParameters;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance;
+import org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.NexentaStorZvol;
+import org.apache.cloudstack.storage.datastore.util.NexentaUtil;
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.host.Host;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.dao.AccountDao;
+
+public class NexentaPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
+    private static final Logger logger = Logger.getLogger(NexentaPrimaryDataStoreDriver.class);
+
+    @Override
+    public boolean connectVolumeToHost(VolumeInfo volumeInfo, Host host, DataStore dataStore) {
+        return false;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void disconnectVolumeFromHost(VolumeInfo volumeInfo, Host host, DataStore dataStore) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public long getVolumeSizeIncludingHypervisorSnapshotReserve(Volume volume, StoragePool pool) {
+        return 0;  //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Inject
+    private VolumeDao _volumeDao;
+    @Inject
+    PrimaryDataStoreDao _storagePoolDao;
+    @Inject
+    private StoragePoolDetailsDao _storagePoolDetailsDao;
+    @Inject
+    private AccountDao _accountDao;
+
+    @Override
+    public Map<String, String> getCapabilities() {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(VolumeInfo volumeInfo) {
+        return null;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data) {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store) {
+        return null;
+    }
+
+    private NexentaStorAppliance getNexentaStorAppliance(long storagePoolId) {
+        NexentaPluginParameters parameters = new NexentaPluginParameters();
+
+        parameters.setNmsUrl(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.NMS_URL).getValue());
+        parameters.setVolume(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.VOLUME).getValue());
+        parameters.setStorageType(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.STORAGE_TYPE).getValue());
+        parameters.setStorageHost(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.STORAGE_HOST).getValue());
+        parameters.setStoragePort(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.STORAGE_PORT).getValue());
+        parameters.setStoragePath(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.STORAGE_PATH).getValue());
+        parameters.setSparseVolumes(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.SPARSE_VOLUMES).getValue());
+        parameters.setVolumeBlockSize(_storagePoolDetailsDao.findDetail(storagePoolId, NexentaUtil.VOLUME_BLOCK_SIZE).getValue());
+
+        return new NexentaStorAppliance(parameters);
+    }
+
+    @Override
+    public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {}
+
+    @Override
+    public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CommandResult> callback) {}
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
+        String iqn = null;
+        String errorMessage = null;
+
+        if (dataObject.getType() != DataObjectType.VOLUME) {
+            errorMessage = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
+        } else {
+            VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+
+            long storagePoolId = dataStore.getId();
+            NexentaStorAppliance appliance = getNexentaStorAppliance(storagePoolId);
+
+            // TODO: maybe we should use md5(volume name) as volume name
+            NexentaStorZvol zvol = (NexentaStorZvol) appliance.createVolume(volumeInfo.getName(), volumeInfo.getSize());
+            iqn = zvol.getIqn();
+
+            VolumeVO volume = this._volumeDao.findById(volumeInfo.getId());
+            volume.set_iScsiName(iqn);
+            volume.setFolder(zvol.getName());
+            volume.setPoolType(Storage.StoragePoolType.IscsiLUN);
+            volume.setPoolId(storagePoolId);
+            _volumeDao.update(volume.getId(), volume);
+
+            StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+            long capacityBytes = storagePool.getCapacityBytes();
+            long usedBytes = storagePool.getUsedBytes();
+            usedBytes += volumeInfo.getSize();
+            storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes);
+            _storagePoolDao.update(storagePoolId, storagePool);
+        }
+
+        CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errorMessage == null, errorMessage));
+        result.setResult(errorMessage);
+        callback.complete(result);
+    }
+
+    @Override
+    public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
+        String errorMessage = null;
+        if (data.getType() == DataObjectType.VOLUME) {
+            VolumeInfo volumeInfo = (VolumeInfo) data;
+            long storagePoolId = store.getId();
+            NexentaStorAppliance appliance = getNexentaStorAppliance(storagePoolId);
+            StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+
+
+//            _storagePoolDao.update(stoagePoolId);
+        } else {
+            errorMessage = String.format(
+                    "Invalid DataObjectType(%s) passed to deleteAsync",
+                    data.getType());
+        }
+        CommandResult result = new CommandResult();
+        result.setResult(errorMessage);
+        callback.complete(result);
+    }
+
+    @Override
+    public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {}
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData) {
+        return false;
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
new file mode 100644
index 0000000..775e6b5
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java
@@ -0,0 +1,159 @@
+package org.apache.cloudstack.storage.datastore.lifecylce;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import com.cloud.dc.DataCenterVO;
+import com.cloud.host.HostVO;
+import com.cloud.resource.ResourceManager;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePoolAutomation;
+import org.apache.cloudstack.storage.datastore.util.NexentaUtil;
+import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.hypervisor.Hypervisor;
+
+public class NexentaPrimaryDataStoreLifeCycle
+        implements PrimaryDataStoreLifeCycle {
+    private static final Logger logger =
+            Logger.getLogger(NexentaPrimaryDataStoreLifeCycle.class);
+
+    @Inject
+    private DataCenterDao zoneDao;
+    @Inject
+    private PrimaryDataStoreHelper dataStoreHelper;
+    @Inject
+    private ResourceManager _resourceMgr;
+    @Inject
+    StorageManager _storageMgr;
+    @Inject
+    private StoragePoolAutomation storagePoolAutomation;
+
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+        String url = (String) dsInfos.get("url");
+        Long zoneId = (Long) dsInfos.get("zoneId");
+        String storagePoolName = (String) dsInfos.get("name");
+        String providerName = (String) dsInfos.get("providerName");
+        Long capacityBytes = (Long)dsInfos.get("capacityBytes");
+        Long capacityIops = (Long)dsInfos.get("capacityIops");
+        String tags = (String)dsInfos.get("tags");
+        Map<String, String> details = (Map<String, String>) dsInfos.get("details");
+        NexentaUtil.NexentaPluginParameters params = NexentaUtil.parseNexentaPluginUrl(url);
+        DataCenterVO zone = zoneDao.findById(zoneId);
+        String uuid = String.format("%s_%s_%s", NexentaUtil.PROVIDER_NAME, zone.getUuid(), params.getNmsUrl().getHost());
+
+        if (capacityBytes == null || capacityBytes <= 0) {
+            throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0.");
+        }
+
+        if (capacityIops == null || capacityIops <= 0) {
+            throw new IllegalArgumentException("'capacityIops' must be present and greater than 0.");
+        }
+
+        PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
+
+        parameters.setHost(params.getStorageHost());
+        parameters.setPort(params.getStoragePort());
+        parameters.setPath(params.getStoragePath());
+        parameters.setType(params.getStorageType());
+        parameters.setUuid(uuid);
+        parameters.setZoneId(zoneId);
+        parameters.setName(storagePoolName);
+        parameters.setProviderName(providerName);
+        parameters.setManaged(true);
+        parameters.setCapacityBytes(capacityBytes);
+        parameters.setUsedBytes(0);
+        parameters.setCapacityIops(capacityIops);
+        parameters.setHypervisorType(Hypervisor.HypervisorType.Any);
+        parameters.setTags(tags);
+
+        details.put(NexentaUtil.NMS_URL, params.getNmsUrl().toString());
+
+        details.put(NexentaUtil.VOLUME, params.getVolume());
+        details.put(NexentaUtil.SPARSE_VOLUMES, params.getSparseVolumes().toString());
+
+        details.put(NexentaUtil.STORAGE_TYPE, params.getStorageType().toString());
+        details.put(NexentaUtil.STORAGE_HOST, params.getStorageHost());
+        details.put(NexentaUtil.STORAGE_PORT, params.getStoragePort().toString());
+        details.put(NexentaUtil.STORAGE_PATH, params.getStoragePath());
+
+        parameters.setDetails(details);
+
+        // this adds a row in the cloud.storage_pool table for this SolidFire cluster
+        return dataStoreHelper.createPrimaryDataStore(parameters);
+    }
+
+    @Override
+    public boolean attachCluster(DataStore store, ClusterScope scope) {
+        return true;
+    }
+
+    @Override
+    public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
+        return true;
+    }
+
+    @Override
+    public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.HypervisorType hypervisorType) {
+        dataStoreHelper.attachZone(dataStore);
+
+        List<HostVO> xenServerHosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.XenServer, scope.getScopeId());
+        List<HostVO> vmWareServerHosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.VMware, scope.getScopeId());
+        List<HostVO> kvmHosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(Hypervisor.HypervisorType.KVM, scope.getScopeId());
+        List<HostVO> hosts = new ArrayList<HostVO>();
+
+        hosts.addAll(xenServerHosts);
+        hosts.addAll(vmWareServerHosts);
+        hosts.addAll(kvmHosts);
+
+        for (HostVO host : hosts) {
+            try {
+                _storageMgr.connectHostToSharedPool(host.getId(), dataStore.getId());
+            } catch (Exception e) {
+                logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean maintain(DataStore store) {
+        storagePoolAutomation.maintain(store);
+        dataStoreHelper.maintain(store);
+
+        return true;
+    }
+
+    @Override
+    public boolean cancelMaintain(DataStore store) {
+        dataStoreHelper.cancelMaintain(store);
+        storagePoolAutomation.cancelMaintain(store);
+
+        return true;
+    }
+
+    @Override
+    public boolean deleteDataStore(DataStore store) {
+        return dataStoreHelper.deletePrimaryDataStore(store);
+    }
+
+    @Override
+    public boolean migrateToObjectStore(DataStore store) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaHostListener.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaHostListener.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaHostListener.java
new file mode 100644
index 0000000..bf6d442
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaHostListener.java
@@ -0,0 +1,17 @@
+package org.apache.cloudstack.storage.datastore.provider;
+
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+
+public class NexentaHostListener implements HypervisorHostListener {
+    private static final Logger logger = Logger.getLogger(NexentaHostListener.class);
+
+    public boolean hostConnect(long hostId, long poolId) {
+        return true;
+    }
+
+    public boolean hostDisconnected(long hostId, long poolId) {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaPrimaryDataStoreProvider.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaPrimaryDataStoreProvider.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaPrimaryDataStoreProvider.java
new file mode 100644
index 0000000..afd7e35
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/provider/NexentaPrimaryDataStoreProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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.apache.cloudstack.storage.datastore.provider;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
+import org.apache.cloudstack.storage.datastore.driver.NexentaPrimaryDataStoreDriver;
+import org.apache.cloudstack.storage.datastore.lifecylce.NexentaPrimaryDataStoreLifeCycle;
+import org.apache.cloudstack.storage.datastore.util.NexentaUtil;
+
+import com.cloud.utils.component.ComponentContext;
+
+@Component
+public class NexentaPrimaryDataStoreProvider implements PrimaryDataStoreProvider {
+    private DataStoreLifeCycle lifeCycle;
+    private PrimaryDataStoreDriver driver;
+    private HypervisorHostListener listener;
+
+    @Override
+    public DataStoreLifeCycle getDataStoreLifeCycle() {
+        return lifeCycle;
+    }
+
+    @Override
+    public DataStoreDriver getDataStoreDriver() {
+        return driver;
+    }
+
+    @Override
+    public HypervisorHostListener getHostListener() {
+        return listener;
+    }
+
+    @Override
+    public String getName() {
+        return NexentaUtil.PROVIDER_NAME;
+    }
+
+    @Override
+    public boolean configure(Map<String, Object> params) {
+        lifeCycle = ComponentContext.inject(NexentaPrimaryDataStoreLifeCycle.class);
+        driver = ComponentContext.inject(NexentaPrimaryDataStoreDriver.class);
+        listener = ComponentContext.inject(NexentaHostListener.class);
+
+        return true;
+    }
+
+    @Override
+    public Set<DataStoreProviderType> getTypes() {
+        Set<DataStoreProviderType> types = new HashSet<DataStoreProviderType>();
+
+        types.add(DataStoreProviderType.PRIMARY);
+
+        return types;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsClient.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsClient.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsClient.java
new file mode 100644
index 0000000..7ae1577
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsClient.java
@@ -0,0 +1,212 @@
+package org.apache.cloudstack.storage.datastore.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.BasicClientConnectionManager;
+import org.apache.log4j.Logger;
+
+public class NexentaNmsClient {
+    private static final Logger logger = Logger.getLogger(NexentaNmsClient.class);
+
+    protected NexentaNmsUrl nmsUrl = null;
+    protected DefaultHttpClient httpClient = null;
+
+    NexentaNmsClient(NexentaNmsUrl nmsUrl) {
+        this.nmsUrl = nmsUrl;
+    }
+
+    private static boolean isSuccess(int iCode) {
+        return iCode >= 200 && iCode < 300;
+    }
+
+    protected DefaultHttpClient getClient() {
+        if (httpClient == null) {
+            if (nmsUrl.getSchema().equalsIgnoreCase("http")) {
+                httpClient = getHttpClient();
+            } else {
+                httpClient = getHttpsClient();
+            }
+            AuthScope authScope = new AuthScope(nmsUrl.getHost(), nmsUrl.getPort(), AuthScope.ANY_SCHEME, "basic");
+            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(nmsUrl.getUsername(), nmsUrl.getPassword());
+            httpClient.getCredentialsProvider().setCredentials(authScope, credentials);
+        }
+        return httpClient;
+    }
+
+    protected DefaultHttpClient getHttpsClient() {
+        try {
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            X509TrustManager tm = new X509TrustManager() {
+                @Override
+                public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
+                }
+
+                @Override
+                public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
+                }
+
+                @Override
+                public X509Certificate[] getAcceptedIssuers() {
+                    return null;
+                }
+            };
+
+            sslContext.init(null, new TrustManager[] {tm}, new SecureRandom());
+
+            SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+            SchemeRegistry registry = new SchemeRegistry();
+
+            registry.register(new Scheme("https", nmsUrl.getPort(), socketFactory));
+
+            BasicClientConnectionManager mgr = new BasicClientConnectionManager(registry);
+
+            return new DefaultHttpClient(mgr);
+        } catch (NoSuchAlgorithmException ex) {
+            throw new CloudRuntimeException(ex.getMessage());
+        } catch (KeyManagementException ex) {
+            throw new CloudRuntimeException(ex.getMessage());
+        }
+    }
+
+    protected DefaultHttpClient getHttpClient() {
+        return new DefaultHttpClient();
+    }
+
+    @SuppressWarnings("unused")
+    static class NmsRequest {
+        private String method;
+        private String object;
+        private Object[] params;
+
+        NmsRequest(String object, String method) {
+            this(method, object, null);
+        }
+
+        NmsRequest(String object, String method, Object... params) {
+            this.method = method;
+            this.object = object;
+            this.params = params;
+        }
+
+        public String toString() {
+            StringBuffer b = new StringBuffer();
+            b.append("Request to ").append(object).append(" method ").append(method);
+            if (params != null) {
+                b.append(" params:");
+                for (Object param:params) {
+                    b.append(" ").append(param).append(",");
+                }
+            }
+            return b.toString();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static class NmsError {
+        private String message;
+
+        public String getMesssage() {
+            return message;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static class NmsResponse {
+        @SerializedName("tg_flash") protected String tgFlash;
+
+        protected NmsError error;
+
+        NmsResponse() {}
+
+        NmsResponse(String tgFlash, NmsError error) {
+            this.tgFlash = tgFlash;
+            this.error = error;
+        }
+
+        public String getTgFlash() {
+            return tgFlash;
+        }
+
+        public NmsError getError() {
+            return error;
+        }
+    }
+
+    public NmsResponse execute(Class responseClass, String object, String method, Object... params) {
+        StringBuilder sb = new StringBuilder();
+        NmsRequest nmsRequest = new NmsRequest(object, method, params);
+        if (logger.isDebugEnabled()) {
+            logger.debug(nmsRequest);
+        }
+        final Gson gson = new Gson();
+        String jsonRequest = gson.toJson(nmsRequest);
+        StringEntity input = new StringEntity(jsonRequest, ContentType.APPLICATION_JSON);
+        HttpPost postRequest = new HttpPost(nmsUrl.toString());
+        postRequest.setEntity(input);
+
+        DefaultHttpClient httpClient = getClient();
+        try {
+            HttpResponse response = httpClient.execute(postRequest);
+            final int status = response.getStatusLine().getStatusCode();
+            if (!isSuccess(status)) {
+                throw new CloudRuntimeException("Failed on JSON-RPC API call. HTTP error code = " + status);
+            }
+            BufferedReader buffer = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
+            String tmp;
+            while ((tmp = buffer.readLine()) != null) {
+                sb.append(tmp);
+            }
+        } catch (ClientProtocolException ex) {
+            throw new CloudRuntimeException(ex.getMessage());
+        } catch (IOException ex) {
+            throw new CloudRuntimeException(ex.getMessage());
+        } finally {
+            if (httpClient != null) {
+                try {
+                    httpClient.getConnectionManager().shutdown();
+                } catch (Exception t) {
+                    logger.debug(t.getMessage());
+                }
+            }
+        }
+
+        String responseString = sb.toString();
+        if (logger.isDebugEnabled()) {
+            logger.debug("NexentaStor Appliance response: " + responseString);
+        }
+
+        NmsResponse nmsResponse = (NmsResponse) gson.fromJson(responseString, responseClass);
+        if (nmsResponse.getError() != null) {
+            throw new CloudRuntimeException(nmsResponse.getError().getMesssage());
+        }
+
+        return nmsResponse;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsUrl.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsUrl.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsUrl.java
new file mode 100644
index 0000000..684b8dc
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaNmsUrl.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.apache.cloudstack.storage.datastore.util;
+
+public class NexentaNmsUrl {
+    protected final boolean isAuto;
+    protected String schema;
+    protected final String username;
+    protected final String password;
+    protected final String host;
+    protected int port;
+
+    public NexentaNmsUrl(boolean isAuto, String schema, String username, String password, String host, int port) {
+        this.isAuto = isAuto;
+        this.schema = schema;
+        this.username = username;
+        this.password = password;
+        this.host = host;
+        this.port = port;
+    }
+
+    public boolean isAuto() {
+        return this.isAuto;
+    }
+
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    public String getSchema() {
+        return this.schema;
+    }
+
+    public String getUsername() {
+        return this.username;
+    }
+
+    public String getPassword() {
+        return this.password;
+    }
+
+    public String getHost() {
+        return this.host;
+    }
+
+    public int getPort() {
+        return this.port;
+    }
+
+    public String getPath() {
+        return "/rest/nms";
+    }
+
+    public String toString() {
+        StringBuilder b = new StringBuilder();
+        if (isAuto) {
+            b.append("auto://");
+        } else {
+            b.append(schema).append("://");
+        }
+        if (username != null && password != null) {
+            b.append(username).append(":").append(password).append("@");
+        } else if (username != null) {
+            b.append(username).append("@");
+        }
+        b.append(host).append(":").append(port).append("/rest/nms/");
+        return b.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaStorAppliance.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaStorAppliance.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaStorAppliance.java
new file mode 100644
index 0000000..d5cad58
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaStorAppliance.java
@@ -0,0 +1,401 @@
+package org.apache.cloudstack.storage.datastore.util;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import com.google.gson.annotations.SerializedName;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.storage.datastore.util.NexentaNmsClient.NmsResponse;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class NexentaStorAppliance {
+    private static final Logger logger = LogManager.getLogger(NexentaStorAppliance.class);
+
+    protected NexentaNmsClient client;
+    protected NexentaUtil.NexentaPluginParameters parameters;
+
+    public NexentaStorAppliance(NexentaUtil.NexentaPluginParameters parameters) {
+        client = new NexentaNmsClient(parameters.getNmsUrl());
+        this.parameters = parameters;
+    }
+
+    NexentaStorAppliance(NexentaNmsClient client, NexentaUtil.NexentaPluginParameters parameters) {
+        this.client = client;
+        this.parameters = parameters;
+    }
+
+    String getVolumeName(String volumeName) {
+        if (volumeName.startsWith("/")) {
+            return String.format("%s%s", parameters.getVolume(), volumeName);
+        }
+        return String.format("%s/%s", parameters.getVolume(), volumeName);
+    }
+
+    static String getTargetName(String volumeName) {
+        return NexentaUtil.ISCSI_TARGET_NAME_PREFIX + volumeName;
+    }
+
+    static String getTargetGroupName(String volumeName) {
+        return NexentaUtil.ISCSI_TARGET_GROUP_PREFIX + volumeName;
+    }
+
+    @SuppressWarnings("unused")
+    static final class IntegerNmsResponse extends NmsResponse {
+        Integer result;
+
+        IntegerNmsResponse(int result) {
+            this.result = Integer.valueOf(result);
+        }
+
+        public Integer getResult() {
+            return result;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static final class IscsiTarget {
+        protected String status;
+        protected String protocol;
+        protected String name;
+        protected String sessions;
+        protected String alias;
+        protected String provider;
+
+        IscsiTarget(String status, String protocol, String name, String sessions, String alias, String provider) {
+            this.status = status;
+            this.protocol = protocol;
+            this.name = name;
+            this.sessions = sessions;
+            this.alias = alias;
+            this.provider = provider;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static final class ListOfIscsiTargetsNmsResponse extends NmsResponse {
+        protected HashMap<String, IscsiTarget> result;
+
+        ListOfIscsiTargetsNmsResponse() {}
+
+        ListOfIscsiTargetsNmsResponse(HashMap<String, IscsiTarget> result) {
+            this.result = result;
+        }
+
+        public HashMap<String, IscsiTarget> getResult() {
+            return result;
+        }
+    }
+
+    /**
+     * Checks if iSCSI target exists.
+     * @param targetName iSCSI target name
+     * @return true if iSCSI target exists, else false
+     */
+    boolean isIscsiTargetExists(String targetName) {
+        ListOfIscsiTargetsNmsResponse response = (ListOfIscsiTargetsNmsResponse) client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets");
+        if (response == null) {
+            return false;
+        }
+        HashMap<String, IscsiTarget> result = response.getResult();
+        return result != null && result.keySet().contains(targetName);
+    }
+
+    @SuppressWarnings("unused")
+    static final class CreateIscsiTargetRequestParams {
+        @SerializedName("target_name") String targetName;
+
+        CreateIscsiTargetRequestParams(String targetName) {
+            this.targetName = targetName;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return other instanceof CreateIscsiTargetRequestParams && targetName.equals(((CreateIscsiTargetRequestParams) other).targetName);
+        }
+    }
+
+    /**
+     * Creates iSCSI target on NexentaStor Appliance.
+     * @param targetName iSCSI target name
+     */
+    void createIscsiTarget(String targetName) {
+        try {
+            client.execute(NmsResponse.class, "iscsitarget", "create_target", new CreateIscsiTargetRequestParams(targetName));
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("already configured")) {
+                throw ex;
+            }
+            logger.debug("Ignored target creation error: " + ex);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static final class ListOfStringsNmsResponse extends NmsResponse {
+        LinkedList<String> result;
+
+        ListOfStringsNmsResponse() {}
+
+        ListOfStringsNmsResponse(LinkedList<String> result) {
+            this.result = result;
+        }
+
+        public LinkedList<String> getResult() {
+            return result;
+        }
+    }
+
+    /**
+     * Checks if iSCSI target group already exists on NexentaStor Appliance.
+     * @param targetGroupName iSCSI target group name
+     * @return true if iSCSI target group already exists, else false
+     */
+    boolean isIscsiTargetGroupExists(String targetGroupName) {
+        ListOfStringsNmsResponse response = (ListOfStringsNmsResponse) client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups");
+        if (response == null) {
+            return false;
+        }
+        LinkedList<String> result = response.getResult();
+        return result != null && result.contains(targetGroupName);
+    }
+
+    /**
+     * Creates iSCSI target group on NexentaStor Appliance.
+     * @param targetGroupName iSCSI target group name
+     */
+    void createIscsiTargetGroup(String targetGroupName) {
+        try {
+            client.execute(NmsResponse.class, "stmf", "create_targetgroup", targetGroupName);
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("already exists") && !ex.getMessage().contains("target must be offline")) {
+                throw ex;
+            }
+            logger.info("Ignored target group creation error: " + ex);
+        }
+    }
+
+    /**
+     * Checks if iSCSI target is member of target group.
+     * @param targetGroupName iSCSI target group name
+     * @param targetName iSCSI target name
+     * @return true if target is member of iSCSI target group, else false
+     */
+    boolean isTargetMemberOfTargetGroup(String targetGroupName, String targetName) {
+        ListOfStringsNmsResponse response = (ListOfStringsNmsResponse) client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName);
+        if (response == null) {
+            return false;
+        }
+        LinkedList<String> result = response.getResult();
+        return result != null && result.contains(targetName);
+    }
+
+    /**
+     * Adds iSCSI target to target group.
+     * @param targetGroupName iSCSI target group name
+     * @param targetName iSCSI target name
+     */
+    void addTargetGroupMember(String targetGroupName, String targetName) {
+        try {
+            client.execute(NmsResponse.class, "stmf", "add_targetgroup_member", targetGroupName, targetName);
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("already exists") && !ex.getMessage().contains("target must be offline")) {
+                throw ex;
+            }
+            logger.debug("Ignored target group member addition error: " + ex);
+        }
+    }
+
+    /**
+     * Checks if LU already exists on NexentaStor appliance.
+     * @param luName LU name
+     * @return true if LU already exists, else false
+     */
+    boolean isLuExists(String luName) {
+        IntegerNmsResponse response;
+        try {
+            response = (IntegerNmsResponse) client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", luName);
+        } catch (CloudRuntimeException ex) {
+            if (ex.getMessage().contains("does not exist")) {
+                return false;
+            }
+            throw ex;
+        }
+        return response!= null && response.getResult() > 0;
+    }
+
+    @SuppressWarnings("unused")
+    static final class LuParams {
+        @Override
+        public boolean equals(Object other) {
+            return other instanceof LuParams;
+        }
+    }
+
+    /**
+     * Creates LU for volume.
+     * @param volumeName volume name
+     */
+    void createLu(String volumeName) {
+        try {
+            client.execute(NmsResponse.class, "scsidisk", "create_lu", volumeName, new LuParams());
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("in use")) {
+                throw ex;
+            }
+            logger.info("Ignored LU creation error: " + ex);
+        }
+    }
+
+    /**
+     * Checks if LU shared on NexentaStor appliance.
+     * @param luName LU name
+     * @return true if LU was already shared, else false
+     */
+    boolean isLuShared(String luName) {
+        IntegerNmsResponse response;
+        try {
+            response = (IntegerNmsResponse) client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName);
+        } catch (CloudRuntimeException ex) {
+            if (ex.getMessage().contains("does not exist")) {
+                return false;
+            }
+            throw ex;
+        }
+        return response != null && response.getResult() > 0;
+    }
+
+    @SuppressWarnings("unused")
+    static final class MappingEntry {
+        @SerializedName("target_group") String targetGroup;
+        String lun;
+        String zvol;
+        @SerializedName("host_group") String hostGroup;
+        @SerializedName("entry_number") String entryNumber;
+
+        MappingEntry(String targetGroup, String lun) {
+            this.targetGroup = targetGroup;
+            this.lun = lun;
+        }
+
+        static boolean isEquals(Object a, Object b) {
+            return (a == null && b == null) || (a != null && a.equals(b));
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof MappingEntry) {
+                MappingEntry o = (MappingEntry) other;
+                return isEquals(targetGroup, o.targetGroup) && isEquals(lun, o.lun) && isEquals(zvol, o.zvol) &&
+                        isEquals(hostGroup, o.hostGroup) && isEquals(entryNumber, o.entryNumber);
+            }
+            return false;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    static final class AddMappingEntryNmsResponse extends NmsResponse {
+        MappingEntry result;
+    }
+
+    /**
+     * Adds LU mapping entry to iSCSI target group.
+     * @param luName LU name
+     * @param targetGroupName iSCSI target group name
+     */
+    void addLuMappingEntry(String luName, String targetGroupName) {
+        MappingEntry mappingEntry = new MappingEntry(targetGroupName, "0");
+        try {
+            client.execute(AddMappingEntryNmsResponse.class, "scsidisk", "add_lun_mapping_entry", luName, mappingEntry);
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("view already exists")) {
+                throw ex;
+            }
+            logger.debug("Ignored LU mapping entry addition error " + ex);
+        }
+    }
+
+    NexentaStorZvol createIscsiVolume(String volumeName, Long volumeSize) {
+        final String zvolName = getVolumeName(volumeName);
+        String volumeSizeString = String.format("%dB", volumeSize);
+
+        client.execute(NmsResponse.class, "zvol", "create", zvolName, volumeSizeString, parameters.getVolumeBlockSize(), parameters.getSparseVolumes());
+
+        final String targetName = getTargetName(volumeName);
+        final String targetGroupName = getTargetGroupName(volumeName);
+
+        if (!isIscsiTargetExists(targetName)) {
+            createIscsiTarget(targetName);
+        }
+
+        if (!isIscsiTargetGroupExists(targetGroupName)) {
+            createIscsiTargetGroup(targetGroupName);
+        }
+
+        if (!isTargetMemberOfTargetGroup(targetGroupName, targetName)) {
+            addTargetGroupMember(targetGroupName, targetName);
+        }
+
+        if (!isLuExists(zvolName)) {
+            createLu(zvolName);
+        }
+
+        if (!isLuShared(zvolName)) {
+            addLuMappingEntry(zvolName, targetGroupName);
+        }
+
+        return new NexentaStorZvol(zvolName, targetName);
+    }
+
+    static abstract class NexentaStorVolume {
+        protected String name;
+
+        NexentaStorVolume(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    public static final class NexentaStorZvol extends NexentaStorVolume {
+        protected String iqn;
+
+        public NexentaStorZvol(String name, String iqn) {
+            super(name);
+            this.iqn = iqn;
+        }
+
+        public String getIqn() {
+            return iqn;
+        }
+    }
+
+    public void deleteIscsiVolume(String volumeName) {
+        try {
+            NmsResponse response = client.execute(NmsResponse.class, "zvol", "destroy", volumeName, "");
+        } catch (CloudRuntimeException ex) {
+            if (!ex.getMessage().contains("does not exist")) {
+                throw ex;
+            }
+            logger.debug(String.format(
+                    "Volume %s does not exist, it seems it was already " +
+                            "deleted.", volumeName));
+        }
+    }
+
+    public NexentaStorVolume createVolume(String volumeName, Long volumeSize) {
+        return createIscsiVolume(volumeName, volumeSize);
+    }
+
+    public void deleteVolume(String volumeName) {
+        deleteIscsiVolume(volumeName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaUtil.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaUtil.java b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaUtil.java
new file mode 100644
index 0000000..ee6a78f
--- /dev/null
+++ b/plugins/storage/volume/nexenta/src/org/apache/cloudstack/storage/datastore/util/NexentaUtil.java
@@ -0,0 +1,242 @@
+/*
+ * 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.apache.cloudstack.storage.datastore.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.StringTokenizer;
+
+import com.cloud.storage.Storage;
+
+public class NexentaUtil {
+    public static final String PROVIDER_NAME = "Nexenta";
+
+    public static final String NMS_URL = "nmsUrl";
+    public static final String VOLUME = "volume";
+
+    public static final String STORAGE_HOST = "storageHost";
+    public static final String STORAGE_PORT = "storagePort";
+    public static final String STORAGE_TYPE = "storageType";
+    public static final String STORAGE_PATH = "storagePath";
+
+    public static final String DEFAULT_NMS_USER = "admin";
+    public static final String DEFAULT_NMS_PASSWORD = "nexenta";
+
+    public static final String SPARSE_VOLUMES = "sparseVolumes";
+    public static final String VOLUME_BLOCK_SIZE = "volumeBlockSize";
+
+    public static final int DEFAULT_NMS_PORT = 2000;
+    public static final int DEFAULT_ISCSI_TARGET_PORTAL_PORT = 3260;
+    public static final int DEFAULT_NFS_PORT = 2049;
+
+    public static final String ISCSI_TARGET_NAME_PREFIX = "iqn.1986-03.com.sun:02:cloudstack-";
+    public static final String ISCSI_TARGET_GROUP_PREFIX = "cloudstack/";
+
+    /**
+     * Parse NMS url into normalized parts like scheme, user, host and others.
+     *
+     * Example NMS URL:
+     *    auto://admin:nexenta@192.168.1.1:2000/
+     *
+     * NMS URL parts:
+     *    auto                true if url starts with auto://, protocol will be automatically switched to https if http not supported;
+     *    scheme (auto)       connection protocol (http or https);
+     *    user (admin)        NMS user;
+     *    password (nexenta)  NMS password;
+     *    host (192.168.1.1)  NMS host;
+     *    port (2000)         NMS port.
+     *
+     * @param nmsUrl url string to parse
+     * @return instance of NexentaConnection class
+     */
+    public static NexentaNmsUrl parseNmsUrl(String nmsUrl) {
+        URI uri;
+
+        try {
+            uri = new URI(nmsUrl);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("Invalid URI: " + nmsUrl);
+        }
+
+        boolean isAuto = false;
+        String schema = uri.getScheme();
+        if (schema == null || schema.isEmpty() || "auto".equalsIgnoreCase(schema)) {
+            schema = "http";
+            isAuto = true;
+        }
+
+        String username, password, userInfo = uri.getUserInfo();
+        if (userInfo == null) {
+            username = DEFAULT_NMS_USER;
+            password = DEFAULT_NMS_PASSWORD;
+        } else {
+            if (userInfo.indexOf(':') < 0) {
+                username = userInfo;
+                password = DEFAULT_NMS_PASSWORD;
+            } else {
+                String[] parts = userInfo.split(":", 2);
+                username = parts[0];
+                password = parts[1];
+            }
+        }
+
+        String host = uri.getHost();
+        if (host == null) {
+            throw new IllegalArgumentException(String.format("NMS host required: %s.", nmsUrl));
+        }
+
+        int port = uri.getPort();
+        if (port == -1) {
+            port = DEFAULT_NMS_PORT;
+        }
+
+        return new NexentaNmsUrl(isAuto, schema, username, password, host, port);
+    }
+
+    public static Storage.StoragePoolType getStorageType(String v) {
+        if ("iSCSI".equalsIgnoreCase(v)) {
+            return Storage.StoragePoolType.Iscsi;
+        } else if ("NFS".equalsIgnoreCase(v)) {
+            return Storage.StoragePoolType.NetworkFilesystem;
+        }
+        return Storage.StoragePoolType.Iscsi;
+    }
+
+    public static class NexentaPluginParameters {
+        protected NexentaNmsUrl nmsUrl;
+        protected String volume;
+        protected Storage.StoragePoolType storageType = Storage.StoragePoolType.Iscsi;
+        protected String storageHost;
+        protected Integer storagePort;
+        protected String storagePath;
+        protected Boolean sparseVolumes = false;
+        protected String volumeBlockSize = "8K";
+
+        public void setNmsUrl(String url) {
+            this.nmsUrl = NexentaUtil.parseNmsUrl(url);
+        }
+
+        public NexentaNmsUrl getNmsUrl() {
+            return nmsUrl;
+        }
+
+        public void setVolume(String volume) {
+            if (volume.endsWith("/")) {
+                this.volume = volume.substring(0, volume.length() - 1);
+            } else {
+                this.volume = volume;
+            }
+        }
+
+        public String getVolume() {
+            return volume;
+        }
+
+        public void setStorageType(String storageType) {
+            this.storageType = NexentaUtil.getStorageType(storageType);
+        }
+
+        public Storage.StoragePoolType getStorageType() {
+            return storageType;
+        }
+
+        public void setStorageHost(String host) {
+            this.storageHost = host;
+        }
+
+        public String getStorageHost() {
+            if (storageHost == null && nmsUrl != null) {
+                return nmsUrl.getHost();
+            }
+            return storageHost;
+        }
+
+        public void setStoragePort(String port) {
+            this.storagePort = Integer.parseInt(port);
+        }
+
+        public Integer getStoragePort() {
+            if (storagePort == null && storageType != null) {
+                if (storageType == Storage.StoragePoolType.Iscsi) {
+                    return DEFAULT_ISCSI_TARGET_PORTAL_PORT;
+                } else {
+                    return DEFAULT_NFS_PORT;
+                }
+            }
+            return storagePort;
+        }
+
+        public void setStoragePath(String path) {
+            this.storagePath = path;
+        }
+
+        public String getStoragePath() {
+            return storagePath;
+        }
+
+        public void setSparseVolumes(String sparseVolumes) {
+            this.sparseVolumes = Boolean.TRUE.toString().equalsIgnoreCase(sparseVolumes);
+        }
+
+        public Boolean getSparseVolumes() {
+            return sparseVolumes;
+        }
+
+        public void setVolumeBlockSize(String volumeBlockSize) {
+            this.volumeBlockSize = volumeBlockSize;
+        }
+
+        public String getVolumeBlockSize() {
+            return volumeBlockSize;
+        }
+    }
+
+    public static NexentaPluginParameters parseNexentaPluginUrl(String url) {
+        final String delimiter1 = ";";
+        final String delimiter2 = "=";
+        StringTokenizer st = new StringTokenizer(url, delimiter1);
+        NexentaPluginParameters params = new NexentaPluginParameters();
+        while (st.hasMoreElements()) {
+            String token = st.nextElement().toString();
+            int idx = token.indexOf(delimiter2);
+            if (idx == -1) {
+                throw new RuntimeException("Invalid URL format");
+            }
+            String[] urlKeyAndValue = token.split(delimiter2, 2);
+            if (NMS_URL.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setNmsUrl(urlKeyAndValue[1]);
+            } else if (VOLUME.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setVolume(urlKeyAndValue[1]);
+            } else if (STORAGE_TYPE.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setStorageType(urlKeyAndValue[1]);
+            } else if (STORAGE_HOST.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setStorageHost(urlKeyAndValue[1]);
+            } else if (STORAGE_PORT.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setStoragePort(urlKeyAndValue[1]);
+            } else if (STORAGE_PATH.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setStoragePath(urlKeyAndValue[1]);
+            } else if (SPARSE_VOLUMES.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setSparseVolumes(urlKeyAndValue[1]);
+            } else if (VOLUME_BLOCK_SIZE.equalsIgnoreCase(urlKeyAndValue[0])) {
+                params.setVolumeBlockSize(urlKeyAndValue[1]);
+            }
+        }
+        return params;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java b/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
new file mode 100644
index 0000000..af320be
--- /dev/null
+++ b/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaStorApplianceTest.java
@@ -0,0 +1,319 @@
+package org.apache.cloudstack.storage.datastore.util;
+
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.IscsiTarget;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.ListOfIscsiTargetsNmsResponse;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.CreateIscsiTargetRequestParams;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.ListOfStringsNmsResponse;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.IntegerNmsResponse;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.LuParams;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.MappingEntry;
+import static org.apache.cloudstack.storage.datastore.util.NexentaStorAppliance.AddMappingEntryNmsResponse;
+import static org.apache.cloudstack.storage.datastore.util.NexentaNmsClient.NmsResponse;
+
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NexentaStorApplianceTest {
+    private NexentaNmsClient client;
+
+    private NexentaStorAppliance appliance;
+
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
+    @Before
+    public void init() {
+        final String url = "nmsUrl=https://admin:nexenta@10.1.3.182:8457;volume=cloudstack;storageType=iscsi";
+        NexentaUtil.NexentaPluginParameters parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        //client = new NexentaNmsClient(parameters.getNmsUrl());
+        client = mock(NexentaNmsClient.class);
+        appliance = new NexentaStorAppliance(client, parameters);
+    }
+
+    @Test
+    public void testIsIscsiTargetExists() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+
+        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets")).thenReturn(null);
+        assertFalse(appliance.isIscsiTargetExists(targetName));
+
+        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets")).thenReturn(new ListOfIscsiTargetsNmsResponse());
+        assertFalse(appliance.isIscsiTargetExists(targetName));
+
+        final HashMap<String, IscsiTarget> result = new HashMap<String, IscsiTarget>();
+
+        result.put("any", new IscsiTarget("Online", "iSCSI", "any", "0", "-", "iscsit"));
+        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets")).thenReturn(new ListOfIscsiTargetsNmsResponse(result));
+        assertFalse(appliance.isIscsiTargetExists(targetName));
+
+        result.put(targetName, new IscsiTarget("Online", "iSCSI", targetName, "0", "-", "iscsit"));
+        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets")).thenReturn(new ListOfIscsiTargetsNmsResponse(result));
+        assertTrue(appliance.isIscsiTargetExists(targetName));
+    }
+
+    final static String ISCSI_TARGET_ALREADY_CONFIGURED_ERROR = "Unable to create iscsi target\\n iSCSI target %s already configured\\n itadm create-target failed with error " +
+            "17\\n";
+
+    @Test
+    public void testCreateIscsiTarget() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+        final CreateIscsiTargetRequestParams p = new CreateIscsiTargetRequestParams(targetName);
+
+        appliance.createIscsiTarget(targetName);
+        verify(client).execute(NmsResponse.class, "iscsitarget", "create_target", p);
+
+        final String error = String.format(ISCSI_TARGET_ALREADY_CONFIGURED_ERROR, targetName);
+        when(client.execute(NmsResponse.class, "iscsitarget", "create_target", p)).thenThrow(new CloudRuntimeException(error));
+        appliance.createIscsiTarget(targetName);
+    }
+
+    @Test
+    public void testCreateIscsiTargetFails() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+        final CreateIscsiTargetRequestParams p = new CreateIscsiTargetRequestParams(targetName);
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        when(client.execute(NmsResponse.class, "iscsitarget", "create_target", p)).thenThrow(new CloudRuntimeException("any exception"));
+        appliance.createIscsiTarget(targetName);
+    }
+
+    @Test
+    public void testIsIscsiTargetGroupExists() {
+        final String targetGroup = NexentaStorAppliance.getTargetGroupName("volume1");
+
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(null);
+        assertFalse(appliance.isIscsiTargetGroupExists(targetGroup));
+
+        when(client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(new ListOfIscsiTargetsNmsResponse());
+        assertFalse(appliance.isIscsiTargetGroupExists(targetGroup));
+
+        LinkedList<String> result = new LinkedList<String>();
+
+        result.add("any");
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(new ListOfStringsNmsResponse(result));
+        assertFalse(appliance.isIscsiTargetGroupExists(targetGroup));
+
+        result.add(targetGroup);
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups")).thenReturn(new ListOfStringsNmsResponse(result));
+        assertTrue(appliance.isIscsiTargetGroupExists(targetGroup));
+    }
+
+    final static String ISCSI_TARGET_GROUP_EXISTS_ERROR = "Unable to create targetgroup: stmfadm: %s: already exists\\n";
+
+    @Test
+    public void testCreateIscsiTargetGroup() {
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+
+        appliance.createIscsiTargetGroup(targetGroupName);
+        verify(client).execute(NmsResponse.class, "stmf", "create_targetgroup", targetGroupName);
+
+        final String error = String.format(ISCSI_TARGET_GROUP_EXISTS_ERROR, targetGroupName);
+        when(client.execute(NmsResponse.class, "stmf", "create_targetgroup", targetGroupName)).thenThrow(new CloudRuntimeException(error));
+        appliance.createIscsiTargetGroup(targetGroupName);
+    }
+
+    @Test
+    public void testCreateIscsiTargetGroupFails() {
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+        when(client.execute(NmsResponse.class, "stmf", "create_targetgroup", targetGroupName)).thenThrow(new CloudRuntimeException("any exception"));
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        appliance.createIscsiTargetGroup(targetGroupName);
+    }
+
+    @Test
+    public void testIsMemberOfTargetGroup() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName)).thenReturn(null);
+        assertFalse(appliance.isTargetMemberOfTargetGroup(targetGroupName, targetName));
+
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName)).thenReturn(new ListOfStringsNmsResponse());
+        assertFalse(appliance.isTargetMemberOfTargetGroup(targetGroupName, targetName));
+
+        LinkedList<String> result = new LinkedList<String>();
+
+        result.add("any");
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName)).thenReturn(new ListOfStringsNmsResponse(result));
+        assertFalse(appliance.isTargetMemberOfTargetGroup(targetGroupName, targetName));
+
+        result.add(targetName);
+        when(client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName)).thenReturn(new ListOfStringsNmsResponse(result));
+        assertTrue(appliance.isTargetMemberOfTargetGroup(targetGroupName, targetName));
+    }
+
+    @Test
+    public void testAddTargetGroupMember() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+
+        appliance.addTargetGroupMember(targetGroupName, targetName);
+        verify(client).execute(NmsResponse.class, "stmf", "add_targetgroup_member", targetGroupName, targetName);
+
+        String error = String.format(ISCSI_TARGET_ALREADY_EXISTS_IN_TARGET_GROUP_ERROR, targetName);
+        when(client.execute(NmsResponse.class, "stmf", "add_targetgroup_member", targetGroupName, targetName)).thenThrow(new CloudRuntimeException(error));
+        appliance.addTargetGroupMember(targetGroupName, targetName);
+    }
+
+    final static String ISCSI_TARGET_ALREADY_EXISTS_IN_TARGET_GROUP_ERROR = "Unable to add member to targetgroup: stmfadm: %s: already exists\\n";
+
+    @Test
+    public void testAddTargetGroupMemberFails() {
+        final String targetName = NexentaStorAppliance.getTargetName("volume1");
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+
+        when(client.execute(NmsResponse.class, "stmf", "add_targetgroup_member", targetGroupName, targetName)).thenThrow(new CloudRuntimeException("any exception"));
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        appliance.addTargetGroupMember(targetGroupName, targetName);
+    }
+
+    @Test
+    public void testIsLuExists() {
+        final String volumeName = appliance.getVolumeName("volume1");
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", volumeName)).thenReturn(null);
+        assertFalse(appliance.isLuExists(volumeName));
+
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", volumeName)).thenReturn(new IntegerNmsResponse(0));
+        assertFalse(appliance.isLuExists(volumeName));
+
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", volumeName)).thenReturn(new IntegerNmsResponse(1));
+        assertTrue(appliance.isLuExists(volumeName));
+
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", volumeName)).thenThrow(new CloudRuntimeException("does not exist"));
+        assertFalse(appliance.isLuExists(volumeName));
+    }
+
+    @Test
+    public void testIsLuExistsFails() {
+        final String volumeName = appliance.getVolumeName("volume1");
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", volumeName)).thenThrow(new CloudRuntimeException("any exception"));
+        assertTrue(appliance.isLuExists(volumeName));
+    }
+
+    final static String CREATE_LU_IN_USE_ERROR = "Unable to create lu with " +
+            "zvol '%s':\\n stmfadm: filename /dev/zvol/rdsk/%s: in use\\n";
+
+    @Test
+    public void testCreateLu() {
+        final String luName = appliance.getVolumeName("volume1");
+        final LuParams p = new LuParams();
+
+        appliance.createLu(luName);
+        verify(client).execute(NmsResponse.class, "scsidisk", "create_lu", luName, p);
+
+        String error = String.format(CREATE_LU_IN_USE_ERROR, luName, luName);
+        when(client.execute(NmsResponse.class, "scsidisk", "create_lu", luName, p)).thenThrow(new CloudRuntimeException(error));
+        appliance.createLu(luName);
+    }
+
+    @Test
+    public void testCreateLuFails() {
+        final String luName = appliance.getVolumeName("volume1");
+        when(client.execute(NmsResponse.class, "scsidisk", "create_lu", luName, new LuParams())).thenThrow(new CloudRuntimeException("any exception"));
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        appliance.createLu(luName);
+    }
+
+    final static String ZVOL_DOES_NOT_EXISTS_ERROR = "Zvol '%s' does not exist";
+
+    @Test
+    public void testIsLuShared() {
+        final String luName = appliance.getVolumeName("volume1");
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName)).thenReturn(null);
+        assertFalse(appliance.isLuShared(luName));
+
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName)).thenReturn(new IntegerNmsResponse(0));
+        assertFalse(appliance.isLuShared(luName));
+
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName)).thenReturn(new IntegerNmsResponse(1));
+        assertTrue(appliance.isLuShared(luName));
+
+        final String error = String.format(ZVOL_DOES_NOT_EXISTS_ERROR, luName);
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName)).thenThrow(new CloudRuntimeException(error));
+        assertFalse(appliance.isLuShared(luName));
+    }
+
+    @Test
+    public void testIsLuSharedFails() {
+        final String luName = appliance.getVolumeName("volume1");
+        when(client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName)).thenThrow(new CloudRuntimeException("any exception"));
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        appliance.isLuShared(luName);
+    }
+
+    final static String ADD_LUN_MAPPING_ENTRY_ERROR = "(rc: 256) Unable to " +
+            "add view to zvol '%s':\\n add-view: view already exists\\n";
+
+    @Test
+    public void testAddLuMappingEntry() {
+        final String luName = appliance.getVolumeName("volume1");
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+        final MappingEntry mappingEntry = new MappingEntry(targetGroupName, "0");
+        appliance.addLuMappingEntry(luName, targetGroupName);
+        verify(client).execute(AddMappingEntryNmsResponse.class, "scsidisk", "add_lun_mapping_entry", luName, mappingEntry);
+
+        String error = String.format(ADD_LUN_MAPPING_ENTRY_ERROR, luName);
+        when(client.execute(AddMappingEntryNmsResponse.class, "scsidisk", "add_lun_mapping_entry", luName, mappingEntry)).thenThrow(new CloudRuntimeException(error));
+        appliance.addLuMappingEntry(luName, targetGroupName);
+    }
+
+    @Test
+    public void testAddLuMappingEntryTest() {
+        final String luName = appliance.getVolumeName("volume1");
+        final String targetGroupName = NexentaStorAppliance.getTargetGroupName("volume1");
+        final MappingEntry mappingEntry = new MappingEntry(targetGroupName, "0");
+        when(client.execute(AddMappingEntryNmsResponse.class, "scsidisk", "add_lun_mapping_entry", luName, mappingEntry)).thenThrow(new CloudRuntimeException("any exception"));
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        appliance.addLuMappingEntry(luName, targetGroupName);
+    }
+
+    @Test
+    public void testCreateIscsiVolume() {
+        final String volumeName = "volume1";
+        final Long volumeSize = Long.valueOf(1);
+        appliance.createIscsiVolume(volumeName, volumeSize);
+    }
+
+    @Test
+    public void testDeleteIscsiVolume() {
+        final String volumeName = appliance.getVolumeName("volume1");
+        appliance.deleteIscsiVolume(volumeName);
+        verify(client).execute(NmsResponse.class, "zvol", "destroy", volumeName, "");
+
+        when(client.execute(NmsResponse.class, "zvol", "destroy", volumeName, "")).thenThrow(new CloudRuntimeException(String.format("Zvol '%s' does not exist", volumeName)));
+        appliance.deleteIscsiVolume(volumeName);
+    }
+
+    @Test
+    public void testDeleteIscsiVolumeFails() {
+        final String volumeName = appliance.getVolumeName("volume1");
+        exception.expect(CloudRuntimeException.class);
+        exception.expectMessage("any exception");
+        when(client.execute(NmsResponse.class, "zvol", "destroy", volumeName, "")).thenThrow(new CloudRuntimeException("any exception"));
+        appliance.deleteIscsiVolume(volumeName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/753b5639/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaUtilTest.java
----------------------------------------------------------------------
diff --git a/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaUtilTest.java b/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaUtilTest.java
new file mode 100644
index 0000000..5968266
--- /dev/null
+++ b/plugins/storage/volume/nexenta/test/org/apache/cloudstack/storage/datastore/util/NexentaUtilTest.java
@@ -0,0 +1,117 @@
+package org.apache.cloudstack.storage.datastore.util;
+
+import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import com.cloud.storage.Storage;
+
+@RunWith(JUnit4.class)
+public class NexentaUtilTest {
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
+    @Test
+    public void testParseNmsUrl() {
+        NexentaNmsUrl c;
+
+        c = NexentaUtil.parseNmsUrl("auto://192.168.1.1/");
+        assertEquals(c.toString(), "auto://admin:nexenta@192.168.1.1:2000/rest/nms/");
+        assertEquals(c.getSchema(), "http");
+
+        c = NexentaUtil.parseNmsUrl("http://192.168.1.1/");
+        assertEquals(c.toString(), "http://admin:nexenta@192.168.1.1:2000/rest/nms/");
+
+        c = NexentaUtil.parseNmsUrl("http://192.168.1.1:8080");
+        assertEquals(c.toString(), "http://admin:nexenta@192.168.1.1:8080/rest/nms/");
+
+        c = NexentaUtil.parseNmsUrl("https://root@192.168.1.1:8080");
+        assertEquals(c.toString(), "https://root:nexenta@192.168.1.1:8080/rest/nms/");
+
+        c = NexentaUtil.parseNmsUrl("https://root:password@192.168.1.1:8080");
+        assertEquals(c.toString(), "https://root:password@192.168.1.1:8080/rest/nms/");
+    }
+
+    @Test
+    public void testGetStorageType() {
+        assertEquals(NexentaUtil.getStorageType("iscsi"), Storage.StoragePoolType.Iscsi);
+        assertEquals(NexentaUtil.getStorageType("nfs"), Storage.StoragePoolType.NetworkFilesystem);
+        assertEquals(NexentaUtil.getStorageType("any"), Storage.StoragePoolType.Iscsi);
+    }
+
+    @Test
+    public void testParseNexentaPluginUrl() {
+        String url = "nmsUrl=http://admin:nexenta@192.168.1.1:2000;";
+
+        NexentaUtil.NexentaPluginParameters parameters;
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getNmsUrl().toString(), "http://admin:nexenta@192.168.1.1:2000/rest/nms/");
+        assertNull(parameters.getVolume());
+        assertEquals(parameters.getStorageType(), Storage.StoragePoolType.Iscsi);
+        assertEquals(parameters.getStorageHost(), "192.168.1.1");
+        assertEquals((int) parameters.getStoragePort(), NexentaUtil.DEFAULT_ISCSI_TARGET_PORTAL_PORT);
+        assertNull(parameters.getStoragePath());
+        assertEquals((boolean) parameters.getSparseVolumes(), false);
+        assertEquals(parameters.getVolumeBlockSize(), "8K");
+
+        url += "volume=cloudstack";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getNmsUrl().toString(), "http://admin:nexenta@192.168.1.1:2000/rest/nms/");
+        assertEquals(parameters.getVolume(), "cloudstack");
+
+        url += "/;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getVolume(), "cloudstack");
+
+        url += "storageType=";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url + "nfs");
+        assertEquals(parameters.getStorageType(), Storage.StoragePoolType.NetworkFilesystem);
+        assertEquals((int) parameters.getStoragePort(), NexentaUtil.DEFAULT_NFS_PORT);
+
+        parameters = NexentaUtil.parseNexentaPluginUrl(url + "iscsi");
+        assertEquals(parameters.getStorageType(), Storage.StoragePoolType.Iscsi);
+        assertEquals((int) parameters.getStoragePort(), NexentaUtil.DEFAULT_ISCSI_TARGET_PORTAL_PORT);
+
+        url += "nfs;storageHost=192.168.1.2;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getStorageHost(), "192.168.1.2");
+
+        url += "storagePort=3000;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals((int) parameters.getStoragePort(), 3000);
+
+        url += "storagePath=/volumes/cloudstack;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getStoragePath(), "/volumes/cloudstack");
+
+        url += "sparseVolumes=true;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getSparseVolumes(), Boolean.TRUE);
+
+        url += "volumeBlockSize=128K;";
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+        assertEquals(parameters.getVolumeBlockSize(), "128K");
+
+        url += "unknownParameter=value;";  // NOTE: exception should not be raised
+        parameters = NexentaUtil.parseNexentaPluginUrl(url);
+
+        assertEquals(parameters.getNmsUrl().toString(), "http://admin:nexenta@192.168.1.1:2000/rest/nms/");
+        assertEquals(parameters.getVolume(), "cloudstack");
+        assertEquals(parameters.getStorageType(), Storage.StoragePoolType.NetworkFilesystem);
+        assertEquals(parameters.getStorageHost(), "192.168.1.2");
+        assertEquals((int) parameters.getStoragePort(), 3000);
+        assertEquals(parameters.getStoragePath(), "/volumes/cloudstack");
+        assertEquals(parameters.getSparseVolumes(), Boolean.TRUE);
+        assertEquals(parameters.getVolumeBlockSize(), "128K");
+
+        exception.expect(RuntimeException.class);
+        exception.expectMessage("Invalid URL format");
+
+        NexentaUtil.parseNexentaPluginUrl(url + "invalidParameter");
+    }
+}