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");
+ }
+}