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 <sudison@gmail.com>
This commit is contained in:
Victor Rodionov 2014-01-15 15:12:02 -08:00 committed by Edison Su
parent cc2b1c4961
commit 753b563947
14 changed files with 1925 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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) {}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

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