mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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:
parent
cc2b1c4961
commit
753b563947
@ -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>
|
||||
|
||||
47
plugins/storage/volume/nexenta/pom.xml
Normal file
47
plugins/storage/volume/nexenta/pom.xml
Normal 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>
|
||||
@ -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
|
||||
@ -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>
|
||||
@ -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) {}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user