mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Extend support of CloudStack-managed storage to KVM
This commit is contained in:
parent
11441a3672
commit
858ce76665
@ -18,21 +18,35 @@
|
||||
*/
|
||||
package com.cloud.agent.api.to;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.storage.Volume;
|
||||
|
||||
public class DiskTO {
|
||||
public static final String CHAP_INITIATOR_USERNAME = "chapInitiatorUsername";
|
||||
public static final String CHAP_INITIATOR_SECRET = "chapInitiatorSecret";
|
||||
public static final String CHAP_TARGET_USERNAME = "chapTargetUsername";
|
||||
public static final String CHAP_TARGET_SECRET = "chapTargetSecret";
|
||||
public static final String MANAGED = "managed";
|
||||
public static final String IQN = "iqn";
|
||||
public static final String STORAGE_HOST = "storageHost";
|
||||
public static final String STORAGE_PORT = "storagePort";
|
||||
public static final String VOLUME_SIZE = "volumeSize";
|
||||
|
||||
private DataTO data;
|
||||
private Long diskSeq;
|
||||
private String vdiUuid;
|
||||
private String path;
|
||||
private Volume.Type type;
|
||||
private Map<String, String> _details;
|
||||
|
||||
public DiskTO() {
|
||||
|
||||
}
|
||||
|
||||
public DiskTO(DataTO data, Long diskSeq, String vdiUuid, Volume.Type type) {
|
||||
public DiskTO(DataTO data, Long diskSeq, String path, Volume.Type type) {
|
||||
this.data = data;
|
||||
this.diskSeq = diskSeq;
|
||||
this.vdiUuid = vdiUuid;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@ -52,12 +66,12 @@ public class DiskTO {
|
||||
this.diskSeq = diskSeq;
|
||||
}
|
||||
|
||||
public String getVdiUuid() {
|
||||
return vdiUuid;
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setVdiUuid(String vdiUuid) {
|
||||
this.vdiUuid = vdiUuid;
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Volume.Type getType() {
|
||||
@ -67,4 +81,12 @@ public class DiskTO {
|
||||
public void setType(Volume.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
_details = details;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return _details;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,26 +16,33 @@
|
||||
// under the License.
|
||||
package com.cloud.agent.api;
|
||||
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
|
||||
public class MigrateCommand extends Command {
|
||||
String vmName;
|
||||
String destIp;
|
||||
String hostGuid;
|
||||
boolean isWindows;
|
||||
|
||||
VirtualMachineTO vmTO;
|
||||
|
||||
protected MigrateCommand() {
|
||||
}
|
||||
|
||||
public MigrateCommand(String vmName, String destIp, boolean isWindows) {
|
||||
public MigrateCommand(String vmName, String destIp, boolean isWindows, VirtualMachineTO vmTO) {
|
||||
this.vmName = vmName;
|
||||
this.destIp = destIp;
|
||||
this.isWindows = isWindows;
|
||||
this.vmTO = vmTO;
|
||||
}
|
||||
|
||||
public boolean isWindows() {
|
||||
return isWindows;
|
||||
}
|
||||
|
||||
public VirtualMachineTO getVirtualMachine() {
|
||||
return vmTO;
|
||||
}
|
||||
|
||||
public String getDestinationIp() {
|
||||
return destIp;
|
||||
}
|
||||
|
||||
@ -16,11 +16,14 @@
|
||||
// under the License.
|
||||
package com.cloud.agent.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
|
||||
public class StartAnswer extends Answer {
|
||||
VirtualMachineTO vm;
|
||||
String host_guid;
|
||||
Map<String, String> _iqnToPath;
|
||||
|
||||
protected StartAnswer() {
|
||||
}
|
||||
@ -54,4 +57,12 @@ public class StartAnswer extends Answer {
|
||||
public String getHost_guid() {
|
||||
return host_guid;
|
||||
}
|
||||
|
||||
public void setIqnToPath(Map<String, String> iqnToPath) {
|
||||
_iqnToPath = iqnToPath;
|
||||
}
|
||||
|
||||
public Map<String, String> getIqnToPath() {
|
||||
return _iqnToPath;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,14 +24,6 @@ import com.cloud.agent.api.to.DiskTO;
|
||||
public final class AttachCommand extends Command implements StorageSubSystemCommand {
|
||||
private DiskTO disk;
|
||||
private String vmName;
|
||||
private String _storageHost;
|
||||
private int _storagePort;
|
||||
private boolean _managed;
|
||||
private String _iScsiName;
|
||||
private String _chapInitiatorUsername;
|
||||
private String _chapInitiatorPassword;
|
||||
private String _chapTargetUsername;
|
||||
private String _chapTargetPassword;
|
||||
|
||||
public AttachCommand(DiskTO disk, String vmName) {
|
||||
super();
|
||||
@ -59,68 +51,4 @@ public final class AttachCommand extends Command implements StorageSubSystemComm
|
||||
public void setVmName(String vmName) {
|
||||
this.vmName = vmName;
|
||||
}
|
||||
|
||||
public void setStorageHost(String storageHost) {
|
||||
_storageHost = storageHost;
|
||||
}
|
||||
|
||||
public String getStorageHost() {
|
||||
return _storageHost;
|
||||
}
|
||||
|
||||
public void setStoragePort(int storagePort) {
|
||||
_storagePort = storagePort;
|
||||
}
|
||||
|
||||
public int getStoragePort() {
|
||||
return _storagePort;
|
||||
}
|
||||
|
||||
public void setManaged(boolean managed) {
|
||||
_managed = managed;
|
||||
}
|
||||
|
||||
public boolean isManaged() {
|
||||
return _managed;
|
||||
}
|
||||
|
||||
public void set_iScsiName(String iScsiName) {
|
||||
this._iScsiName = iScsiName;
|
||||
}
|
||||
|
||||
public String get_iScsiName() {
|
||||
return _iScsiName;
|
||||
}
|
||||
|
||||
public void setChapInitiatorUsername(String chapInitiatorUsername) {
|
||||
_chapInitiatorUsername = chapInitiatorUsername;
|
||||
}
|
||||
|
||||
public String getChapInitiatorUsername() {
|
||||
return _chapInitiatorUsername;
|
||||
}
|
||||
|
||||
public void setChapInitiatorPassword(String chapInitiatorPassword) {
|
||||
_chapInitiatorPassword = chapInitiatorPassword;
|
||||
}
|
||||
|
||||
public String getChapInitiatorPassword() {
|
||||
return _chapInitiatorPassword;
|
||||
}
|
||||
|
||||
public void setChapTargetUsername(String chapTargetUsername) {
|
||||
_chapTargetUsername = chapTargetUsername;
|
||||
}
|
||||
|
||||
public String getChapTargetUsername() {
|
||||
return _chapTargetUsername;
|
||||
}
|
||||
|
||||
public void setChapTargetPassword(String chapTargetPassword) {
|
||||
_chapTargetPassword = chapTargetPassword;
|
||||
}
|
||||
|
||||
public String getChapTargetPassword() {
|
||||
return _chapTargetPassword;
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,6 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.Listener;
|
||||
@ -844,6 +843,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
|
||||
VirtualMachineTO vmTO = hvGuru.implement(vmProfile);
|
||||
|
||||
handlePath(vmTO.getDisks(), vm.getHypervisorType());
|
||||
|
||||
cmds = new Commands(Command.OnError.Stop);
|
||||
cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence()));
|
||||
|
||||
@ -862,6 +863,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
|
||||
startAnswer = cmds.getAnswer(StartAnswer.class);
|
||||
if (startAnswer != null && startAnswer.getResult()) {
|
||||
handlePath(vmTO.getDisks(), startAnswer.getIqnToPath());
|
||||
String host_guid = startAnswer.getHost_guid();
|
||||
if (host_guid != null) {
|
||||
HostVO finalHost = _resourceMgr.findHostByGuid(host_guid);
|
||||
@ -975,14 +977,63 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
}
|
||||
}
|
||||
|
||||
// for managed storage on KVM, need to make sure the path field of the volume in question is populated with the IQN
|
||||
private void handlePath(DiskTO[] disks, HypervisorType hypervisorType) {
|
||||
if (hypervisorType != HypervisorType.KVM) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disks != null) {
|
||||
for (DiskTO disk : disks) {
|
||||
Map<String, String> details = disk.getDetails();
|
||||
boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED));
|
||||
|
||||
if (isManaged && disk.getPath() == null) {
|
||||
Long volumeId = disk.getData().getId();
|
||||
VolumeVO volume = _volsDao.findById(volumeId);
|
||||
|
||||
disk.setPath(volume.get_iScsiName());
|
||||
volume.setPath(volume.get_iScsiName());
|
||||
|
||||
_volsDao.update(volumeId, volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for managed storage on XenServer and VMware, need to update the DB with a path if the VDI/VMDK file was newly created
|
||||
private void handlePath(DiskTO[] disks, Map<String, String> iqnToPath) {
|
||||
if (disks != null) {
|
||||
for (DiskTO disk : disks) {
|
||||
Map<String, String> details = disk.getDetails();
|
||||
boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED));
|
||||
|
||||
if (isManaged && disk.getPath() == null) {
|
||||
Long volumeId = disk.getData().getId();
|
||||
VolumeVO volume = _volsDao.findById(volumeId);
|
||||
String iScsiName = volume.get_iScsiName();
|
||||
String path = iqnToPath.get(iScsiName);
|
||||
|
||||
volume.setPath(path);
|
||||
|
||||
_volsDao.update(volumeId, volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDiskChainChange(StartAnswer answer) {
|
||||
VirtualMachineTO vmSpec = answer.getVirtualMachine();
|
||||
|
||||
for(DiskTO disk : vmSpec.getDisks()) {
|
||||
if(disk.getType() != Volume.Type.ISO) {
|
||||
VolumeObjectTO vol = (VolumeObjectTO)disk.getData();
|
||||
VolumeVO volume = _volsDao.findById(vol.getId());
|
||||
|
||||
volumeMgr.updateVolumeDiskChain(vol.getId(), vol.getPath(), vol.getChainInfo());
|
||||
// Use getPath() from VolumeVO to get a fresh copy of what's in the DB.
|
||||
// Before doing this, in a certain situation, getPath() from VolumeObjectTO
|
||||
// returned null instead of an actual path (because it was out of date with the DB).
|
||||
volumeMgr.updateVolumeDiskChain(vol.getId(), volume.getPath(), vol.getChainInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1523,7 +1574,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
boolean migrated = false;
|
||||
try {
|
||||
boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
|
||||
MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows);
|
||||
MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows, to);
|
||||
mc.setHostGuid(dest.getHost().getGuid());
|
||||
|
||||
try {
|
||||
@ -3150,7 +3201,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
boolean migrated = false;
|
||||
try {
|
||||
boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
|
||||
MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows);
|
||||
MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows, to);
|
||||
mc.setHostGuid(dest.getHost().getGuid());
|
||||
|
||||
try {
|
||||
|
||||
@ -32,6 +32,7 @@ import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
|
||||
@ -886,7 +887,12 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
|
||||
for (VolumeVO vol : vols) {
|
||||
DataTO volTO = volFactory.getVolume(vol.getId()).getTO();
|
||||
DiskTO disk = new DiskTO(volTO, vol.getDeviceId(), null, vol.getVolumeType());
|
||||
DiskTO disk = new DiskTO(volTO, vol.getDeviceId(), vol.getPath(), vol.getVolumeType());
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(vol.getId());
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(vol.getPoolId(), DataStoreRole.Primary);
|
||||
|
||||
disk.setDetails(getDetails(volumeInfo, dataStore));
|
||||
|
||||
vm.addDisk(disk);
|
||||
}
|
||||
|
||||
@ -897,6 +903,29 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getDetails(VolumeInfo volumeInfo, DataStore dataStore) {
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
|
||||
StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
|
||||
|
||||
details.put(DiskTO.MANAGED, String.valueOf(storagePool.isManaged()));
|
||||
details.put(DiskTO.STORAGE_HOST, storagePool.getHostAddress());
|
||||
details.put(DiskTO.STORAGE_PORT, String.valueOf(storagePool.getPort()));
|
||||
details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeInfo.getSize()));
|
||||
details.put(DiskTO.IQN, volumeInfo.get_iScsiName());
|
||||
|
||||
ChapInfo chapInfo = volService.getChapInfo(volumeInfo, dataStore);
|
||||
|
||||
if (chapInfo != null) {
|
||||
details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
|
||||
details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
|
||||
details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
|
||||
details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
private static enum VolumeTaskType {
|
||||
RECREATE, NOP, MIGRATE
|
||||
}
|
||||
@ -1084,7 +1113,12 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
vol = result.first();
|
||||
}
|
||||
DataTO volumeTO = volFactory.getVolume(vol.getId()).getTO();
|
||||
DiskTO disk = new DiskTO(volumeTO, vol.getDeviceId(), null, vol.getVolumeType());
|
||||
DiskTO disk = new DiskTO(volumeTO, vol.getDeviceId(), vol.getPath(), vol.getVolumeType());
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(vol.getId());
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(vol.getPoolId(), DataStoreRole.Primary);
|
||||
|
||||
disk.setDetails(getDetails(volumeInfo, dataStore));
|
||||
|
||||
vm.addDisk(disk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1203,8 +1203,6 @@ ServerResource {
|
||||
return execute((AttachIsoCommand) cmd);
|
||||
} else if (cmd instanceof AttachVolumeCommand) {
|
||||
return execute((AttachVolumeCommand) cmd);
|
||||
} else if (cmd instanceof StopCommand) {
|
||||
return execute((StopCommand) cmd);
|
||||
} else if (cmd instanceof CheckConsoleProxyLoadCommand) {
|
||||
return execute((CheckConsoleProxyLoadCommand) cmd);
|
||||
} else if (cmd instanceof WatchConsoleProxyLoadCommand) {
|
||||
@ -2905,6 +2903,8 @@ ServerResource {
|
||||
*/
|
||||
destDomain = dm.migrate(dconn, (1 << 0) | (1 << 3), xmlDesc, vmName, "tcp:"
|
||||
+ cmd.getDestinationIp(), _migrateSpeed);
|
||||
|
||||
_storagePoolMgr.disconnectPhysicalDisksViaVmSpec(cmd.getVirtualMachine());
|
||||
} catch (LibvirtException e) {
|
||||
s_logger.debug("Can't migrate domain: " + e.getMessage());
|
||||
result = e.getMessage();
|
||||
@ -2953,6 +2953,9 @@ ServerResource {
|
||||
}
|
||||
|
||||
NicTO[] nics = vm.getNics();
|
||||
|
||||
boolean success = false;
|
||||
|
||||
try {
|
||||
Connect conn = LibvirtConnection.getConnectionByVmName(vm.getName());
|
||||
for (NicTO nic : nics) {
|
||||
@ -2967,10 +2970,14 @@ ServerResource {
|
||||
}
|
||||
}
|
||||
|
||||
_storagePoolMgr.connectPhysicalDisksViaVmSpec(vm);
|
||||
|
||||
synchronized (_vms) {
|
||||
_vms.put(vm.getName(), State.Migrating);
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
return new PrepareForMigrationAnswer(cmd);
|
||||
} catch (LibvirtException e) {
|
||||
return new PrepareForMigrationAnswer(cmd, e.toString());
|
||||
@ -2978,6 +2985,10 @@ ServerResource {
|
||||
return new PrepareForMigrationAnswer(cmd, e.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
return new PrepareForMigrationAnswer(cmd, e.toString());
|
||||
} finally {
|
||||
if (!success) {
|
||||
_storagePoolMgr.disconnectPhysicalDisksViaVmSpec(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3241,10 +3252,7 @@ ServerResource {
|
||||
String result = stopVM(conn, vmName);
|
||||
if (result == null) {
|
||||
for (DiskDef disk : disks) {
|
||||
if (disk.getDeviceType() == DiskDef.deviceType.CDROM
|
||||
&& disk.getDiskPath() != null) {
|
||||
cleanupDisk(conn, disk);
|
||||
}
|
||||
cleanupDisk(disk);
|
||||
}
|
||||
for (InterfaceDef iface: ifaces) {
|
||||
// We don't know which "traffic type" is associated with
|
||||
@ -3517,6 +3525,8 @@ ServerResource {
|
||||
|
||||
createVbd(conn, vmSpec, vmName, vm);
|
||||
|
||||
_storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec);
|
||||
|
||||
createVifs(vmSpec, vm);
|
||||
|
||||
s_logger.debug("starting " + vmName + ": " + vm.toString());
|
||||
@ -3597,6 +3607,9 @@ ServerResource {
|
||||
_vms.remove(vmName);
|
||||
}
|
||||
}
|
||||
if (state != State.Running) {
|
||||
_storagePoolMgr.disconnectPhysicalDisksViaVmSpec(vmSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3678,7 +3691,7 @@ ServerResource {
|
||||
disk.defNetworkBasedDisk(physicalDisk.getPath().replace("rbd:", ""), pool.getSourceHost(), pool.getSourcePort(),
|
||||
pool.getAuthUserName(), pool.getUuid(),
|
||||
devId, diskBusType, diskProtocol.RBD);
|
||||
} else if (pool.getType() == StoragePoolType.CLVM) {
|
||||
} else if (pool.getType() == StoragePoolType.CLVM || physicalDisk.getFormat() == PhysicalDiskFormat.RAW) {
|
||||
disk.defBlockBasedDisk(physicalDisk.getPath(), devId,
|
||||
diskBusType);
|
||||
} else {
|
||||
@ -3762,38 +3775,20 @@ ServerResource {
|
||||
return new CheckSshAnswer(cmd);
|
||||
}
|
||||
|
||||
public boolean cleanupDisk(Connect conn, DiskDef disk) {
|
||||
// need to umount secondary storage
|
||||
public boolean cleanupDisk(DiskDef disk) {
|
||||
String path = disk.getDiskPath();
|
||||
String poolUuid = null;
|
||||
if (path.endsWith("systemvm.iso")) {
|
||||
//Don't need to clean up system vm iso, as it's stored in local
|
||||
return true;
|
||||
}
|
||||
if (path != null) {
|
||||
String[] token = path.split("/");
|
||||
if (token.length > 3) {
|
||||
poolUuid = token[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (poolUuid == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// we use libvirt as storage adaptor since we passed a libvirt
|
||||
// connection to cleanupDisk. We pass a storage type that maps
|
||||
// to libvirt adaptor.
|
||||
KVMStoragePool pool = _storagePoolMgr.getStoragePool(
|
||||
StoragePoolType.Filesystem, poolUuid);
|
||||
if (pool != null) {
|
||||
_storagePoolMgr.deleteStoragePool(pool.getType(),pool.getUuid());
|
||||
}
|
||||
return true;
|
||||
} catch (CloudRuntimeException e) {
|
||||
if (path == null) {
|
||||
s_logger.debug("Unable to clean up disk with null path (perhaps empty cdrom drive):" + disk);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.endsWith("systemvm.iso")) {
|
||||
// don't need to clean up system vm ISO as it's stored in local
|
||||
return true;
|
||||
}
|
||||
|
||||
return _storagePoolMgr.disconnectPhysicalDiskByPath(path);
|
||||
}
|
||||
|
||||
protected synchronized String attachOrDetachISO(Connect conn,
|
||||
@ -3823,7 +3818,7 @@ ServerResource {
|
||||
if (result == null && !isAttach) {
|
||||
for (DiskDef disk : disks) {
|
||||
if (disk.getDeviceType() == DiskDef.deviceType.CDROM) {
|
||||
cleanupDisk(conn, disk);
|
||||
cleanupDisk(disk);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,19 +17,23 @@
|
||||
package com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
|
||||
public interface KVMStoragePool {
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name,
|
||||
PhysicalDiskFormat format, long size);
|
||||
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, long size);
|
||||
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, long size);
|
||||
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, long size);
|
||||
|
||||
public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details);
|
||||
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid);
|
||||
|
||||
public boolean deletePhysicalDisk(String uuid);
|
||||
public boolean disconnectPhysicalDisk(String volumeUuid);
|
||||
|
||||
public boolean deletePhysicalDisk(String volumeUuid);
|
||||
|
||||
public List<KVMPhysicalDisk> listPhysicalDisks();
|
||||
|
||||
|
||||
@ -18,6 +18,8 @@ package com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.HashMap;
|
||||
@ -25,14 +27,21 @@ import java.util.UUID;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
|
||||
import com.cloud.hypervisor.kvm.resource.KVMHABase;
|
||||
import com.cloud.hypervisor.kvm.resource.KVMHABase.PoolType;
|
||||
import com.cloud.hypervisor.kvm.resource.KVMHAMonitor;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.StorageLayer;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
||||
public class KVMStoragePoolManager {
|
||||
private static final Logger s_logger = Logger
|
||||
.getLogger(KVMStoragePoolManager.class);
|
||||
@ -62,7 +71,6 @@ public class KVMStoragePoolManager {
|
||||
this.poolType = poolType;
|
||||
}
|
||||
}
|
||||
private StorageAdaptor _storageAdaptor;
|
||||
private KVMHAMonitor _haMonitor;
|
||||
private final Map<String, StoragePoolInformation> _storagePools = new ConcurrentHashMap<String, StoragePoolInformation>();
|
||||
private final Map<String, StorageAdaptor> _storageMapper = new HashMap<String, StorageAdaptor>();
|
||||
@ -89,11 +97,102 @@ public class KVMStoragePoolManager {
|
||||
}
|
||||
|
||||
public KVMStoragePoolManager(StorageLayer storagelayer, KVMHAMonitor monitor) {
|
||||
this._storageAdaptor = new LibvirtStorageAdaptor(storagelayer);
|
||||
this._haMonitor = monitor;
|
||||
this._storageMapper.put("libvirt", new LibvirtStorageAdaptor(storagelayer));
|
||||
// add other storage adaptors here
|
||||
// this._storageMapper.put("newadaptor", new NewStorageAdaptor(storagelayer));
|
||||
// this._storageMapper.put("newadaptor", new NewStorageAdaptor(storagelayer));
|
||||
this._storageMapper.put(StoragePoolType.Iscsi.toString(), new iScsiAdmStorageAdaptor());
|
||||
}
|
||||
|
||||
public boolean connectPhysicalDisk(StoragePoolType type, String poolUuid, String volPath, Map<String, String> details) {
|
||||
StorageAdaptor adaptor = getStorageAdaptor(type);
|
||||
KVMStoragePool pool = adaptor.getStoragePool(poolUuid);
|
||||
|
||||
return adaptor.connectPhysicalDisk(volPath, pool, details);
|
||||
}
|
||||
|
||||
public boolean connectPhysicalDisksViaVmSpec(VirtualMachineTO vmSpec) {
|
||||
boolean result = false;
|
||||
|
||||
final String vmName = vmSpec.getName();
|
||||
|
||||
List<DiskTO> disks = Arrays.asList(vmSpec.getDisks());
|
||||
|
||||
for (DiskTO disk : disks) {
|
||||
if (disk.getType() != Volume.Type.ISO) {
|
||||
VolumeObjectTO vol = (VolumeObjectTO) disk.getData();
|
||||
PrimaryDataStoreTO store = (PrimaryDataStoreTO) vol.getDataStore();
|
||||
KVMStoragePool pool = getStoragePool(store.getPoolType(), store.getUuid());
|
||||
|
||||
StorageAdaptor adaptor = getStorageAdaptor(pool.getType());
|
||||
|
||||
result = adaptor.connectPhysicalDisk(vol.getPath(), pool, disk.getDetails());
|
||||
|
||||
if (!result) {
|
||||
s_logger.error("Failed to connect disks via vm spec for vm: " + vmName + " volume:" + vol.toString());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean disconnectPhysicalDiskByPath(String path) {
|
||||
for (Map.Entry<String, StorageAdaptor> set : _storageMapper.entrySet()) {
|
||||
StorageAdaptor adaptor = set.getValue();
|
||||
|
||||
if (adaptor.disconnectPhysicalDiskByPath(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean disconnectPhysicalDisksViaVmSpec(VirtualMachineTO vmSpec) {
|
||||
if (vmSpec == null) {
|
||||
/* CloudStack often tries to stop VMs that shouldn't be running, to ensure a known state,
|
||||
for example if we lose communication with the agent and the VM is brought up elsewhere.
|
||||
We may not know about these yet. This might mean that we can't use the vmspec map, because
|
||||
when we restart the agent we lose all of the info about running VMs. */
|
||||
|
||||
s_logger.debug("disconnectPhysicalDiskViaVmSpec: Attempted to stop a VM that is not yet in our hash map");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean result = true;
|
||||
|
||||
final String vmName = vmSpec.getName();
|
||||
|
||||
List<DiskTO> disks = Arrays.asList(vmSpec.getDisks());
|
||||
|
||||
for (DiskTO disk : disks) {
|
||||
if (disk.getType() != Volume.Type.ISO) {
|
||||
s_logger.debug("Disconnecting disk " + disk.getPath());
|
||||
|
||||
VolumeObjectTO vol = (VolumeObjectTO) disk.getData();
|
||||
PrimaryDataStoreTO store = (PrimaryDataStoreTO) vol.getDataStore();
|
||||
|
||||
KVMStoragePool pool = getStoragePool(store.getPoolType(), store.getUuid());
|
||||
|
||||
StorageAdaptor adaptor = getStorageAdaptor(pool.getType());
|
||||
|
||||
// if a disk fails to disconnect, still try to disconnect remaining
|
||||
|
||||
boolean subResult = adaptor.disconnectPhysicalDisk(vol.getPath(), pool);
|
||||
|
||||
if (!subResult) {
|
||||
s_logger.error("Failed to disconnect disks via vm spec for vm: " + vmName + " volume:" + vol.toString());
|
||||
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public KVMStoragePool getStoragePool(StoragePoolType type, String uuid) {
|
||||
@ -197,6 +296,13 @@ public class KVMStoragePoolManager {
|
||||
return pool;
|
||||
}
|
||||
|
||||
public boolean disconnectPhysicalDisk(StoragePoolType type, String poolUuid, String volPath) {
|
||||
StorageAdaptor adaptor = getStorageAdaptor(type);
|
||||
KVMStoragePool pool = adaptor.getStoragePool(poolUuid);
|
||||
|
||||
return adaptor.disconnectPhysicalDisk(volPath, pool);
|
||||
}
|
||||
|
||||
public boolean deleteStoragePool(StoragePoolType type, String uuid) {
|
||||
StorageAdaptor adaptor = getStorageAdaptor(type);
|
||||
_haMonitor.removeStoragePool(uuid);
|
||||
|
||||
@ -828,7 +828,7 @@ public class KVMStorageProcessor implements StorageProcessor {
|
||||
if (result == null && !isAttach) {
|
||||
for (DiskDef disk : disks) {
|
||||
if (disk.getDeviceType() == DiskDef.deviceType.CDROM) {
|
||||
this.resource.cleanupDisk(conn, disk);
|
||||
this.resource.cleanupDisk(disk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -972,6 +972,9 @@ public class KVMStorageProcessor implements StorageProcessor {
|
||||
String vmName = cmd.getVmName();
|
||||
try {
|
||||
Connect conn = LibvirtConnection.getConnectionByVmName(vmName);
|
||||
|
||||
storagePoolMgr.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), vol.getPath(), disk.getDetails());
|
||||
|
||||
KVMPhysicalDisk phyDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), vol.getPath());
|
||||
|
||||
attachOrDetachDisk(conn, true, vmName, phyDisk, disk.getDiskSeq().intValue());
|
||||
@ -994,10 +997,13 @@ public class KVMStorageProcessor implements StorageProcessor {
|
||||
String vmName = cmd.getVmName();
|
||||
try {
|
||||
Connect conn = LibvirtConnection.getConnectionByVmName(vmName);
|
||||
|
||||
KVMPhysicalDisk phyDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), vol.getPath());
|
||||
|
||||
attachOrDetachDisk(conn, false, vmName, phyDisk, disk.getDiskSeq().intValue());
|
||||
|
||||
storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), vol.getPath());
|
||||
|
||||
return new DettachAnswer(disk);
|
||||
} catch (LibvirtException e) {
|
||||
s_logger.debug("Failed to attach volume: " + vol.getPath() + ", due to " + e.toString());
|
||||
|
||||
@ -706,6 +706,53 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
||||
return disk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String name, KVMStoragePool pool, Map<String, String> details) {
|
||||
// this is for managed storage that needs to prep disks prior to use
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String uuid, KVMStoragePool pool) {
|
||||
// this is for managed storage that needs to cleanup disks after use
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDiskByPath(String localPath) {
|
||||
// we've only ever cleaned up ISOs that are NFS mounted
|
||||
|
||||
String poolUuid = null;
|
||||
|
||||
if (localPath != null && localPath.startsWith(_mountPoint)) {
|
||||
String[] token = localPath.split("/");
|
||||
|
||||
if (token.length > 3) {
|
||||
poolUuid = token[2];
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (poolUuid == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Connect conn = LibvirtConnection.getConnection();
|
||||
|
||||
StoragePool pool = conn.storagePoolLookupByUUIDString(poolUuid);
|
||||
|
||||
deleteStoragePool(poolUuid);
|
||||
|
||||
return true;
|
||||
} catch (LibvirtException ex) {
|
||||
return false;
|
||||
} catch (CloudRuntimeException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String uuid, KVMStoragePool pool) {
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ package com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
@ -153,6 +154,16 @@ public class LibvirtStoragePool implements KVMStoragePool {
|
||||
return disk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String name, Map<String, String> details) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String uuid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String uuid) {
|
||||
return this._storageAdaptor.deletePhysicalDisk(uuid, this);
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
@ -25,6 +26,8 @@ public interface StorageAdaptor {
|
||||
|
||||
public KVMStoragePool getStoragePool(String uuid);
|
||||
|
||||
// given disk path (per database) and pool, create new KVMPhysicalDisk, populate
|
||||
// it with info from local disk, and return it
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid,
|
||||
KVMStoragePool pool);
|
||||
|
||||
@ -36,6 +39,16 @@ public interface StorageAdaptor {
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
|
||||
PhysicalDiskFormat format, long size);
|
||||
|
||||
// given disk path (per database) and pool, prepare disk on host
|
||||
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details);
|
||||
|
||||
// given disk path (per database) and pool, clean up disk on host
|
||||
public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool);
|
||||
|
||||
// given local path to file/device (per Libvirt XML), 1) check that device is
|
||||
// handled by your adaptor, return false if not. 2) clean up device, return true
|
||||
public boolean disconnectPhysicalDiskByPath(String localPath);
|
||||
|
||||
public boolean deletePhysicalDisk(String uuid, KVMStoragePool pool);
|
||||
|
||||
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template,
|
||||
|
||||
@ -0,0 +1,373 @@
|
||||
// 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 com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.OutputInterpreter;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
public class iScsiAdmStorageAdaptor implements StorageAdaptor {
|
||||
private static final Logger s_logger = Logger.getLogger(iScsiAdmStorageAdaptor.class);
|
||||
|
||||
private static final Map<String, KVMStoragePool> _mapStorageUuidToStoragePool = new HashMap<String, KVMStoragePool>();
|
||||
|
||||
@Override
|
||||
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, StoragePoolType storagePoolType) {
|
||||
iScsiAdmStoragePool storagePool = new iScsiAdmStoragePool(uuid, host, port, storagePoolType, this);
|
||||
|
||||
_mapStorageUuidToStoragePool.put(uuid, storagePool);
|
||||
|
||||
return storagePool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMStoragePool getStoragePool(String uuid) {
|
||||
return _mapStorageUuidToStoragePool.get(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteStoragePool(String uuid) {
|
||||
return _mapStorageUuidToStoragePool.remove(uuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteStoragePool(KVMStoragePool pool) {
|
||||
return deleteStoragePool(pool.getUuid());
|
||||
}
|
||||
|
||||
// called from LibvirtComputingResource.execute(CreateCommand)
|
||||
// does not apply for iScsiAdmStorageAdaptor
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, KVMStoragePool pool, PhysicalDiskFormat format, long size) {
|
||||
throw new UnsupportedOperationException("Creating a physical disk is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 -o new
|
||||
Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("-m", "node");
|
||||
iScsiAdmCmd.add("-T", getIqn(volumeUuid));
|
||||
iScsiAdmCmd.add("-p", pool.getSourceHost() + ":" + pool.getSourcePort());
|
||||
iScsiAdmCmd.add("-o", "new");
|
||||
|
||||
String result = iScsiAdmCmd.execute();
|
||||
|
||||
if (result != null) {
|
||||
s_logger.debug("Failed to add iSCSI target " + volumeUuid);
|
||||
System.out.println("Failed to add iSCSI target " + volumeUuid);
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
s_logger.debug("Successfully added iSCSI target " + volumeUuid);
|
||||
System.out.println("Successfully added to iSCSI target " + volumeUuid);
|
||||
}
|
||||
|
||||
String chapInitiatorUsername = details.get(DiskTO.CHAP_INITIATOR_USERNAME);
|
||||
String chapInitiatorSecret = details.get(DiskTO.CHAP_INITIATOR_SECRET);
|
||||
|
||||
if (StringUtils.isNotBlank(chapInitiatorUsername) && StringUtils.isNotBlank(chapInitiatorSecret)) {
|
||||
try {
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --op update -n node.session.auth.authmethod -v CHAP
|
||||
executeChapCommand(volumeUuid, pool, "node.session.auth.authmethod", "CHAP", null);
|
||||
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --op update -n node.session.auth.username -v username
|
||||
executeChapCommand(volumeUuid, pool, "node.session.auth.username", chapInitiatorUsername, "username");
|
||||
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 --op update -n node.session.auth.password -v password
|
||||
executeChapCommand(volumeUuid, pool, "node.session.auth.password", chapInitiatorSecret, "password");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10 --login
|
||||
iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("-m", "node");
|
||||
iScsiAdmCmd.add("-T", getIqn(volumeUuid));
|
||||
iScsiAdmCmd.add("-p", pool.getSourceHost() + ":" + pool.getSourcePort());
|
||||
iScsiAdmCmd.add("--login");
|
||||
|
||||
result = iScsiAdmCmd.execute();
|
||||
|
||||
if (result != null) {
|
||||
s_logger.debug("Failed to log in to iSCSI target " + volumeUuid);
|
||||
System.out.println("Failed to log in to iSCSI target " + volumeUuid);
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
s_logger.debug("Successfully logged in to iSCSI target " + volumeUuid);
|
||||
System.out.println("Successfully logged in to iSCSI target " + volumeUuid);
|
||||
}
|
||||
|
||||
// There appears to be a race condition where logging in to the iSCSI volume via iscsiadm
|
||||
// returns success before the device has been added to the OS.
|
||||
// What happens is you get logged in and the device shows up, but the device may not
|
||||
// show up before we invoke Libvirt to attach the device to a VM.
|
||||
// waitForDiskToBecomeAvailable(String, KVMStoragePool) invokes blockdev
|
||||
// via getPhysicalDisk(String, KVMStoragePool) and checks if the size came back greater
|
||||
// than 0.
|
||||
// After a certain number of tries and a certain waiting period in between tries,
|
||||
// this method could still return (it should not block indefinitely) (the race condition
|
||||
// isn't solved here, but made highly unlikely to be a problem).
|
||||
waitForDiskToBecomeAvailable(volumeUuid, pool);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void waitForDiskToBecomeAvailable(String volumeUuid, KVMStoragePool pool) {
|
||||
int numberOfTries = 10;
|
||||
int timeBetweenTries = 1000;
|
||||
|
||||
while (getPhysicalDisk(volumeUuid, pool).getSize() == 0 && numberOfTries > 0) {
|
||||
numberOfTries--;
|
||||
|
||||
try {
|
||||
Thread.sleep(timeBetweenTries);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// don't do anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeChapCommand(String path, KVMStoragePool pool, String nParameter, String vParameter, String detail) throws Exception {
|
||||
Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("-m", "node");
|
||||
iScsiAdmCmd.add("-T", getIqn(path));
|
||||
iScsiAdmCmd.add("-p", pool.getSourceHost() + ":" + pool.getSourcePort());
|
||||
iScsiAdmCmd.add("--op", "update");
|
||||
iScsiAdmCmd.add("-n", nParameter);
|
||||
iScsiAdmCmd.add("-v", vParameter);
|
||||
|
||||
String result = iScsiAdmCmd.execute();
|
||||
|
||||
boolean useDetail = detail != null && detail.trim().length() > 0;
|
||||
|
||||
detail = useDetail ? detail.trim() + " " : detail;
|
||||
|
||||
if (result != null) {
|
||||
s_logger.debug("Failed to execute CHAP " + (useDetail ? detail : "") + "command for iSCSI target " + path + " : message = " + result);
|
||||
System.out.println("Failed to execute CHAP " + (useDetail ? detail : "") + "command for iSCSI target " + path + " : message = " + result);
|
||||
|
||||
throw new Exception("Failed to execute CHAP " + (useDetail ? detail : "") + "command for iSCSI target " + path + " : message = " + result);
|
||||
} else {
|
||||
s_logger.debug("CHAP " + (useDetail ? detail : "") + "command executed successfully for iSCSI target " + path);
|
||||
System.out.println("CHAP " + (useDetail ? detail : "") + "command executed successfully for iSCSI target " + path);
|
||||
}
|
||||
}
|
||||
|
||||
// example by-path: /dev/disk/by-path/ip-192.168.233.10:3260-iscsi-iqn.2012-03.com.solidfire:storagepool2-lun-0
|
||||
private String getByPath(String host, String path) {
|
||||
return "/dev/disk/by-path/ip-" + host + "-iscsi-" + getIqn(path) + "-lun-" + getLun(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
|
||||
String deviceByPath = getByPath(pool.getSourceHost() + ":" + pool.getSourcePort(), volumeUuid);
|
||||
KVMPhysicalDisk physicalDisk = new KVMPhysicalDisk(deviceByPath, volumeUuid, pool);
|
||||
|
||||
physicalDisk.setFormat(PhysicalDiskFormat.RAW);
|
||||
|
||||
long deviceSize = getDeviceSize(deviceByPath);
|
||||
|
||||
physicalDisk.setSize(deviceSize);
|
||||
physicalDisk.setVirtualSize(deviceSize);
|
||||
|
||||
return physicalDisk;
|
||||
}
|
||||
|
||||
private long getDeviceSize(String deviceByPath) {
|
||||
Script iScsiAdmCmd = new Script(true, "blockdev", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("--getsize64", deviceByPath);
|
||||
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
|
||||
String result = iScsiAdmCmd.execute(parser);
|
||||
|
||||
if (result != null) {
|
||||
s_logger.warn("Unable to retrieve the size of device " + deviceByPath);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Long.parseLong(parser.getLine());
|
||||
}
|
||||
|
||||
private static String getIqn(String path) {
|
||||
return getComponent(path, 1);
|
||||
}
|
||||
|
||||
private static String getLun(String path) {
|
||||
return getComponent(path, 2);
|
||||
}
|
||||
|
||||
private static String getComponent(String path, int index) {
|
||||
String[] tmp = path.split("/");
|
||||
|
||||
if (tmp.length != 3) {
|
||||
String msg = "Wrong format for iScsi path: " + path + ". It should be formatted as '/targetIQN/LUN'.";
|
||||
|
||||
s_logger.warn(msg);
|
||||
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
return tmp[index].trim();
|
||||
}
|
||||
|
||||
public boolean disconnectPhysicalDisk(String host, int port, String iqn, String lun) {
|
||||
// use iscsiadm to log out of the iSCSI target and un-discover it
|
||||
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10 --logout
|
||||
Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("-m", "node");
|
||||
iScsiAdmCmd.add("-T", iqn);
|
||||
iScsiAdmCmd.add("-p", host + ":" + port);
|
||||
iScsiAdmCmd.add("--logout");
|
||||
|
||||
String result = iScsiAdmCmd.execute();
|
||||
|
||||
if (result != null) {
|
||||
s_logger.debug("Failed to log out of iSCSI target /" + iqn + "/" + lun + " : message = " + result);
|
||||
System.out.println("Failed to log out of iSCSI target /" + iqn + "/" + lun + " : message = " + result);
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
s_logger.debug("Successfully logged out of iSCSI target /" + iqn + "/" + lun);
|
||||
System.out.println("Successfully logged out of iSCSI target /" + iqn + "/" + lun);
|
||||
}
|
||||
|
||||
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 -o delete
|
||||
iScsiAdmCmd = new Script(true, "iscsiadm", 0, s_logger);
|
||||
|
||||
iScsiAdmCmd.add("-m", "node");
|
||||
iScsiAdmCmd.add("-T", iqn);
|
||||
iScsiAdmCmd.add("-p", host + ":" + port);
|
||||
iScsiAdmCmd.add("-o", "delete");
|
||||
|
||||
result = iScsiAdmCmd.execute();
|
||||
|
||||
if (result != null) {
|
||||
s_logger.debug("Failed to remove iSCSI target /" + iqn + "/" + lun + " : message = " + result);
|
||||
System.out.println("Failed to remove iSCSI target /" + iqn + "/" + lun + " : message = " + result);
|
||||
|
||||
return false;
|
||||
} else {
|
||||
s_logger.debug("Removed iSCSI target /" + iqn + "/" + lun);
|
||||
System.out.println("Removed iSCSI target /" + iqn + "/" + lun);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
|
||||
return disconnectPhysicalDisk(pool.getSourceHost(), pool.getSourcePort(), getIqn(volumeUuid), getLun(volumeUuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDiskByPath(String localPath) {
|
||||
String search1 = "/dev/disk/by-path/ip-";
|
||||
String search2 = ":";
|
||||
String search3 = "-iscsi-";
|
||||
String search4 = "-lun-";
|
||||
|
||||
if (localPath.indexOf(search3) == -1) {
|
||||
// this volume doesn't below to this adaptor, so just return true
|
||||
return true;
|
||||
}
|
||||
|
||||
int index = localPath.indexOf(search2);
|
||||
|
||||
String host = localPath.substring(search1.length(), index);
|
||||
|
||||
int index2 = localPath.indexOf(search3);
|
||||
|
||||
String port = localPath.substring(index + search2.length(), index2);
|
||||
|
||||
index = localPath.indexOf(search4);
|
||||
|
||||
String iqn = localPath.substring(index2 + search3.length(), index);
|
||||
|
||||
String lun = localPath.substring(index + search4.length());
|
||||
|
||||
return disconnectPhysicalDisk(host, Integer.parseInt(port), iqn, lun);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String volumeUuid, KVMStoragePool pool) {
|
||||
throw new UnsupportedOperationException("Deleting a physical disk is not supported.");
|
||||
}
|
||||
|
||||
// does not apply for iScsiAdmStorageAdaptor
|
||||
@Override
|
||||
public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) {
|
||||
throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format, long size,
|
||||
KVMStoragePool destPool, int timeout) {
|
||||
throw new UnsupportedOperationException("Creating a disk from a template is not yet supported for this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createTemplateFromDisk(KVMPhysicalDisk disk, String name, PhysicalDiskFormat format,
|
||||
long size, KVMStoragePool destPool) {
|
||||
throw new UnsupportedOperationException("Creating a template from a disk is not yet supported for this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
|
||||
throw new UnsupportedOperationException("Copying a disk is not supported in this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
|
||||
throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refresh(KVMStoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(String uuid, String path) {
|
||||
throw new UnsupportedOperationException("A folder cannot be created in this configuration.");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
// 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 com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
|
||||
public class iScsiAdmStoragePool implements KVMStoragePool {
|
||||
private String _uuid;
|
||||
private String _sourceHost;
|
||||
private int _sourcePort;
|
||||
private StoragePoolType _storagePoolType;
|
||||
private StorageAdaptor _storageAdaptor;
|
||||
private String _authUsername;
|
||||
private String _authSecret;
|
||||
private String _sourceDir;
|
||||
private String _localPath;
|
||||
|
||||
public iScsiAdmStoragePool(String uuid, String host, int port, StoragePoolType storagePoolType, StorageAdaptor storageAdaptor) {
|
||||
_uuid = uuid;
|
||||
_sourceHost = host;
|
||||
_sourcePort = port;
|
||||
_storagePoolType = storagePoolType;
|
||||
_storageAdaptor = storageAdaptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return _uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceHost() {
|
||||
return _sourceHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSourcePort() {
|
||||
return _sourcePort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCapacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsed() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAvailable() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoragePoolType getType() {
|
||||
return _storagePoolType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhysicalDiskFormat getDefaultFormat() {
|
||||
return PhysicalDiskFormat.RAW;
|
||||
}
|
||||
|
||||
// called from LibvirtComputingResource.copyPhysicalDisk(KVMPhysicalDisk, String, KVMStoragePool) and
|
||||
// from LibvirtComputingResource.createDiskFromTemplate(KVMPhysicalDisk, String, PhysicalDiskFormat, long, KVMStoragePool)
|
||||
// does not apply for iScsiAdmStoragePool
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, PhysicalDiskFormat format, long size) {
|
||||
throw new UnsupportedOperationException("Creating a physical disk is not supported.");
|
||||
}
|
||||
|
||||
// called from LibvirtComputingResource.execute(CreateCommand) and
|
||||
// from KVMStorageProcessor.createVolume(CreateObjectCommand)
|
||||
// does not apply for iScsiAdmStoragePool
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, long size) {
|
||||
throw new UnsupportedOperationException("Creating a physical disk is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String name, Map<String, String> details) {
|
||||
return this._storageAdaptor.connectPhysicalDisk(name, this, details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid) {
|
||||
return this._storageAdaptor.getPhysicalDisk(volumeUuid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String volumeUuid) {
|
||||
return this._storageAdaptor.disconnectPhysicalDisk(volumeUuid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String volumeUuid) {
|
||||
return this._storageAdaptor.deletePhysicalDisk(volumeUuid, this);
|
||||
}
|
||||
|
||||
// does not apply for iScsiAdmStoragePool
|
||||
@Override
|
||||
public List<KVMPhysicalDisk> listPhysicalDisks() {
|
||||
return this._storageAdaptor.listPhysicalDisks(_uuid, this);
|
||||
}
|
||||
|
||||
// does not apply for iScsiAdmStoragePool
|
||||
@Override
|
||||
public boolean refresh() {
|
||||
return this._storageAdaptor.refresh(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return this._storageAdaptor.deleteStoragePool(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(String path) {
|
||||
return this._storageAdaptor.createFolder(_uuid, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExternalSnapshot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthUserName() {
|
||||
return _authUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthSecret() {
|
||||
return _authSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceDir() {
|
||||
return _sourceDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalPath() {
|
||||
return _localPath;
|
||||
}
|
||||
}
|
||||
@ -1164,19 +1164,16 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
||||
|
||||
@Override
|
||||
public Answer attachVolume(AttachCommand cmd) {
|
||||
return this.attachVolume(cmd, cmd.getDisk(), true, cmd.isManaged(), cmd.getVmName(), cmd.get_iScsiName(),
|
||||
cmd.getStorageHost(), cmd.getStoragePort(), cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword(),
|
||||
cmd.getChapTargetUsername(), cmd.getChapTargetPassword());
|
||||
Map<String, String> details = cmd.getDisk().getDetails();
|
||||
boolean isManaged = Boolean.parseBoolean(details.get(DiskTO.MANAGED));
|
||||
String iScsiName = details.get(DiskTO.IQN);
|
||||
String storageHost = details.get(DiskTO.STORAGE_HOST);
|
||||
int storagePort = Integer.parseInt(details.get(DiskTO.STORAGE_PORT));
|
||||
|
||||
return this.attachVolume(cmd, cmd.getDisk(), true, isManaged, cmd.getVmName(), iScsiName, storageHost, storagePort);
|
||||
}
|
||||
|
||||
private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean isManaged, String vmName, String iScsiName, String storageHost, int storagePort) {
|
||||
return attachVolume(cmd, disk, isAttach, isManaged, vmName, iScsiName, storageHost, storagePort, null, null, null, null);
|
||||
}
|
||||
|
||||
private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean isManaged, String vmName,
|
||||
String iScsiName, String storageHost, int storagePort, String initiatorUsername, String initiatorPassword,
|
||||
String targetUsername, String targetPassword) {
|
||||
|
||||
VolumeObjectTO volumeTO = (VolumeObjectTO)disk.getData();
|
||||
PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)volumeTO.getDataStore();
|
||||
try {
|
||||
@ -1191,8 +1188,11 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
||||
ManagedObjectReference morDs = null;
|
||||
|
||||
if (isAttach && isManaged) {
|
||||
Map<String, String> details = disk.getDetails();
|
||||
|
||||
morDs = hostService.getVmfsDatastore(hyperHost, VmwareResource.getDatastoreName(iScsiName), storageHost, storagePort,
|
||||
VmwareResource.trimIqn(iScsiName), initiatorUsername, initiatorPassword, targetUsername, targetPassword);
|
||||
VmwareResource.trimIqn(iScsiName), details.get(DiskTO.CHAP_INITIATOR_USERNAME), details.get(DiskTO.CHAP_INITIATOR_SECRET),
|
||||
details.get(DiskTO.CHAP_TARGET_USERNAME), details.get(DiskTO.CHAP_TARGET_SECRET));
|
||||
|
||||
DatastoreMO dsMo = new DatastoreMO(hostService.getServiceContext(null), morDs);
|
||||
|
||||
@ -1236,7 +1236,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
disk.setVdiUuid(datastoreVolumePath);
|
||||
disk.setPath(datastoreVolumePath);
|
||||
|
||||
AttachAnswer answer = new AttachAnswer(disk);
|
||||
|
||||
|
||||
@ -1199,10 +1199,13 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
}
|
||||
}
|
||||
|
||||
protected VBD createVbd(Connection conn, DiskTO volume, String vmName, VM vm, BootloaderType bootLoaderType) throws XmlRpcException, XenAPIException {
|
||||
protected VBD createVbd(Connection conn, DiskTO volume, String vmName, VM vm, BootloaderType bootLoaderType, VDI vdi) throws XmlRpcException, XenAPIException {
|
||||
Volume.Type type = volume.getType();
|
||||
|
||||
VDI vdi = mount(conn, vmName, volume);
|
||||
if (vdi == null) {
|
||||
vdi = mount(conn, vmName, volume);
|
||||
}
|
||||
|
||||
if ( vdi != null ) {
|
||||
Map<String, String> smConfig = vdi.getSmConfig(conn);
|
||||
for (String key : smConfig.keySet()) {
|
||||
@ -1608,6 +1611,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
String vmName = vmSpec.getName();
|
||||
State state = State.Stopped;
|
||||
VM vm = null;
|
||||
// if a VDI is created, record its UUID to send back to the CS MS
|
||||
Map<String, String> iqnToPath = new HashMap<String, String>();
|
||||
try {
|
||||
Set<VM> vms = VM.getByNameLabel(conn, vmName);
|
||||
if ( vms != null ) {
|
||||
@ -1636,7 +1641,37 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
vm = createVmFromTemplate(conn, vmSpec, host);
|
||||
|
||||
for (DiskTO disk : vmSpec.getDisks()) {
|
||||
createVbd(conn, disk, vmName, vm, vmSpec.getBootloader());
|
||||
VDI vdi = null;
|
||||
|
||||
if (disk.getData() instanceof VolumeObjectTO) {
|
||||
Map<String, String> details = disk.getDetails();
|
||||
boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED));
|
||||
|
||||
if (isManaged) {
|
||||
String iScsiName = details.get(DiskTO.IQN);
|
||||
String storageHost = details.get(DiskTO.STORAGE_HOST);
|
||||
String chapInitiatorUsername = disk.getDetails().get(DiskTO.CHAP_INITIATOR_USERNAME);
|
||||
String chapInitiatorSecret = disk.getDetails().get(DiskTO.CHAP_INITIATOR_SECRET);
|
||||
Long volumeSize = Long.parseLong(details.get(DiskTO.VOLUME_SIZE));
|
||||
String vdiNameLabel = vmName + "-DATA";
|
||||
|
||||
SR sr = getIscsiSR(conn, iScsiName, storageHost, iScsiName,
|
||||
chapInitiatorUsername, chapInitiatorSecret, true);
|
||||
|
||||
vdi = getVDIbyUuid(conn, disk.getPath(), false);
|
||||
|
||||
if (vdi == null) {
|
||||
vdi = createVdi(sr, vdiNameLabel, volumeSize);
|
||||
|
||||
iqnToPath.put(iScsiName, vdi.getUuid(conn));
|
||||
}
|
||||
else {
|
||||
vdi.setNameLabel(conn, vdiNameLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createVbd(conn, disk, vmName, vm, vmSpec.getBootloader(), vdi);
|
||||
}
|
||||
|
||||
if (vmSpec.getType() != VirtualMachine.Type.User) {
|
||||
@ -1718,11 +1753,21 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
}
|
||||
|
||||
state = State.Running;
|
||||
return new StartAnswer(cmd);
|
||||
|
||||
StartAnswer startAnswer = new StartAnswer(cmd);
|
||||
|
||||
startAnswer.setIqnToPath(iqnToPath);
|
||||
|
||||
return startAnswer;
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Catch Exception: " + e.getClass().toString() + " due to " + e.toString(), e);
|
||||
String msg = handleVmStartFailure(conn, vmName, vm, "", e);
|
||||
return new StartAnswer(cmd, msg);
|
||||
|
||||
StartAnswer startAnswer = new StartAnswer(cmd, msg);
|
||||
|
||||
startAnswer.setIqnToPath(iqnToPath);
|
||||
|
||||
return startAnswer;
|
||||
} finally {
|
||||
synchronized (_cluster.intern()) {
|
||||
if (state != State.Stopped) {
|
||||
@ -1733,6 +1778,46 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
s_logger.debug("The VM is in stopped state, detected problem during startup : " + vmName);
|
||||
}
|
||||
}
|
||||
|
||||
if (state != State.Running) {
|
||||
disconnectManagedVolumes(conn, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnectManagedVolumes(Connection conn, VM vm) {
|
||||
try {
|
||||
Set<VBD> vbds = vm.getVBDs(conn);
|
||||
|
||||
for (VBD vbd : vbds) {
|
||||
VDI vdi = vbd.getVDI(conn);
|
||||
SR sr = null;
|
||||
|
||||
try {
|
||||
sr = vdi.getSR(conn);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sr.getNameLabel(conn).startsWith("/iqn.")) {
|
||||
VBD.Record vbdr = vbd.getRecord(conn);
|
||||
|
||||
if (vbdr.currentlyAttached) {
|
||||
vbd.unplug(conn);
|
||||
}
|
||||
|
||||
vbd.destroy(conn);
|
||||
|
||||
vdi.setNameLabel(conn, "detached");
|
||||
|
||||
umount(conn, vdi);
|
||||
|
||||
handleSrAndVdiDetach(sr.getNameLabel(conn));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
s_logger.debug(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3976,6 +4061,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
|
||||
try {
|
||||
if (vm.getPowerState(conn) == VmPowerState.HALTED) {
|
||||
disconnectManagedVolumes(conn, vm);
|
||||
|
||||
Map<String, String> platform = vm.getPlatform(conn);
|
||||
Integer timeoffset = null;
|
||||
try {
|
||||
@ -5385,7 +5472,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
if (pool.getType() == StoragePoolType.NetworkFilesystem) {
|
||||
getNfsSR(conn, pool);
|
||||
} else if (pool.getType() == StoragePoolType.IscsiLUN) {
|
||||
getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null);
|
||||
getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, false);
|
||||
} else if (pool.getType() == StoragePoolType.PreSetup) {
|
||||
} else {
|
||||
return new Answer(cmd, false, "The pool type: " + pool.getType().name() + " is not supported.");
|
||||
@ -6224,7 +6311,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
}
|
||||
|
||||
protected SR getIscsiSR(Connection conn, String srNameLabel, String target, String path,
|
||||
String chapInitiatorUsername, String chapInitiatorPassword) {
|
||||
String chapInitiatorUsername, String chapInitiatorPassword, boolean ignoreIntroduceException) {
|
||||
synchronized (srNameLabel.intern()) {
|
||||
Map<String, String> deviceConfig = new HashMap<String, String>();
|
||||
try {
|
||||
@ -6273,8 +6360,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
deviceConfig.put("target", target);
|
||||
deviceConfig.put("targetIQN", targetiqn);
|
||||
|
||||
if (StringUtils.isNotBlank(chapInitiatorUsername) &&
|
||||
StringUtils.isNotBlank(chapInitiatorPassword)) {
|
||||
if (StringUtils.isNotBlank(chapInitiatorUsername) && StringUtils.isNotBlank(chapInitiatorPassword)) {
|
||||
deviceConfig.put("chapuser", chapInitiatorUsername);
|
||||
deviceConfig.put("chappassword", chapInitiatorPassword);
|
||||
}
|
||||
@ -6329,8 +6415,17 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
sr = SR.create(conn, host, deviceConfig, new Long(0), srNameLabel, srNameLabel, type, "user", true,
|
||||
smConfig);
|
||||
} else {
|
||||
sr = SR.introduce(conn, pooluuid, srNameLabel, srNameLabel,
|
||||
type, "user", true, smConfig);
|
||||
try {
|
||||
sr = SR.introduce(conn, pooluuid, srNameLabel, srNameLabel,
|
||||
type, "user", true, smConfig);
|
||||
}
|
||||
catch (XenAPIException ex) {
|
||||
if (ignoreIntroduceException) {
|
||||
return sr;
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
Set<Host> setHosts = Host.getAll(conn);
|
||||
|
||||
@ -6557,6 +6652,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
Connection conn = getConnection();
|
||||
boolean attach = cmd.getAttach();
|
||||
String vmName = cmd.getVmName();
|
||||
String vdiNameLabel = vmName + "-DATA";
|
||||
Long deviceId = cmd.getDeviceId();
|
||||
|
||||
String errorMsg;
|
||||
@ -6571,12 +6667,12 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
|
||||
if (cmd.getAttach() && cmd.isManaged()) {
|
||||
SR sr = getIscsiSR(conn, cmd.get_iScsiName(), cmd.getStorageHost(), cmd.get_iScsiName(),
|
||||
cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword());
|
||||
cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword(), true);
|
||||
|
||||
vdi = getVDIbyUuid(conn, cmd.getVolumePath(), false);
|
||||
|
||||
if (vdi == null) {
|
||||
vdi = createVdi(sr, cmd.get_iScsiName(), cmd.getVolumeSize());
|
||||
vdi = createVdi(sr, vdiNameLabel, cmd.getVolumeSize());
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -6632,7 +6728,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
vbd.plug(conn);
|
||||
|
||||
// Update the VDI's label to include the VM name
|
||||
vdi.setNameLabel(conn, vmName + "-DATA");
|
||||
vdi.setNameLabel(conn, vdiNameLabel);
|
||||
|
||||
return new AttachVolumeAnswer(cmd, Long.parseLong(diskNumber), vdi.getUuid(conn));
|
||||
} else {
|
||||
|
||||
@ -162,6 +162,7 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
||||
@Override
|
||||
public AttachAnswer attachVolume(AttachCommand cmd) {
|
||||
String vmName = cmd.getVmName();
|
||||
String vdiNameLabel = vmName + "-DATA";
|
||||
DiskTO disk = cmd.getDisk();
|
||||
DataTO data = disk.getData();
|
||||
|
||||
@ -170,16 +171,23 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
||||
|
||||
VDI vdi = null;
|
||||
|
||||
if (cmd.isManaged()) {
|
||||
SR sr = this.hypervisorResource.getIscsiSR(conn, cmd.get_iScsiName(), cmd.getStorageHost(), cmd.get_iScsiName(),
|
||||
cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword());
|
||||
Map<String, String> details = cmd.getDisk().getDetails();
|
||||
boolean isManaged = Boolean.parseBoolean(details.get(DiskTO.MANAGED));
|
||||
|
||||
if (isManaged) {
|
||||
String iScsiName = details.get(DiskTO.IQN);
|
||||
String storageHost = details.get(DiskTO.STORAGE_HOST);
|
||||
String chapInitiatorUsername = disk.getDetails().get(DiskTO.CHAP_INITIATOR_USERNAME);
|
||||
String chapInitiatorSecret = disk.getDetails().get(DiskTO.CHAP_INITIATOR_SECRET);
|
||||
Long volumeSize = Long.parseLong(details.get(DiskTO.VOLUME_SIZE));
|
||||
|
||||
SR sr = this.hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName,
|
||||
chapInitiatorUsername, chapInitiatorSecret, true);
|
||||
|
||||
vdi = this.hypervisorResource.getVDIbyUuid(conn, data.getPath(), false);
|
||||
|
||||
if (vdi == null) {
|
||||
VolumeObjectTO volume = (VolumeObjectTO)data;
|
||||
|
||||
vdi = this.hypervisorResource.createVdi(sr, cmd.get_iScsiName(), volume.getSize());
|
||||
vdi = this.hypervisorResource.createVdi(sr, vdiNameLabel, volumeSize);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -236,7 +244,7 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
||||
vbd.plug(conn);
|
||||
|
||||
// Update the VDI's label to include the VM name
|
||||
vdi.setNameLabel(conn, vmName + "-DATA");
|
||||
vdi.setNameLabel(conn, vdiNameLabel);
|
||||
DiskTO newDisk = new DiskTO(disk.getData(), Long.parseLong(diskNumber), vdi.getUuid(conn), disk.getType());
|
||||
return new AttachAnswer(newDisk);
|
||||
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.cloudstack.storage.datastore.lifecycle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@ -33,54 +35,62 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.util.SolidFireUtil;
|
||||
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.dc.DataCenterVO;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.agent.api.StoragePoolInfo;
|
||||
import com.cloud.resource.ResourceManager;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.StoragePoolAutomation;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle {
|
||||
@Inject PrimaryDataStoreDao storagePoolDao;
|
||||
@Inject PrimaryDataStoreHelper dataStoreHelper;
|
||||
@Inject StoragePoolAutomation storagePoolAutomation;
|
||||
@Inject StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
@Inject DataCenterDao zoneDao;
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(SolidFirePrimaryDataStoreLifeCycle.class);
|
||||
|
||||
@Inject private DataCenterDao zoneDao;
|
||||
@Inject private PrimaryDataStoreDao storagePoolDao;
|
||||
@Inject private PrimaryDataStoreHelper dataStoreHelper;
|
||||
@Inject private ResourceManager _resourceMgr;
|
||||
@Inject StorageManager _storageMgr;
|
||||
@Inject private StoragePoolAutomation storagePoolAutomation;
|
||||
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
|
||||
private static final int DEFAULT_MANAGEMENT_PORT = 443;
|
||||
private static final int DEFAULT_STORAGE_PORT = 3260;
|
||||
|
||||
|
||||
// invoked to add primary storage that is based on the SolidFire plug-in
|
||||
@Override
|
||||
public DataStore initialize(Map<String, Object> dsInfos) {
|
||||
String url = (String)dsInfos.get("url");
|
||||
Long zoneId = (Long)dsInfos.get("zoneId");
|
||||
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");
|
||||
|
||||
String storageVip = getStorageVip(url);
|
||||
int storagePort = getStoragePort(url);
|
||||
|
||||
DataCenterVO zone = zoneDao.findById(zoneId);
|
||||
|
||||
String uuid = SolidFireUtil.PROVIDER_NAME + "_" + zone.getUuid() + "_" + storageVip;
|
||||
|
||||
String storageVip = getStorageVip(url);
|
||||
int storagePort = getStoragePort(url);
|
||||
|
||||
DataCenterVO zone = zoneDao.findById(zoneId);
|
||||
|
||||
String uuid = SolidFireUtil.PROVIDER_NAME + "_" + zone.getUuid() + "_" + storageVip;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
|
||||
if (capacityIops == null || capacityIops <= 0) {
|
||||
throw new IllegalArgumentException("'capacityIops' must be present and greater than 0.");
|
||||
}
|
||||
|
||||
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
|
||||
|
||||
|
||||
parameters.setHost(storageVip);
|
||||
parameters.setPort(storagePort);
|
||||
parameters.setPath(getModifiedUrl(url));
|
||||
@ -96,16 +106,16 @@ public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeC
|
||||
parameters.setHypervisorType(HypervisorType.Any);
|
||||
parameters.setTags(tags);
|
||||
parameters.setDetails(details);
|
||||
|
||||
|
||||
String managementVip = getManagementVip(url);
|
||||
int managementPort = getManagementPort(url);
|
||||
|
||||
|
||||
details.put(SolidFireUtil.MANAGEMENT_VIP, managementVip);
|
||||
details.put(SolidFireUtil.MANAGEMENT_PORT, String.valueOf(managementPort));
|
||||
|
||||
|
||||
String clusterAdminUsername = getValue(SolidFireUtil.CLUSTER_ADMIN_USERNAME, url);
|
||||
String clusterAdminPassword = getValue(SolidFireUtil.CLUSTER_ADMIN_PASSWORD, url);
|
||||
|
||||
|
||||
details.put(SolidFireUtil.CLUSTER_ADMIN_USERNAME, clusterAdminUsername);
|
||||
details.put(SolidFireUtil.CLUSTER_ADMIN_PASSWORD, clusterAdminPassword);
|
||||
|
||||
@ -171,144 +181,158 @@ public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeC
|
||||
// this adds a row in the cloud.storage_pool table for this SolidFire cluster
|
||||
return dataStoreHelper.createPrimaryDataStore(parameters);
|
||||
}
|
||||
|
||||
|
||||
// remove the clusterAdmin and password key/value pairs
|
||||
private String getModifiedUrl(String originalUrl)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String delimiter = ";";
|
||||
|
||||
StringTokenizer st = new StringTokenizer(originalUrl, delimiter);
|
||||
|
||||
while (st.hasMoreElements()) {
|
||||
String token = st.nextElement().toString().toUpperCase();
|
||||
|
||||
if (token.startsWith(SolidFireUtil.MANAGEMENT_VIP.toUpperCase()) ||
|
||||
token.startsWith(SolidFireUtil.STORAGE_VIP.toUpperCase())) {
|
||||
sb.append(token).append(delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
String modifiedUrl = sb.toString();
|
||||
int lastIndexOf = modifiedUrl.lastIndexOf(delimiter);
|
||||
|
||||
if (lastIndexOf == (modifiedUrl.length() - delimiter.length())) {
|
||||
return modifiedUrl.substring(0, lastIndexOf);
|
||||
}
|
||||
|
||||
return modifiedUrl;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String delimiter = ";";
|
||||
|
||||
StringTokenizer st = new StringTokenizer(originalUrl, delimiter);
|
||||
|
||||
while (st.hasMoreElements()) {
|
||||
String token = st.nextElement().toString().toUpperCase();
|
||||
|
||||
if (token.startsWith(SolidFireUtil.MANAGEMENT_VIP.toUpperCase()) ||
|
||||
token.startsWith(SolidFireUtil.STORAGE_VIP.toUpperCase())) {
|
||||
sb.append(token).append(delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
String modifiedUrl = sb.toString();
|
||||
int lastIndexOf = modifiedUrl.lastIndexOf(delimiter);
|
||||
|
||||
if (lastIndexOf == (modifiedUrl.length() - delimiter.length())) {
|
||||
return modifiedUrl.substring(0, lastIndexOf);
|
||||
}
|
||||
|
||||
return modifiedUrl;
|
||||
}
|
||||
|
||||
|
||||
private String getManagementVip(String url)
|
||||
{
|
||||
return getVip(SolidFireUtil.MANAGEMENT_VIP, url);
|
||||
}
|
||||
|
||||
|
||||
private String getStorageVip(String url)
|
||||
{
|
||||
return getVip(SolidFireUtil.STORAGE_VIP, url);
|
||||
}
|
||||
|
||||
|
||||
private int getManagementPort(String url)
|
||||
{
|
||||
return getPort(SolidFireUtil.MANAGEMENT_VIP, url, DEFAULT_MANAGEMENT_PORT);
|
||||
}
|
||||
|
||||
|
||||
private int getStoragePort(String url)
|
||||
{
|
||||
return getPort(SolidFireUtil.STORAGE_VIP, url, DEFAULT_STORAGE_PORT);
|
||||
}
|
||||
|
||||
|
||||
private String getVip(String keyToMatch, String url)
|
||||
{
|
||||
String delimiter = ":";
|
||||
|
||||
String storageVip = getValue(keyToMatch, url);
|
||||
|
||||
int index = storageVip.indexOf(delimiter);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
return storageVip.substring(0, index);
|
||||
}
|
||||
|
||||
return storageVip;
|
||||
String delimiter = ":";
|
||||
|
||||
String storageVip = getValue(keyToMatch, url);
|
||||
|
||||
int index = storageVip.indexOf(delimiter);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
return storageVip.substring(0, index);
|
||||
}
|
||||
|
||||
return storageVip;
|
||||
}
|
||||
|
||||
|
||||
private int getPort(String keyToMatch, String url, int defaultPortNumber)
|
||||
{
|
||||
String delimiter = ":";
|
||||
|
||||
String storageVip = getValue(keyToMatch, url);
|
||||
|
||||
int index = storageVip.indexOf(delimiter);
|
||||
|
||||
int portNumber = defaultPortNumber;
|
||||
|
||||
if (index != -1) {
|
||||
String port = storageVip.substring(index + delimiter.length());
|
||||
|
||||
try {
|
||||
portNumber = Integer.parseInt(port);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid URL format (port is not an integer)");
|
||||
}
|
||||
}
|
||||
|
||||
return portNumber;
|
||||
String delimiter = ":";
|
||||
|
||||
String storageVip = getValue(keyToMatch, url);
|
||||
|
||||
int index = storageVip.indexOf(delimiter);
|
||||
|
||||
int portNumber = defaultPortNumber;
|
||||
|
||||
if (index != -1) {
|
||||
String port = storageVip.substring(index + delimiter.length());
|
||||
|
||||
try {
|
||||
portNumber = Integer.parseInt(port);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid URL format (port is not an integer)");
|
||||
}
|
||||
}
|
||||
|
||||
return portNumber;
|
||||
}
|
||||
|
||||
|
||||
private String getValue(String keyToMatch, String url)
|
||||
{
|
||||
String delimiter1 = ";";
|
||||
String delimiter2 = "=";
|
||||
|
||||
StringTokenizer st = new StringTokenizer(url, delimiter1);
|
||||
|
||||
while (st.hasMoreElements()) {
|
||||
String token = st.nextElement().toString();
|
||||
|
||||
int index = token.indexOf(delimiter2);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
throw new RuntimeException("Invalid URL format");
|
||||
}
|
||||
|
||||
String key = token.substring(0, index);
|
||||
|
||||
if (key.equalsIgnoreCase(keyToMatch)) {
|
||||
String valueToReturn = token.substring(index + delimiter2.length());
|
||||
|
||||
return valueToReturn;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Key not found in URL");
|
||||
String delimiter1 = ";";
|
||||
String delimiter2 = "=";
|
||||
|
||||
StringTokenizer st = new StringTokenizer(url, delimiter1);
|
||||
|
||||
while (st.hasMoreElements()) {
|
||||
String token = st.nextElement().toString();
|
||||
|
||||
int index = token.indexOf(delimiter2);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
throw new RuntimeException("Invalid URL format");
|
||||
}
|
||||
|
||||
String key = token.substring(0, index);
|
||||
|
||||
if (key.equalsIgnoreCase(keyToMatch)) {
|
||||
String valueToReturn = token.substring(index + delimiter2.length());
|
||||
|
||||
return valueToReturn;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Key not found in URL");
|
||||
}
|
||||
|
||||
|
||||
// do not implement this method for SolidFire's plug-in
|
||||
@Override
|
||||
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
|
||||
return true; // should be ignored for zone-wide-only plug-ins like SolidFire's
|
||||
}
|
||||
|
||||
|
||||
// do not implement this method for SolidFire's plug-in
|
||||
@Override
|
||||
public boolean attachCluster(DataStore store, ClusterScope scope) {
|
||||
return true; // should be ignored for zone-wide-only plug-ins like SolidFire's
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
|
||||
dataStoreHelper.attachZone(dataStore);
|
||||
|
||||
dataStoreHelper.attachZone(dataStore);
|
||||
|
||||
List<HostVO> xenServerHosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(HypervisorType.XenServer, scope.getScopeId());
|
||||
List<HostVO> kvmHosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(HypervisorType.KVM, scope.getScopeId());
|
||||
List<HostVO> hosts = new ArrayList<HostVO>();
|
||||
|
||||
hosts.addAll(xenServerHosts);
|
||||
hosts.addAll(kvmHosts);
|
||||
|
||||
for (HostVO host : hosts) {
|
||||
try {
|
||||
_storageMgr.connectHostToSharedPool(host.getId(), dataStore.getId());
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean maintain(DataStore dataStore) {
|
||||
storagePoolAutomation.maintain(dataStore);
|
||||
@ -316,7 +340,7 @@ public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeC
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean cancelMaintain(DataStore store) {
|
||||
dataStoreHelper.cancelMaintain(store);
|
||||
@ -324,7 +348,7 @@ public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeC
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// invoked to delete primary storage that is based on the SolidFire plug-in
|
||||
@Override
|
||||
public boolean deleteDataStore(DataStore store) {
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.StoragePoolHostVO;
|
||||
import com.cloud.storage.dao.StoragePoolHostDao;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class SolidFireHostListener implements HypervisorHostListener {
|
||||
private static final Logger s_logger = Logger.getLogger(SolidFireHostListener.class);
|
||||
|
||||
@Inject private AgentManager _agentMgr;
|
||||
@Inject private AlertManager _alertMgr;
|
||||
@Inject private DataStoreManager _dataStoreMgr;
|
||||
@Inject private HostDao _hostDao;
|
||||
@Inject private StoragePoolHostDao storagePoolHostDao;
|
||||
|
||||
@Override
|
||||
public boolean hostConnect(long hostId, long storagePoolId) {
|
||||
HostVO host = _hostDao.findById(hostId);
|
||||
|
||||
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(storagePoolId, hostId);
|
||||
|
||||
if (storagePoolHost == null) {
|
||||
storagePoolHost = new StoragePoolHostVO(storagePoolId, hostId, "");
|
||||
|
||||
storagePoolHostDao.persist(storagePoolHost);
|
||||
}
|
||||
|
||||
// just want to send the ModifyStoragePoolCommand for KVM
|
||||
if (host.getHypervisorType() != HypervisorType.KVM) {
|
||||
return true;
|
||||
}
|
||||
|
||||
StoragePool storagePool = (StoragePool)_dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
|
||||
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, storagePool);
|
||||
|
||||
Answer answer = _agentMgr.easySend(hostId, cmd);
|
||||
|
||||
if (answer == null) {
|
||||
throw new CloudRuntimeException("Unable to get an answer to the modify storage pool command (" + storagePool.getId() + ")");
|
||||
}
|
||||
|
||||
if (!answer.getResult()) {
|
||||
String msg = "Unable to attach storage pool " + storagePoolId + " to host " + hostId;
|
||||
|
||||
_alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
|
||||
|
||||
throw new CloudRuntimeException("Unable to establish a connection from agent to storage pool " + storagePool.getId() +
|
||||
" due to " + answer.getDetails() + " (" + storagePool.getId() + ")");
|
||||
}
|
||||
|
||||
assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; Pool = " + storagePool.getId() + " Host = " + hostId;
|
||||
|
||||
s_logger.info("Connection established between storage pool " + storagePool + " and host + " + hostId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostDisconnected(long hostId, long storagePoolId) {
|
||||
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(storagePoolId, hostId);
|
||||
|
||||
if (storagePoolHost != null) {
|
||||
storagePoolHostDao.deleteStoragePoolHostDetails(hostId, storagePoolId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -66,15 +66,7 @@ public class SolidfirePrimaryDataStoreProvider implements PrimaryDataStoreProvid
|
||||
public boolean configure(Map<String, Object> params) {
|
||||
lifecycle = ComponentContext.inject(SolidFirePrimaryDataStoreLifeCycle.class);
|
||||
driver = ComponentContext.inject(SolidfirePrimaryDataStoreDriver.class);
|
||||
listener = ComponentContext.inject(new HypervisorHostListener() {
|
||||
public boolean hostConnect(long hostId, long poolId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hostDisconnected(long hostId, long poolId) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
listener = ComponentContext.inject(SolidFireHostListener.class);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@ -1231,7 +1232,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
StoragePoolVO volumePool = _storagePoolDao.findById(volume.getPoolId());
|
||||
|
||||
DataTO volTO = volFactory.getVolume(volume.getId()).getTO();
|
||||
DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), null, volume.getVolumeType());
|
||||
DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), volume.getVolumeType());
|
||||
|
||||
DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName());
|
||||
|
||||
@ -1605,29 +1606,41 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
|
||||
if (sendCommand) {
|
||||
volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId());
|
||||
long storagePoolId = volumeToAttachStoragePool.getId();
|
||||
|
||||
HostVO host = _hostDao.findById(hostId);
|
||||
|
||||
if (host.getHypervisorType() == HypervisorType.KVM &&
|
||||
volumeToAttachStoragePool.isManaged() &&
|
||||
volumeToAttach.getPath() == null) {
|
||||
volumeToAttach.setPath(volumeToAttach.get_iScsiName());
|
||||
|
||||
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
|
||||
}
|
||||
|
||||
DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO();
|
||||
DiskTO disk = new DiskTO(volTO, deviceId, null, volumeToAttach.getVolumeType());
|
||||
DiskTO disk = new DiskTO(volTO, deviceId, volumeToAttach.getPath(), volumeToAttach.getVolumeType());
|
||||
|
||||
AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName());
|
||||
|
||||
cmd.setManaged(volumeToAttachStoragePool.isManaged());
|
||||
|
||||
cmd.setStorageHost(volumeToAttachStoragePool.getHostAddress());
|
||||
cmd.setStoragePort(volumeToAttachStoragePool.getPort());
|
||||
|
||||
cmd.set_iScsiName(volumeToAttach.get_iScsiName());
|
||||
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(volumeToAttach.getId());
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary);
|
||||
ChapInfo chapInfo = volService.getChapInfo(volumeInfo, dataStore);
|
||||
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
|
||||
disk.setDetails(details);
|
||||
|
||||
details.put(DiskTO.MANAGED, String.valueOf(volumeToAttachStoragePool.isManaged()));
|
||||
details.put(DiskTO.STORAGE_HOST, volumeToAttachStoragePool.getHostAddress());
|
||||
details.put(DiskTO.STORAGE_PORT, String.valueOf(volumeToAttachStoragePool.getPort()));
|
||||
details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeToAttach.getSize()));
|
||||
details.put(DiskTO.IQN, volumeToAttach.get_iScsiName());
|
||||
|
||||
if (chapInfo != null) {
|
||||
cmd.setChapInitiatorUsername(chapInfo.getInitiatorUsername());
|
||||
cmd.setChapInitiatorPassword(chapInfo.getInitiatorSecret());
|
||||
cmd.setChapTargetUsername(chapInfo.getTargetUsername());
|
||||
cmd.setChapTargetPassword(chapInfo.getTargetSecret());
|
||||
details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
|
||||
details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
|
||||
details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
|
||||
details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1646,7 +1659,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
volumeToAttach = _volsDao.findById(volumeToAttach.getId());
|
||||
|
||||
if (volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
|
||||
volumeToAttach.setPath(answer.getDisk().getVdiUuid());
|
||||
volumeToAttach.setPath(answer.getDisk().getPath());
|
||||
|
||||
_volsDao.update(volumeToAttach.getId(), volumeToAttach);
|
||||
}
|
||||
|
||||
@ -81,6 +81,9 @@ public class StoragePoolMonitor implements Listener {
|
||||
List<StoragePoolVO> zoneStoragePoolsByHypervisor = _poolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), scCmd.getHypervisorType());
|
||||
zoneStoragePoolsByTags.retainAll(zoneStoragePoolsByHypervisor);
|
||||
pools.addAll(zoneStoragePoolsByTags);
|
||||
List<StoragePoolVO> zoneStoragePoolsByAnyHypervisor = _poolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), HypervisorType.Any);
|
||||
pools.addAll(zoneStoragePoolsByAnyHypervisor);
|
||||
|
||||
for (StoragePoolVO pool : pools) {
|
||||
if (pool.getStatus() != StoragePoolStatus.Up) {
|
||||
continue;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user