mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
StorPool storage plugin (#6007)
* StorPool storage plugin Adds volume storage plugin for StorPool SDS * Added support for alternative endpoint Added option to switch to alternative endpoint for SP primary storage * renamed all classes from Storpool to StorPool * Address review * removed unnecessary else * Removed check about the storage provider We don't need this check, we'll get if the snapshot is on StorPool be its name from path * Check that current plugin supports all functionality before upgrade CS * Smoke tests for StorPool plug-in * Fixed conflicts * Fixed conflicts and added missed Apache license header * Removed whitespaces in smoke tests * Added StorPool plugin jar for Debian the StorPool jar will be included into cloudstack-agent package for Debian/Ubuntu
This commit is contained in:
parent
9067938a0d
commit
4004dfcfd8
@ -97,6 +97,11 @@
|
||||
<artifactId>cloud-plugin-storage-volume-linstor</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-storage-volume-storpool</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-server</artifactId>
|
||||
@ -755,6 +760,12 @@
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-storage-volume-storpool</artifactId>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
@ -799,6 +810,7 @@
|
||||
<exclude>org.bouncycastle:bcpkix-jdk15on</exclude>
|
||||
<exclude>org.bouncycastle:bctls-jdk15on</exclude>
|
||||
<exclude>mysql:mysql-connector-java</exclude>
|
||||
<exclude>org.apache.cloudstack:cloud-plugin-storage-volume-storpool</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<transformers>
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
package org.apache.cloudstack.storage.command;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class CopyCmdAnswer extends Answer {
|
||||
@ -37,4 +38,8 @@ public class CopyCmdAnswer extends Answer {
|
||||
public CopyCmdAnswer(String errMsg) {
|
||||
super(null, false, errMsg);
|
||||
}
|
||||
|
||||
public CopyCmdAnswer(Command cmd, Exception e) {
|
||||
super(cmd, e);
|
||||
}
|
||||
}
|
||||
|
||||
1
debian/rules
vendored
1
debian/rules
vendored
@ -41,6 +41,7 @@ override_dh_auto_install:
|
||||
mkdir $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib
|
||||
install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-$(VERSION).jar $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/
|
||||
install -D plugins/hypervisors/kvm/target/dependencies/* $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/
|
||||
install -D plugins/storage/volume/storpool/target/cloud-plugin-storage-volume-storpool-$(VERSION).jar $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/
|
||||
|
||||
install -d -m0755 debian/$(PACKAGE)-agent/lib/systemd/system
|
||||
install -m0644 packaging/systemd/$(PACKAGE)-agent.service debian/$(PACKAGE)-agent/lib/systemd/system/$(PACKAGE)-agent.service
|
||||
|
||||
@ -103,4 +103,33 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver {
|
||||
* returns true if the host can access the storage pool
|
||||
*/
|
||||
boolean canHostAccessStoragePool(Host host, StoragePool pool);
|
||||
|
||||
/**
|
||||
* Used by storage pools which want to keep VMs' information
|
||||
* @return true if additional VM info is needed (intended for storage pools).
|
||||
*/
|
||||
boolean isVmInfoNeeded();
|
||||
|
||||
/**
|
||||
* Provides additional info for a VM (intended for storage pools).
|
||||
* E.g. the storage pool may want to keep/delete information if the volume is attached/detached to any VM.
|
||||
* @param vmId The ID of the virtual machine
|
||||
* @param volumeId the ID of the volume
|
||||
*/
|
||||
void provideVmInfo(long vmId, long volumeId);
|
||||
|
||||
/**
|
||||
* Returns true if the storage have to know about the VM's tags (intended for storage pools).
|
||||
* @param tagKey The name of the tag
|
||||
* @return true if the storage have to know about the VM's tags
|
||||
*/
|
||||
boolean isVmTagsNeeded(String tagKey);
|
||||
|
||||
/**
|
||||
* Provide VM's tags to storage (intended for storage pools).
|
||||
* @param vmId The ID of the virtual machine
|
||||
* @param volumeId The ID of the volume
|
||||
* @param tagValue The value of the VM's tag
|
||||
*/
|
||||
void provideVmTags(long vmId, long volumeId, String tagValue);
|
||||
}
|
||||
|
||||
@ -39,9 +39,9 @@ public interface StorageStrategyFactory {
|
||||
/**
|
||||
* Used only for KVM hypervisors when allocating a VM snapshot
|
||||
* @param vmId the ID of the virtual machine
|
||||
* @param rootPoolId volume pool ID
|
||||
* @param snapshotMemory for VM snapshots with memory
|
||||
* @return VMSnapshotStrategy
|
||||
*/
|
||||
VMSnapshotStrategy getVmSnapshotStrategy(Long vmId, Long rootPoolId, boolean snapshotMemory);
|
||||
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public interface VMSnapshotStrategy {
|
||||
StrategyPriority canHandle(VMSnapshot vmSnapshot);
|
||||
|
||||
/**
|
||||
* Used only for KVM hypervisors when allocating a VM snapshot
|
||||
* Verifies if the strategy can handle the VM snapshot. This method is used only for KVM hypervisors when allocating a VM snapshot.
|
||||
* @param vmId the ID of the virtual machine
|
||||
* @param snapshotMemory for VM snapshots with memory
|
||||
* @return StrategyPriority
|
||||
|
||||
@ -52,8 +52,8 @@ import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.storage.DiskOfferingVO;
|
||||
import com.cloud.storage.GuestOSHypervisorVO;
|
||||
import com.cloud.storage.GuestOSVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.storage.dao.GuestOSDao;
|
||||
import com.cloud.storage.dao.GuestOSHypervisorDao;
|
||||
|
||||
@ -351,6 +351,7 @@ install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT
|
||||
install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
|
||||
install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar
|
||||
cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
|
||||
# Usage server
|
||||
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
|
||||
|
||||
@ -344,6 +344,7 @@ install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT
|
||||
install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
|
||||
install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar
|
||||
cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
|
||||
# Usage server
|
||||
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
|
||||
|
||||
@ -346,6 +346,7 @@ install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT
|
||||
install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
|
||||
install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar
|
||||
cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
|
||||
|
||||
# Usage server
|
||||
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
|
||||
|
||||
@ -123,6 +123,8 @@
|
||||
<module>storage/volume/solidfire</module>
|
||||
<module>storage/volume/scaleio</module>
|
||||
<module>storage/volume/linstor</module>
|
||||
<module>storage/volume/storpool</module>
|
||||
|
||||
|
||||
<module>storage-allocators/random</module>
|
||||
|
||||
@ -211,4 +213,4 @@
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@ -1860,4 +1860,22 @@ public class DateraPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,4 +491,22 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -765,4 +765,22 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,4 +239,22 @@ public class NexentaPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,4 +265,22 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -947,4 +947,22 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
String msg = "SDC not connected on the host: " + host.getId() + ", reconnect the SDC to MDM";
|
||||
alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC disconnected on host: " + host.getUuid(), msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -1619,4 +1619,22 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
}
|
||||
}
|
||||
|
||||
344
plugins/storage/volume/storpool/README.md
Normal file
344
plugins/storage/volume/storpool/README.md
Normal file
@ -0,0 +1,344 @@
|
||||
# StorPool CloudStack Integration
|
||||
|
||||
## CloudStack Overview
|
||||
|
||||
### Primary and Secondary storage
|
||||
|
||||
Primary storage is associated with a cluster or zone, and it stores the virtual disks for all the VMs running on hosts in that cluster/zone.
|
||||
|
||||
Secondary storage stores the following:
|
||||
* Templates — OS images that can be used to boot VMs and can include additional configuration information, such as installed applications
|
||||
* ISO images — disc images containing data or bootable media for operating systems
|
||||
* Disk volume snapshots — saved copies of VM data which can be used for data recovery or to create new templates
|
||||
|
||||
|
||||
### ROOT and DATA volumes
|
||||
|
||||
ROOT volumes correspond to the boot disk of a VM. They are created automatically by CloudStack during VM creation.
|
||||
ROOT volumes are created based on a system disk offering, corresponding to the service offering the user VM
|
||||
is based on. We may change the ROOT volume disk offering but only to another system created disk offering.
|
||||
|
||||
DATA volumes correspond to additional disks. These can be created by users and then attached/detached to VMs.
|
||||
DATA volumes are created based on a user-defined disk offering.
|
||||
|
||||
|
||||
## Plugin Organization
|
||||
|
||||
The StorPool plugin consists of two parts:
|
||||
|
||||
### KVM hypervisor plugin patch
|
||||
|
||||
Source directory: ./apache-cloudstack-4.17-src/plugins/hypervisors/kvm
|
||||
|
||||
### StorPool primary storage plugin
|
||||
|
||||
Source directory: ./apache-cloudstack-4.17.0-src/plugins/storage/volume
|
||||
|
||||
There is one plugin for both the CloudStack management and agents, in the hope that having all the source
|
||||
in one place will ease development and maintenance. The plugin itself though is separated into two mainly
|
||||
independent parts:
|
||||
|
||||
* ./src/com/... directory tree: agent related classes and commands send from management to agent
|
||||
* ./src/org/... directory tree: management related classes
|
||||
|
||||
The plugin is intended to be self contained and non-intrusive, thus ideally deploying it would consist of only
|
||||
dropping the jar file into the appropriate places. This is the reason why all StorPool related communication
|
||||
(ex. data copying, volume resize) is done with StorPool specific commands even when there is a CloudStack command
|
||||
that does pretty much the same.
|
||||
|
||||
Note that for the present the StorPool plugin may only be used for a single primary storage cluster; support for
|
||||
multiple clusters is planned.
|
||||
|
||||
|
||||
## Build, Install, Setup
|
||||
|
||||
### Build
|
||||
|
||||
Go to the source directory and run:
|
||||
|
||||
mvn -Pdeveloper -DskipTests install
|
||||
|
||||
The resulting jar file is located in the target/ subdirectory.
|
||||
|
||||
Note: checkstyle errors: before compilation a code style check is performed; if this fails compilation is aborted.
|
||||
In short: no trailing whitespace, indent using 4 spaces, not tabs, comment-out or remove unused imports.
|
||||
|
||||
Note: Need to build both the KVM plugin and the StorPool plugin proper.
|
||||
|
||||
### Install
|
||||
|
||||
#### StorPool primary storage plugin
|
||||
|
||||
For each CloudStack management host:
|
||||
|
||||
```bash
|
||||
scp ./target/cloud-plugin-storage-volume-storpool-{version}.jar {MGMT_HOST}:/usr/share/cloudstack-management/lib/
|
||||
```
|
||||
|
||||
For each CloudStack agent host:
|
||||
|
||||
```bash
|
||||
scp ./target/cloud-plugin-storage-volume-storpool-{version}.jar {AGENT_HOST}:/usr/share/cloudstack-agent/plugins/
|
||||
```
|
||||
|
||||
Note: CloudStack managements/agents services must be restarted after adding the plugin to the respective directories
|
||||
|
||||
Note: Agents should have access to the StorPool management API, since attach and detach operations happens on the agent.
|
||||
This is a CloudStack design issue, can't do much about it.
|
||||
|
||||
### Setup
|
||||
|
||||
#### Setting up StorPool
|
||||
|
||||
Perform the StorPool installation following the StorPool Installation Guide.
|
||||
|
||||
Create a template to be used by CloudStack. Must set *placeHead*, *placeAll*, *placeTail* and *replication*.
|
||||
No need to set default volume size because it is determined by the CloudStack disks and services offering.
|
||||
|
||||
#### Setting up a StorPool PRIMARY storage pool in CloudStack
|
||||
|
||||
From the WEB UI, go to Infrastructure -> Primary Storage -> Add Primary Storage
|
||||
|
||||
Scope: select Zone-Wide
|
||||
Hypervisor: select KVM
|
||||
Zone: pick appropriate zone.
|
||||
Name: user specified name
|
||||
|
||||
Protocol: select *SharedMountPoint*
|
||||
Path: enter */dev/storpool* (required argument, actually not needed in practice).
|
||||
|
||||
Provider: select *StorPool*
|
||||
Managed: leave unchecked (currently ignored)
|
||||
Capacity Bytes: used for accounting purposes only. May be more or less than the actual StorPool template capacity.
|
||||
Capacity IOPS: currently not used (may use for max IOPS limitations on volumes from this pool).
|
||||
URL: enter SP_API_HTTP=address:port;SP_AUTH_TOKEN=token;SP_TEMPLATE=template_name. At present one template can be used for at most one Storage Pool.
|
||||
|
||||
SP_API_HTTP - address of StorPool Api
|
||||
SP_AUTH_TOKEN - StorPool's token
|
||||
SP_TEMPLATE - name of StorPool's template
|
||||
|
||||
Storage Tags: If left blank, the StorPool storage plugin will use the pool name to create a corresponding storage tag.
|
||||
This storage tag may be used later, when defining service or disk offerings.
|
||||
|
||||
|
||||
## Plugin Functionality
|
||||
|
||||
<table cellpadding="5">
|
||||
<tr>
|
||||
<th>Plugin Action</th>
|
||||
<th>CloudStack Action</th>
|
||||
<th>management/agent</th>
|
||||
<th>impl. details</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create ROOT volume from ISO</td>
|
||||
<td>create VM from ISO</td>
|
||||
<td>management</td>
|
||||
<td>createVolumeAsync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create ROOT volume from Template</td>
|
||||
<td>create VM from Template</td>
|
||||
<td>management + agent</td>
|
||||
<td>copyAsync (T => T, T => V)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create DATA volume</td>
|
||||
<td>create Volume</td>
|
||||
<td>management</td>
|
||||
<td>createVolumeAsync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Attach ROOT/DATA volume</td>
|
||||
<td>start VM (+attach/detach Volume)</td>
|
||||
<td>agent</td>
|
||||
<td>connectPhysicalDisk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Detach ROOT/DATA volume</td>
|
||||
<td>stop VM</td>
|
||||
<td>agent</td>
|
||||
<td>disconnectPhysicalDiskByPath</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>Migrate VM</td>
|
||||
<td>agent</td>
|
||||
<td>attach + detach</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete ROOT volume</td>
|
||||
<td>destroy VM (expunge)</td>
|
||||
<td>management</td>
|
||||
<td>deleteAsync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete DATA volume</td>
|
||||
<td>delete Volume (detached)</td>
|
||||
<td>management</td>
|
||||
<td>deleteAsync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create ROOT/DATA volume snapshot</td>
|
||||
<td>snapshot volume</td>
|
||||
<td>management + agent</td>
|
||||
<td>takeSnapshot + copyAsync (S => S)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create volume from snapshoot</td>
|
||||
<td>create volume from snapshot</td>
|
||||
<td>management + agent(?)</td>
|
||||
<td>copyAsync (S => V)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create TEMPLATE from ROOT volume</td>
|
||||
<td>create template from volume</td>
|
||||
<td>management + agent</td>
|
||||
<td>copyAsync (V => T)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create TEMPLATE from snapshot</td>
|
||||
<td>create template from snapshot</td>
|
||||
<td>SECONDARY STORAGE</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Download volume</td>
|
||||
<td>download volume</td>
|
||||
<td>management + agent</td>
|
||||
<td>copyAsync (V => V)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Revert ROOT/DATA volume to snapshot</td>
|
||||
<td>revert to snapshot</td>
|
||||
<td>management</td>
|
||||
<td>revertSnapshot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>(Live) resize ROOT/DATA volume</td>
|
||||
<td>resize volume</td>
|
||||
<td>management + agent</td>
|
||||
<td>resize + StorpoolResizeCmd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete SNAPSHOT (ROOT/DATA)</td>
|
||||
<td>delete snapshot</td>
|
||||
<td>management</td>
|
||||
<td>StorpoolSnapshotStrategy</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete TEMPLATE</td>
|
||||
<td>delete template</td>
|
||||
<td>agent</td>
|
||||
<td>deletePhysicalDisk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>migrate VM/volume</td>
|
||||
<td>migrate VM/volume to another storage</td>
|
||||
<td>management/management + agent</td>
|
||||
<td>copyAsync (V => V)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VM snapshot</td>
|
||||
<td>group snapshot of VM's disks</td>
|
||||
<td>management</td>
|
||||
<td>StorpoolVMSnapshotStrategy takeVMSnapshot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>revert VM snapshot</td>
|
||||
<td>revert group snapshot of VM's disks</td>
|
||||
<td>management</td>
|
||||
<td>StorpoolVMSnapshotStrategy revertVMSnapshot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>delete VM snapshot</td>
|
||||
<td>delete group snapshot of VM's disks</td>
|
||||
<td>management</td>
|
||||
<td>StorpoolVMSnapshotStrategy deleteVMSnapshot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VM vc_policy tag</td>
|
||||
<td>vc_policy tag for all disks attached to VM</td>
|
||||
<td>management</td>
|
||||
<td>StorPoolCreateTagsCmd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>delete VM vc_policy tag</td>
|
||||
<td>remove vc_policy tag for all disks attached to VM</td>
|
||||
<td>management</td>
|
||||
<td>StorPoolDeleteTagsCmd</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
>NOTE: When using multicluster for each CloudStack cluster in its settings set the value of StorPool's SP_CLUSTER_ID in "sp.cluster.id".
|
||||
>
|
||||
|
||||
>NOTE: Secondary storage could be bypassed with Configuration setting "sp.bypass.secondary.storage" set to true. </br>
|
||||
In this case only snapshots won't be downloaded to secondary storage.
|
||||
>
|
||||
|
||||
### Creating template from snapshot
|
||||
|
||||
#### If bypass option is enabled
|
||||
|
||||
The snapshot exists only on PRIMARY (StorPool) storage. From this snapshot it will be created a template on SECONADRY.
|
||||
|
||||
#### If bypass option is disabled
|
||||
|
||||
TODO: Maybe we should not use CloudStack functionality, and to use that one when bypass option is enabled
|
||||
|
||||
This is independent of StorPool as snapshots exist on secondary.
|
||||
|
||||
### Creating ROOT volume from templates
|
||||
|
||||
When creating the first volume based on the given template, if snapshot of the template does not exists on StorPool it will be first downloaded (cached) to PRIMARY storage.
|
||||
This is mapped to a StorPool snapshot so, creating succecutive volumes from the same template does not incur additional
|
||||
copying of data to PRIMARY storage.
|
||||
|
||||
This cached snapshot is garbage collected when the original template is deleted from CloudStack. This cleanup is done
|
||||
by a background task in CloudStack.
|
||||
|
||||
### Creating a ROOT volume from an ISO image
|
||||
|
||||
We just need to create the volume. The ISO installation is handled by CloudStack.
|
||||
|
||||
### Creating a DATA volume
|
||||
|
||||
DATA volumes are created by CloudStack the first time it is attached to a VM.
|
||||
|
||||
### Creating volume from snapshot
|
||||
|
||||
We use the fact that the snapshot already exists on PRIMARY, so no data is copied. We will copy snapshots from SECONDARY to StorPool PRIMARY,
|
||||
when there is no corresponding StorPool snapshot.
|
||||
|
||||
### Resizing volumes
|
||||
|
||||
We need to send a resize cmd to agent, where the VM the volume is attached to is running, so that
|
||||
the resize is visible by the VM.
|
||||
|
||||
### Creating snapshots
|
||||
|
||||
The snapshot is first created on the PRIMARY storage (i.e. StorPool), then backed-up on SECONDARY storage
|
||||
(tested with NFS secondary) if bypass option is not enabled. The original StorPool snapshot is kept, so that creating volumes from the snapshot does not need to copy
|
||||
the data again to PRIMARY. When the snapshot is deleted from CloudStack so is the corresponding StorPool snapshot.
|
||||
|
||||
Currently snapshots are taken in RAW format.
|
||||
|
||||
### Reverting volume to snapshot
|
||||
|
||||
It's handled by StorPool
|
||||
|
||||
### Migrating volumes to other Storage pools
|
||||
|
||||
Tested with storage pools on NFS only.
|
||||
|
||||
### Virtual Machine Snapshot/Group Snapshot
|
||||
|
||||
StorPool supports consistent snapshots of volumes attached to a virtual machine.
|
||||
|
||||
### BW/IOPS limitations
|
||||
|
||||
Max IOPS are kept in StorPool's volumes with the help of custom service offerings, by adding IOPS limits to the
|
||||
corresponding system disk offering.
|
||||
|
||||
CloudStack has no way to specify max BW. Do they want to be able to specify max BW only is sufficient.
|
||||
68
plugins/storage/volume/storpool/pom.xml
Normal file
68
plugins/storage/volume/storpool/pom.xml
Normal file
@ -0,0 +1,68 @@
|
||||
<!-- 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-storpool</artifactId>
|
||||
<name>Apache CloudStack Plugin - Storage Volume StorPool provider</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.17.0.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>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-engine-storage-snapshot</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-hypervisor-kvm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-engine-orchestration</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>4.4</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,30 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolBackupSnapshotCommand extends StorPoolCopyCommand<SnapshotObjectTO, SnapshotObjectTO> {
|
||||
public StorPoolBackupSnapshotCommand(final DataTO srcTO, final DataTO dstTO, final int timeout, final boolean executeInSequence) {
|
||||
super(srcTO, dstTO, timeout, executeInSequence);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolBackupTemplateFromSnapshotCommand extends StorPoolCopyCommand<DataTO, TemplateObjectTO> {
|
||||
public StorPoolBackupTemplateFromSnapshotCommand(final DataTO srcTO, final DataTO dstTO, final int timeout, final boolean executeInSequence) {
|
||||
super(srcTO, dstTO, timeout, executeInSequence);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolCopyCommand<S extends DataTO, D extends DataTO> extends StorageSubSystemCommand {
|
||||
private S sourceTO;
|
||||
private D destinationTO;
|
||||
private boolean executeInSequence = false;
|
||||
|
||||
public StorPoolCopyCommand(final DataTO sourceTO, final DataTO destinationTO, final int timeout, final boolean executeInSequence) {
|
||||
super();
|
||||
this.sourceTO = (S)sourceTO;
|
||||
this.destinationTO = (D)destinationTO;
|
||||
setWait(timeout);
|
||||
this.executeInSequence = executeInSequence;
|
||||
}
|
||||
|
||||
public S getSourceTO() {
|
||||
return sourceTO;
|
||||
}
|
||||
|
||||
public D getDestinationTO() {
|
||||
return destinationTO;
|
||||
}
|
||||
|
||||
public int getWaitInMillSeconds() {
|
||||
return getWait() * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return executeInSequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExecuteInSequence(final boolean inSeq) {
|
||||
executeInSequence = inSeq;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolCopyVolumeToSecondaryCommand extends StorPoolCopyCommand<VolumeObjectTO, VolumeObjectTO> {
|
||||
public StorPoolCopyVolumeToSecondaryCommand(final DataTO srcTO, final DataTO dstTO, final int timeout, final boolean executeInSequence) {
|
||||
super(srcTO, dstTO, timeout, executeInSequence);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
//
|
||||
// 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.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolDownloadTemplateCommand extends StorPoolCopyCommand<DataTO, DataTO> {
|
||||
protected String objectType;
|
||||
public StorPoolDownloadTemplateCommand(final DataTO srcTO, final DataTO dstTO, final int timeout, final boolean executeInSequence, String objectType) {
|
||||
super(srcTO, dstTO, timeout, executeInSequence);
|
||||
this.objectType = objectType;
|
||||
}
|
||||
|
||||
public String getObjectType() {
|
||||
return objectType;
|
||||
}
|
||||
public void setObjectType(String objectType) {
|
||||
this.objectType = objectType;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
|
||||
public class StorPoolDownloadVolumeCommand extends StorPoolCopyCommand<VolumeObjectTO, VolumeObjectTO> {
|
||||
public StorPoolDownloadVolumeCommand(final DataTO srcTO, final DataTO dstTO, final int timeout, final boolean executeInSequence) {
|
||||
super(srcTO, dstTO, timeout, executeInSequence);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.cloud.agent.api.MigrateCommand;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
|
||||
public class StorPoolMigrateWithVolumesCommand extends MigrateCommand {
|
||||
private List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
|
||||
|
||||
public StorPoolMigrateWithVolumesCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
public StorPoolMigrateWithVolumesCommand(String vmName, String destIp, boolean isWindows, VirtualMachineTO vmTO,
|
||||
boolean executeInSequence) {
|
||||
super(vmName, destIp, isWindows, vmTO, executeInSequence);
|
||||
}
|
||||
|
||||
public List<MigrateDiskInfo> getMigrateDiskInfoList() {
|
||||
return migrateDiskInfoList;
|
||||
}
|
||||
|
||||
public void setMigrateDiskInfoList(List<MigrateDiskInfo> migrateDiskInfoList) {
|
||||
this.migrateDiskInfoList = migrateDiskInfoList;
|
||||
}
|
||||
|
||||
public boolean isMigrateStorageManaged() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isMigrateNonSharedInc() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.api.StoragePoolInfo;
|
||||
import com.cloud.storage.template.TemplateProp;
|
||||
|
||||
public class StorPoolModifyStoragePoolAnswer extends Answer{
|
||||
private StoragePoolInfo poolInfo;
|
||||
private Map<String, TemplateProp> templateInfo;
|
||||
private String localDatastoreName;
|
||||
private String poolType;
|
||||
private List<ModifyStoragePoolAnswer> datastoreClusterChildren = new ArrayList<>();
|
||||
private String clusterId;
|
||||
|
||||
public StorPoolModifyStoragePoolAnswer(StorPoolModifyStoragePoolCommand cmd, long capacityBytes, long availableBytes, Map<String, TemplateProp> tInfo, String clusterId) {
|
||||
super(cmd);
|
||||
result = true;
|
||||
poolInfo = new StoragePoolInfo(null, cmd.getPool().getHost(), cmd.getPool().getPath(), cmd.getLocalPath(), cmd.getPool().getType(), capacityBytes, availableBytes);
|
||||
templateInfo = tInfo;
|
||||
this.clusterId = clusterId;
|
||||
}
|
||||
|
||||
public StorPoolModifyStoragePoolAnswer(String errMsg) {
|
||||
super(null, false, errMsg);
|
||||
}
|
||||
|
||||
public void setPoolInfo(StoragePoolInfo poolInfo) {
|
||||
this.poolInfo = poolInfo;
|
||||
}
|
||||
|
||||
public StoragePoolInfo getPoolInfo() {
|
||||
return poolInfo;
|
||||
}
|
||||
|
||||
public void setTemplateInfo(Map<String, TemplateProp> templateInfo) {
|
||||
this.templateInfo = templateInfo;
|
||||
}
|
||||
|
||||
public Map<String, TemplateProp> getTemplateInfo() {
|
||||
return templateInfo;
|
||||
}
|
||||
|
||||
public void setLocalDatastoreName(String localDatastoreName) {
|
||||
this.localDatastoreName = localDatastoreName;
|
||||
}
|
||||
|
||||
public String getLocalDatastoreName() {
|
||||
return localDatastoreName;
|
||||
}
|
||||
|
||||
public String getPoolType() {
|
||||
return poolType;
|
||||
}
|
||||
|
||||
public void setPoolType(String poolType) {
|
||||
this.poolType = poolType;
|
||||
}
|
||||
|
||||
public List<ModifyStoragePoolAnswer> getDatastoreClusterChildren() {
|
||||
return datastoreClusterChildren;
|
||||
}
|
||||
|
||||
public void setDatastoreClusterChildren(List<ModifyStoragePoolAnswer> datastoreClusterChildren) {
|
||||
this.datastoreClusterChildren = datastoreClusterChildren;
|
||||
}
|
||||
|
||||
public String getClusterId() {
|
||||
return clusterId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
||||
import com.cloud.storage.StoragePool;
|
||||
|
||||
public class StorPoolModifyStoragePoolCommand extends ModifyStoragePoolCommand {
|
||||
private String volumeName;
|
||||
|
||||
public StorPoolModifyStoragePoolCommand(boolean add, StoragePool pool, String volumeName) {
|
||||
super(add, pool);
|
||||
this.volumeName = volumeName;
|
||||
}
|
||||
|
||||
public String getVolumeName() {
|
||||
return volumeName;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
|
||||
|
||||
public class StorPoolResizeVolumeCommand extends ResizeVolumeCommand {
|
||||
protected boolean isAttached;
|
||||
protected StorPoolResizeVolumeCommand() {
|
||||
super();
|
||||
}
|
||||
|
||||
public StorPoolResizeVolumeCommand(String path, StorageFilerTO pool, Long currentSize, Long newSize, boolean shrinkOk, String vmInstance, boolean isAttached) {
|
||||
super(path, pool, currentSize, newSize, shrinkOk, vmInstance);
|
||||
this.isAttached = isAttached;
|
||||
}
|
||||
|
||||
public boolean isAttached() {
|
||||
return isAttached;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
//
|
||||
// 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.resource.wrapper;
|
||||
|
||||
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolBackupSnapshotCommand.class)
|
||||
public final class StorPoolBackupSnapshotCommandWrapper extends CommandWrapper<StorPoolBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolBackupSnapshotCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public CopyCmdAnswer execute(final StorPoolBackupSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String srcPath = null;
|
||||
KVMStoragePool secondaryPool = null;
|
||||
|
||||
try {
|
||||
final SnapshotObjectTO src = cmd.getSourceTO();
|
||||
final SnapshotObjectTO dst = cmd.getDestinationTO();
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
|
||||
SP_LOG("StorpoolBackupSnapshotCommandWrapper.execute: src=" + src.getPath() + "dst=" + dst.getPath());
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", "snapshot", src.getPath());
|
||||
srcPath = src.getPath();
|
||||
|
||||
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
|
||||
|
||||
final DataStoreTO dstDataStore = dst.getDataStore();
|
||||
if (!(dstDataStore instanceof NfsTO)) {
|
||||
return new CopyCmdAnswer("Backup Storpool snapshot: Only NFS secondary supported at present!");
|
||||
}
|
||||
|
||||
secondaryPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl());
|
||||
|
||||
final String dstDir = secondaryPool.getLocalPath() + File.separator + dst.getPath();
|
||||
FileUtils.forceMkdir(new File(dstDir));
|
||||
|
||||
final String dstPath = dstDir + File.separator + dst.getName();
|
||||
final QemuImgFile dstFile = new QemuImgFile(dstPath, PhysicalDiskFormat.QCOW2);
|
||||
|
||||
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
|
||||
qemu.convert(srcFile, dstFile);
|
||||
|
||||
SP_LOG("StorpoolBackupSnapshotCommandWrapper srcFileFormat=%s, dstFileFormat=%s", srcFile.getFormat(), dstFile.getFormat());
|
||||
final File snapFile = new File(dstPath);
|
||||
final long size = snapFile.exists() ? snapFile.length() : 0;
|
||||
|
||||
final SnapshotObjectTO snapshot = new SnapshotObjectTO();
|
||||
snapshot.setPath(dst.getPath() + File.separator + dst.getName());
|
||||
snapshot.setPhysicalSize(size);
|
||||
|
||||
return new CopyCmdAnswer(snapshot);
|
||||
} catch (final Exception e) {
|
||||
final String error = String.format("Failed to backup snapshot with id [%s] with a pool %s, due to %s", cmd.getSourceTO().getId(), cmd.getSourceTO().getDataStore().getUuid(), e.getMessage());
|
||||
SP_LOG(error);
|
||||
s_logger.debug(error);
|
||||
return new CopyCmdAnswer(cmd, e);
|
||||
} finally {
|
||||
if (srcPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", "snapshot", srcPath);
|
||||
}
|
||||
|
||||
if (secondaryPool != null) {
|
||||
try {
|
||||
secondaryPool.delete();
|
||||
} catch (final Exception e) {
|
||||
s_logger.debug("Failed to delete secondary storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.resource.wrapper;
|
||||
|
||||
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.StorageLayer;
|
||||
import com.cloud.storage.template.Processor;
|
||||
import com.cloud.storage.template.Processor.FormatInfo;
|
||||
import com.cloud.storage.template.QCOW2Processor;
|
||||
import com.cloud.storage.template.TemplateLocation;
|
||||
import com.cloud.storage.template.TemplateProp;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolBackupTemplateFromSnapshotCommand.class)
|
||||
public class StorPoolBackupTemplateFromSnapshotCommandWrapper extends CommandWrapper<StorPoolBackupTemplateFromSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolBackupTemplateFromSnapshotCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public CopyCmdAnswer execute(final StorPoolBackupTemplateFromSnapshotCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String srcPath = null;
|
||||
KVMStoragePool secondaryPool = null;
|
||||
String objectType = cmd.getSourceTO().getObjectType().toString().toLowerCase();
|
||||
|
||||
try {
|
||||
final DataTO src = cmd.getSourceTO();
|
||||
final TemplateObjectTO dst = cmd.getDestinationTO();
|
||||
String name = null;
|
||||
String volumeFormatExtension = null;
|
||||
|
||||
if (src instanceof SnapshotObjectTO) {
|
||||
name = ((SnapshotObjectTO) src).getName();
|
||||
volumeFormatExtension = ((SnapshotObjectTO) src).getVolume().getFormat().getFileExtension();
|
||||
} else if (src instanceof VolumeObjectTO) {
|
||||
name = ((VolumeObjectTO) src).getName();
|
||||
volumeFormatExtension = ((VolumeObjectTO) src).getFormat().getFileExtension();
|
||||
} else {
|
||||
return new CopyCmdAnswer("Backup of a template is not supported for data object: " + src.getObjectType() );
|
||||
}
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
StorageLayer storage = libvirtComputingResource.getStorage();
|
||||
Processor processor = new QCOW2Processor();
|
||||
String _tmpltpp = "template.properties";
|
||||
|
||||
SP_LOG("StorpoolBackupTemplateFromSnapshotCommandWrapper.execute: src=" + src.getPath() + "dst=" + dst.getPath());
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", objectType, src.getPath());
|
||||
srcPath = src.getPath();
|
||||
|
||||
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
|
||||
|
||||
final DataStoreTO dstDataStore = dst.getDataStore();
|
||||
if (!(dstDataStore instanceof NfsTO)) {
|
||||
return new CopyCmdAnswer("Backup Storpool snapshot: Only NFS secondary supported at present!");
|
||||
}
|
||||
|
||||
secondaryPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl());
|
||||
|
||||
final String dstDir = secondaryPool.getLocalPath() + File.separator + dst.getPath();
|
||||
FileUtils.forceMkdir(new File(dstDir));
|
||||
|
||||
String nameWithExtension = name + "." + volumeFormatExtension;
|
||||
|
||||
final String dstPath = dstDir + File.separator + nameWithExtension;
|
||||
final QemuImgFile dstFile = new QemuImgFile(dstPath, PhysicalDiskFormat.QCOW2);
|
||||
|
||||
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
|
||||
qemu.convert(srcFile, dstFile);
|
||||
|
||||
storage.create(dstDir, _tmpltpp);
|
||||
String metaFileName = dstDir + File.separator + _tmpltpp;
|
||||
File metaFile = new File(metaFileName);
|
||||
|
||||
try ( FileWriter writer = new FileWriter(metaFile);
|
||||
BufferedWriter bufferWriter = new BufferedWriter(writer);) {
|
||||
bufferWriter.write("uniquename=" + dst.getName());
|
||||
bufferWriter.write("\n");
|
||||
bufferWriter.write("filename=" + nameWithExtension);
|
||||
}
|
||||
Map<String, Object> params = new HashMap<String, Object>();
|
||||
params.put(StorageLayer.InstanceConfigKey, storage);
|
||||
|
||||
processor.configure("template processor", params);
|
||||
|
||||
FormatInfo info = processor.process(dstDir, null, name);
|
||||
TemplateLocation loc = new TemplateLocation(storage, dstDir);
|
||||
loc.create(1, true, dst.getName());
|
||||
loc.addFormat(info);
|
||||
loc.save();
|
||||
|
||||
TemplateProp prop = loc.getTemplateInfo();
|
||||
final TemplateObjectTO template = new TemplateObjectTO();
|
||||
template.setPath(dst.getPath() + File.separator + nameWithExtension);
|
||||
template.setFormat(ImageFormat.QCOW2);
|
||||
template.setSize(prop.getSize());
|
||||
template.setPhysicalSize(prop.getPhysicalSize());
|
||||
|
||||
return new CopyCmdAnswer(template);
|
||||
} catch (final Exception e) {
|
||||
final String error = "failed to backup snapshot: " + e.getMessage();
|
||||
SP_LOG(error);
|
||||
s_logger.debug(error);
|
||||
return new CopyCmdAnswer(cmd, e);
|
||||
} finally {
|
||||
if (srcPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", objectType, srcPath);
|
||||
}
|
||||
|
||||
if (secondaryPool != null) {
|
||||
try {
|
||||
secondaryPool.delete();
|
||||
} catch (final Exception e) {
|
||||
s_logger.debug("Failed to delete secondary storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
//
|
||||
// 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.resource.wrapper;
|
||||
|
||||
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolCopyVolumeToSecondaryCommand.class)
|
||||
public final class StorPoolCopyVolumeToSecondaryCommandWrapper extends CommandWrapper<StorPoolCopyVolumeToSecondaryCommand, CopyCmdAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolCopyVolumeToSecondaryCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public CopyCmdAnswer execute(final StorPoolCopyVolumeToSecondaryCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String srcPath = null;
|
||||
KVMStoragePool secondaryPool = null;
|
||||
|
||||
try {
|
||||
final VolumeObjectTO src = cmd.getSourceTO();
|
||||
final VolumeObjectTO dst = cmd.getDestinationTO();
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
final String destVolumePath = dst.getPath();
|
||||
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: src=" + src.getPath() + "dst=" + dst.getPath());
|
||||
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", "snapshot", src.getPath());
|
||||
srcPath = src.getPath();
|
||||
|
||||
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
|
||||
|
||||
final DataStoreTO dstDataStore = dst.getDataStore();
|
||||
|
||||
final KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: KVMStoragePoolManager " + poolMgr);
|
||||
KVMStoragePool destPool;
|
||||
if( dstDataStore instanceof NfsTO ) {
|
||||
destPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl());
|
||||
destPool.createFolder(destVolumePath);
|
||||
storagePoolMgr.deleteStoragePool(destPool.getType(), destPool.getUuid());
|
||||
destPool = storagePoolMgr.getStoragePoolByURI(dstDataStore.getUrl() + File.separator + destVolumePath);
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: Nfs destPool=%s ",destPool);
|
||||
} else if( dstDataStore instanceof PrimaryDataStoreTO ) {
|
||||
PrimaryDataStoreTO primaryDst = (PrimaryDataStoreTO)dstDataStore;
|
||||
destPool = poolMgr.getStoragePool(primaryDst.getPoolType(), dstDataStore.getUuid());
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: not Nfs destPool=%s " ,destPool);
|
||||
} else {
|
||||
return new CopyCmdAnswer("Don't know how to copy to " + dstDataStore.getClass().getName() + ", " + dst.getPath() );
|
||||
}
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: dstName=%s, dstProvisioningType=%s, srcSize=%s, dstUUID=%s, srcUUID=%s " ,dst.getName(), dst.getProvisioningType(), src.getSize(),dst.getUuid(), src.getUuid());
|
||||
|
||||
KVMPhysicalDisk newDisk = destPool.createPhysicalDisk(dst.getUuid(), dst.getProvisioningType(), src.getSize());
|
||||
SP_LOG("NewDisk path=%s, uuid=%s ", newDisk.getPath(), dst.getUuid());
|
||||
String destPath = newDisk.getPath();
|
||||
newDisk.setPath(dst.getUuid());
|
||||
|
||||
PhysicalDiskFormat destFormat = newDisk.getFormat();
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: KVMPhysicalDisk name=%s, format=%s, path=%s, destinationPath=%s " , newDisk.getName(), newDisk.getFormat(), newDisk.getPath(), destPath);
|
||||
QemuImgFile destFile = new QemuImgFile(destPath, destFormat);
|
||||
QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
|
||||
qemu.convert(srcFile, destFile);
|
||||
|
||||
final File file = new File(destPath);
|
||||
final long size = file.exists() ? file.length() : 0;
|
||||
dst.setPath(destVolumePath + File.separator + dst.getUuid());
|
||||
dst.setSize(size);
|
||||
|
||||
return new CopyCmdAnswer(dst);
|
||||
} catch (final Exception e) {
|
||||
final String error = "Failed to copy volume to secondary storage: " + e.getMessage();
|
||||
s_logger.debug(error);
|
||||
return new CopyCmdAnswer(error);
|
||||
} finally {
|
||||
if (srcPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", "snapshot", srcPath);
|
||||
}
|
||||
|
||||
if (secondaryPool != null) {
|
||||
try {
|
||||
SP_LOG("StorpoolCopyVolumeToSecondaryCommandWrapper.execute: secondaryPool=%s " , secondaryPool);
|
||||
secondaryPool.delete();
|
||||
} catch (final Exception e) {
|
||||
s_logger.debug("Failed to delete secondary storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
//
|
||||
// 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.resource.wrapper;
|
||||
|
||||
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolDownloadTemplateCommand.class)
|
||||
public final class StorPoolDownloadTemplateCommandWrapper extends CommandWrapper<StorPoolDownloadTemplateCommand, CopyCmdAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolDownloadTemplateCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public CopyCmdAnswer execute(final StorPoolDownloadTemplateCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String dstPath = null;
|
||||
KVMStoragePool secondaryPool = null;
|
||||
DataTO src = cmd.getSourceTO();
|
||||
DataTO dst = cmd.getDestinationTO();
|
||||
|
||||
try {
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
SP_LOG("StorpoolDownloadTemplateCommandWrapper.execute: src=" + src.getPath() + " dst=" + dst.getPath());
|
||||
|
||||
final DataStoreTO srcDataStore = src.getDataStore();
|
||||
if (!(srcDataStore instanceof NfsTO)) {
|
||||
return new CopyCmdAnswer("Download template to Storpool: Only NFS secondary supported at present!");
|
||||
}
|
||||
|
||||
final NfsTO nfsImageStore = (NfsTO)srcDataStore;
|
||||
final String tmplturl = nfsImageStore.getUrl() + File.separator + src.getPath();
|
||||
final int index = tmplturl.lastIndexOf("/");
|
||||
final String mountpoint = tmplturl.substring(0, index);
|
||||
String tmpltname = null;
|
||||
if (index < tmplturl.length() - 1) {
|
||||
tmpltname = tmplturl.substring(index + 1);
|
||||
}
|
||||
|
||||
secondaryPool = storagePoolMgr.getStoragePoolByURI(mountpoint);
|
||||
|
||||
KVMPhysicalDisk srcDisk = null;
|
||||
|
||||
if (tmpltname == null) {
|
||||
secondaryPool.refresh();
|
||||
final List<KVMPhysicalDisk> disks = secondaryPool.listPhysicalDisks();
|
||||
if (CollectionUtils.isEmpty(disks)) {
|
||||
SP_LOG("Failed to get volumes from pool: " + secondaryPool.getUuid());
|
||||
return new CopyCmdAnswer("Failed to get volumes from pool: " + secondaryPool.getUuid());
|
||||
}
|
||||
for (final KVMPhysicalDisk disk : disks) {
|
||||
if (disk.getName().endsWith("qcow2")) {
|
||||
srcDisk = disk;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
srcDisk = secondaryPool.getPhysicalDisk(tmpltname);
|
||||
}
|
||||
|
||||
if (srcDisk == null) {
|
||||
SP_LOG("Failed to get template from pool: " + secondaryPool.getUuid());
|
||||
return new CopyCmdAnswer("Failed to get template from pool: " + secondaryPool.getUuid());
|
||||
}
|
||||
|
||||
SP_LOG("got src path: " + srcDisk.getPath() + " srcSize " + srcDisk.getVirtualSize());
|
||||
|
||||
final QemuImgFile srcFile = new QemuImgFile(srcDisk.getPath(), srcDisk.getFormat());
|
||||
|
||||
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
|
||||
StorPoolStorageAdaptor.resize( Long.toString(srcDisk.getVirtualSize()), dst.getPath());
|
||||
|
||||
dstPath = dst.getPath();
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", cmd.getObjectType(), dstPath);
|
||||
|
||||
final QemuImgFile dstFile = new QemuImgFile(dstPath, PhysicalDiskFormat.RAW);
|
||||
|
||||
qemu.convert(srcFile, dstFile);
|
||||
return new CopyCmdAnswer(dst);
|
||||
} catch (final Exception e) {
|
||||
final String error = "Failed to copy template to primary: " + e.getMessage();
|
||||
s_logger.debug(error);
|
||||
return new CopyCmdAnswer(cmd, e);
|
||||
} finally {
|
||||
if (dstPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", cmd.getObjectType(), dstPath);
|
||||
}
|
||||
|
||||
if (secondaryPool != null) {
|
||||
try {
|
||||
secondaryPool.delete();
|
||||
} catch (final Exception e) {
|
||||
s_logger.debug("Failed to delete secondary storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
//
|
||||
// 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.resource.wrapper;
|
||||
|
||||
import static com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor.SP_LOG;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
//import java.io.File;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolDownloadVolumeCommand.class)
|
||||
public final class StorPoolDownloadVolumeCommandWrapper extends CommandWrapper<StorPoolDownloadVolumeCommand, CopyCmdAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolDownloadVolumeCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public CopyCmdAnswer execute(final StorPoolDownloadVolumeCommand cmd, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String dstPath = null;
|
||||
KVMStoragePool secondaryPool = null;
|
||||
|
||||
try {
|
||||
final VolumeObjectTO src = cmd.getSourceTO();
|
||||
final VolumeObjectTO dst = cmd.getDestinationTO();
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
SP_LOG("StorpoolDownloadVolumeCommandWrapper.execute: src=" + src.getPath() + " srcName=" + src.getName() + " dst=" + dst.getPath());
|
||||
|
||||
final DataStoreTO srcDataStore = src.getDataStore();
|
||||
KVMPhysicalDisk srcDisk = null;
|
||||
|
||||
if(srcDataStore instanceof NfsTO) {
|
||||
SP_LOG("StorpoolDownloadVolumeCommandWrapper.execute: srcIsNfsTO");
|
||||
|
||||
final String tmplturl = srcDataStore.getUrl() + srcDataStore.getPathSeparator() + src.getPath();
|
||||
final int index = tmplturl.lastIndexOf("/");
|
||||
final String mountpoint = tmplturl.substring(0, index);
|
||||
String tmpltname = null;
|
||||
if (index < tmplturl.length() - 1) {
|
||||
tmpltname = tmplturl.substring(index + 1);
|
||||
}
|
||||
|
||||
secondaryPool = storagePoolMgr.getStoragePoolByURI(mountpoint);
|
||||
|
||||
if (tmpltname == null) {
|
||||
secondaryPool.refresh();
|
||||
final List<KVMPhysicalDisk> disks = secondaryPool.listPhysicalDisks();
|
||||
if (disks == null || disks.isEmpty()) {
|
||||
SP_LOG("Failed to get volumes from pool: " + secondaryPool.getUuid());
|
||||
return new CopyCmdAnswer("Failed to get volumes from pool: " + secondaryPool.getUuid());
|
||||
}
|
||||
for (final KVMPhysicalDisk disk : disks) {
|
||||
if (disk.getName().endsWith("qcow2")) {
|
||||
srcDisk = disk;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
srcDisk = secondaryPool.getPhysicalDisk(tmpltname);
|
||||
}
|
||||
} else if(srcDataStore instanceof PrimaryDataStoreTO) {
|
||||
SP_LOG("SrcDisk is Primary Storage");
|
||||
PrimaryDataStoreTO primarySrc = (PrimaryDataStoreTO)srcDataStore;
|
||||
SP_LOG("StorpoolDownloadVolumeCommandWrapper.execute primarySrcPoolType=%s, uuid-%s ", primarySrc.getPoolType(), primarySrc.getUuid());
|
||||
final KVMStoragePoolManager poolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
srcDisk = poolMgr.getPhysicalDisk(primarySrc.getPoolType(), srcDataStore.getUuid(), src.getPath());
|
||||
SP_LOG("PhysicalDisk: disk=%s", srcDisk );
|
||||
} else {
|
||||
return new CopyCmdAnswer("Don't know how to copy from " + srcDataStore.getClass().getName() + ", " + src.getPath() );
|
||||
}
|
||||
|
||||
if (srcDisk == null) {
|
||||
SP_LOG("Failed to get src volume");
|
||||
return new CopyCmdAnswer("Failed to get src volume");
|
||||
}
|
||||
|
||||
SP_LOG("got src path: " + srcDisk.getPath() + " srcSize " + srcDisk.getVirtualSize());
|
||||
|
||||
String srcPath = null;
|
||||
boolean isRBDPool = srcDisk.getPool().getType() == StoragePoolType.RBD;
|
||||
if (isRBDPool) {
|
||||
KVMStoragePool srcPool = srcDisk.getPool();
|
||||
String rbdDestPath = srcPool.getSourceDir() + "/" + srcDisk.getName();
|
||||
srcPath = KVMPhysicalDisk.RBDStringBuilder(srcPool.getSourceHost(),
|
||||
srcPool.getSourcePort(),
|
||||
srcPool.getAuthUserName(),
|
||||
srcPool.getAuthSecret(),
|
||||
rbdDestPath);
|
||||
} else {
|
||||
srcPath = srcDisk.getPath();
|
||||
}
|
||||
final QemuImgFile srcFile = new QemuImgFile(srcPath, PhysicalDiskFormat.RAW);
|
||||
|
||||
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
|
||||
StorPoolStorageAdaptor.resize( Long.toString(srcDisk.getVirtualSize()), dst.getPath());
|
||||
|
||||
dstPath = dst.getPath();
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", "volume", dstPath);
|
||||
|
||||
final QemuImgFile dstFile = new QemuImgFile(dstPath, srcFile.getFormat());
|
||||
SP_LOG("SRC format=%s, DST format=%s",srcFile.getFormat(), dstFile.getFormat());
|
||||
qemu.convert(srcFile, dstFile);
|
||||
SP_LOG("StorpoolDownloadVolumeCommandWrapper VolumeObjectTO format=%s, hypervisor=%s", dst.getFormat(), dst.getHypervisorType());
|
||||
if (isRBDPool) {
|
||||
dst.setFormat(ImageFormat.QCOW2);
|
||||
}
|
||||
return new CopyCmdAnswer(dst);
|
||||
} catch (final Exception e) {
|
||||
final String error = "Failed to copy volume to primary: " + e.getMessage();
|
||||
SP_LOG(error);
|
||||
s_logger.debug(error);
|
||||
return new CopyCmdAnswer(cmd, e);
|
||||
} finally {
|
||||
if (dstPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", "volume", dstPath);
|
||||
}
|
||||
|
||||
if (secondaryPool != null) {
|
||||
try {
|
||||
secondaryPool.delete();
|
||||
} catch (final Exception e) {
|
||||
s_logger.debug("Failed to delete secondary storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.resource.wrapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.storage.StorPoolModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.api.storage.StorPoolModifyStoragePoolCommand;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.storage.template.TemplateProp;
|
||||
import com.cloud.utils.script.OutputInterpreter;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
@ResourceWrapper(handles = StorPoolModifyStoragePoolCommand.class)
|
||||
public final class StorPoolModifyStorageCommandWrapper extends CommandWrapper<StorPoolModifyStoragePoolCommand, Answer, LibvirtComputingResource> {
|
||||
private static final Logger log = Logger.getLogger(StorPoolModifyStorageCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public Answer execute(final StorPoolModifyStoragePoolCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String clusterId = getSpClusterId();
|
||||
if (clusterId == null) {
|
||||
log.debug(String.format("Could not get StorPool cluster id for a command $s", command.getClass()));
|
||||
return new Answer(command, false, "spNotFound");
|
||||
}
|
||||
try {
|
||||
String result = attachOrDetachVolume("attach", "volume", command.getVolumeName());
|
||||
if (result != null) {
|
||||
return new Answer(command, false, result);
|
||||
}
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
final KVMStoragePool storagepool =
|
||||
storagePoolMgr.createStoragePool(command.getPool().getUuid(), command.getPool().getHost(), command.getPool().getPort(), command.getPool().getPath(), command.getPool()
|
||||
.getUserInfo(), command.getPool().getType());
|
||||
if (storagepool == null) {
|
||||
log.debug(String.format("Did not find a storage pool [%s]", command.getPool().getId()));
|
||||
return new Answer(command, false, String.format("Failed to create storage pool [%s]", command.getPool().getId()));
|
||||
}
|
||||
|
||||
final Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();
|
||||
final StorPoolModifyStoragePoolAnswer answer = new StorPoolModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, clusterId);
|
||||
|
||||
return answer;
|
||||
} catch (Exception e) {
|
||||
log.debug(String.format("Could not modify storage due to %s", e.getMessage()));
|
||||
return new Answer(command, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSpClusterId() {
|
||||
Script sc = new Script("storpool_confget", 0, log);
|
||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||
|
||||
String SP_CLUSTER_ID = null;
|
||||
final String err = sc.execute(parser);
|
||||
if (err != null) {
|
||||
final String errMsg = String.format("Could not execute storpool_confget. Error: %s", err);
|
||||
log.warn(errMsg);
|
||||
StorPoolStorageAdaptor.SP_LOG("Could not execute storpool_confget. Error: %s", err);
|
||||
return SP_CLUSTER_ID;
|
||||
}
|
||||
|
||||
for (String line: parser.getLines().split("\n")) {
|
||||
String[] toks = line.split("=");
|
||||
if( toks.length != 2 ) {
|
||||
continue;
|
||||
}
|
||||
if (toks[0].equals("SP_CLUSTER_ID")) {
|
||||
SP_CLUSTER_ID = toks[1];
|
||||
return SP_CLUSTER_ID;
|
||||
}
|
||||
}
|
||||
return SP_CLUSTER_ID;
|
||||
}
|
||||
|
||||
public String attachOrDetachVolume(String command, String type, String volumeUuid) {
|
||||
final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeUuid, true);
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String err = null;
|
||||
Script sc = new Script("storpool", 300000, log);
|
||||
sc.add("-M");
|
||||
sc.add("-j");
|
||||
sc.add(command);
|
||||
sc.add(type, name);
|
||||
sc.add("here");
|
||||
sc.add("onRemoteAttached");
|
||||
sc.add("export");
|
||||
|
||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||
|
||||
String res = sc.execute(parser);
|
||||
|
||||
if (res != null) {
|
||||
if (!res.equals(Script.ERR_TIMEOUT)) {
|
||||
try {
|
||||
Set<Entry<String, JsonElement>> obj2 = new JsonParser().parse(res).getAsJsonObject().entrySet();
|
||||
for (Entry<String, JsonElement> entry : obj2) {
|
||||
if (entry.getKey().equals("error")) {
|
||||
res = entry.getValue().getAsJsonObject().get("name").getAsString();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
err = String.format("Unable to %s volume %s. Error: %s", command, name, res);
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
log.warn(err);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
//
|
||||
// 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.resource.wrapper;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
|
||||
@ResourceWrapper(handles = StorPoolResizeVolumeCommand.class)
|
||||
public final class StorPoolResizeVolumeCommandWrapper extends CommandWrapper<StorPoolResizeVolumeCommand, ResizeVolumeAnswer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(StorPoolResizeVolumeCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public ResizeVolumeAnswer execute(final StorPoolResizeVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
final String volid = command.getPath();
|
||||
final long newSize = command.getNewSize();
|
||||
final long currentSize = command.getCurrentSize();
|
||||
final String vmInstanceName = command.getInstanceName();
|
||||
final boolean shrinkOk = command.getShrinkOk();
|
||||
final StorageFilerTO spool = command.getPool();
|
||||
String volPath = null;
|
||||
|
||||
if (currentSize == newSize) {
|
||||
// nothing to do
|
||||
s_logger.info("No need to resize volume: current size " + currentSize + " is same as new size " + newSize);
|
||||
return new ResizeVolumeAnswer(command, true, "success", currentSize);
|
||||
}
|
||||
|
||||
try {
|
||||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
|
||||
final KVMPhysicalDisk vol = pool.getPhysicalDisk(volid);
|
||||
final String path = vol.getPath();
|
||||
volPath = path;
|
||||
if (!command.isAttached()) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("attach", "volume", path);
|
||||
}
|
||||
final Script resizecmd = new Script(libvirtComputingResource.getResizeVolumePath(), libvirtComputingResource.getCmdsTimeout(), s_logger);
|
||||
resizecmd.add("-s", String.valueOf(newSize));
|
||||
resizecmd.add("-c", String.valueOf(currentSize));
|
||||
resizecmd.add("-p", path);
|
||||
resizecmd.add("-t", "NOTIFYONLY");
|
||||
resizecmd.add("-r", String.valueOf(shrinkOk));
|
||||
resizecmd.add("-v", vmInstanceName);
|
||||
final String result = resizecmd.execute();
|
||||
|
||||
if (result != null) {
|
||||
return new ResizeVolumeAnswer(command, true, "Resize succeeded, but need reboot to notify guest");
|
||||
}
|
||||
|
||||
/* fetch new size as seen from libvirt, don't want to assume anything */
|
||||
pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
pool.refresh();
|
||||
|
||||
final long finalSize = pool.getPhysicalDisk(volid).getVirtualSize();
|
||||
s_logger.debug("after resize, size reports as " + finalSize + ", requested " + newSize);
|
||||
return new ResizeVolumeAnswer(command, true, "success", finalSize);
|
||||
} catch (final Exception e) {
|
||||
final String error = "Failed to resize volume: " + e.getMessage();
|
||||
s_logger.debug(error);
|
||||
return new ResizeVolumeAnswer(command, false, error);
|
||||
} finally {
|
||||
if (!command.isAttached() && volPath != null) {
|
||||
StorPoolStorageAdaptor.attachOrDetachVolume("detach", "volume", volPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,388 @@
|
||||
// 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.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.Storage.ProvisioningType;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.OutputInterpreter;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
@StorageAdaptorInfo(storagePoolType=StoragePoolType.SharedMountPoint)
|
||||
public class StorPoolStorageAdaptor implements StorageAdaptor {
|
||||
public static void SP_LOG(String fmt, Object... args) {
|
||||
try (PrintWriter spLogFile = new PrintWriter(new BufferedWriter(new FileWriter("/var/log/cloudstack/agent/storpool-agent.log", true)))) {
|
||||
final String line = String.format(fmt, args);
|
||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,ms").format(Calendar.getInstance().getTime());
|
||||
spLogFile.println(timeStamp +" "+line);
|
||||
spLogFile.flush();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger log = Logger.getLogger(StorPoolStorageAdaptor.class);
|
||||
|
||||
private static final Map<String, KVMStoragePool> storageUuidToStoragePool = new HashMap<String, KVMStoragePool>();
|
||||
|
||||
@Override
|
||||
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, StoragePoolType storagePoolType) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType);
|
||||
|
||||
StorPoolStoragePool storagePool = new StorPoolStoragePool(uuid, host, port, storagePoolType, this);
|
||||
storageUuidToStoragePool.put(uuid, storagePool);
|
||||
return storagePool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMStoragePool getStoragePool(String uuid) {
|
||||
SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s", uuid);
|
||||
return storageUuidToStoragePool.get(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
|
||||
SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo);
|
||||
return storageUuidToStoragePool.get(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteStoragePool(String uuid) {
|
||||
SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", uuid);
|
||||
return storageUuidToStoragePool.remove(uuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteStoragePool(KVMStoragePool pool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid());
|
||||
return deleteStoragePool(pool.getUuid());
|
||||
}
|
||||
|
||||
private static long getDeviceSize(final String devPath) {
|
||||
SP_LOG("StorpooolStorageAdaptor.getDeviceSize: path=%s", devPath);
|
||||
|
||||
if (getVolumeNameFromPath(devPath, true) == null) {
|
||||
return 0;
|
||||
}
|
||||
File file = new File(devPath);
|
||||
if (!file.exists()) {
|
||||
return 0;
|
||||
}
|
||||
Script sc = new Script("blockdev", 0, log);
|
||||
sc.add("--getsize64", devPath);
|
||||
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
|
||||
String res = sc.execute(parser);
|
||||
if (res != null) {
|
||||
SP_LOG("Unable to retrieve device size for %s. Res: %s", devPath, res);
|
||||
|
||||
log.debug(String.format("Unable to retrieve device size for %s. Res: %s", devPath, res));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Long.parseLong(parser.getLine());
|
||||
}
|
||||
|
||||
private static boolean waitForDeviceSymlink(String devPath) {
|
||||
final int numTries = 10;
|
||||
final int sleepTime = 100;
|
||||
|
||||
for(int i = 0; i < numTries; i++) {
|
||||
if (getDeviceSize(devPath) != 0) {
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (Exception ex) {
|
||||
// don't do anything
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getVolumeNameFromPath(final String volumeUuid, boolean tildeNeeded) {
|
||||
if (volumeUuid.startsWith("/dev/storpool/")) {
|
||||
return volumeUuid.split("/")[3];
|
||||
} else if (volumeUuid.startsWith("/dev/storpool-byid/")) {
|
||||
return tildeNeeded ? "~" + volumeUuid.split("/")[3] : volumeUuid.split("/")[3];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean attachOrDetachVolume(String command, String type, String volumeUuid) {
|
||||
final String name = getVolumeNameFromPath(volumeUuid, true);
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SP_LOG("StorpooolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name);
|
||||
|
||||
final int numTries = 10;
|
||||
final int sleepTime = 1000;
|
||||
String err = null;
|
||||
|
||||
for(int i = 0; i < numTries; i++) {
|
||||
Script sc = new Script("storpool", 0, log);
|
||||
sc.add("-M");
|
||||
sc.add(command);
|
||||
sc.add(type, name);
|
||||
sc.add("here");
|
||||
if (command.equals("attach")) {
|
||||
sc.add("onRemoteAttached");
|
||||
sc.add("export");
|
||||
}
|
||||
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
|
||||
String res = sc.execute(parser);
|
||||
if (res == null) {
|
||||
err = null;
|
||||
break;
|
||||
}
|
||||
err = String.format("Unable to %s volume %s. Error: %s", command, name, res);
|
||||
|
||||
if (command.equals("detach")) {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (Exception ex) {
|
||||
// don't do anything
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
SP_LOG(err);
|
||||
log.warn(err);
|
||||
throw new CloudRuntimeException(err);
|
||||
}
|
||||
|
||||
if (command.equals("attach")) {
|
||||
return waitForDeviceSymlink(volumeUuid);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean resize(String newSize, String volumeUuid ) {
|
||||
final String name = getVolumeNameFromPath(volumeUuid, true);
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SP_LOG("StorpooolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name);
|
||||
|
||||
Script sc = new Script("storpool", 0, log);
|
||||
sc.add("-M");
|
||||
sc.add("volume");
|
||||
sc.add(name);
|
||||
sc.add("update");
|
||||
sc.add("size");
|
||||
sc.add(newSize);
|
||||
sc.add("shrinkOk");
|
||||
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
String res = sc.execute(parser);
|
||||
if (res == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String err = String.format("Unable to resize volume %s. Error: %s", name, res);
|
||||
SP_LOG(err);
|
||||
log.warn(err);
|
||||
throw new CloudRuntimeException(err);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
|
||||
|
||||
log.debug(String.format("getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
|
||||
|
||||
final long deviceSize = getDeviceSize(volumeUuid);
|
||||
|
||||
KVMPhysicalDisk physicalDisk = new KVMPhysicalDisk(volumeUuid, volumeUuid, pool);
|
||||
physicalDisk.setFormat(PhysicalDiskFormat.RAW);
|
||||
physicalDisk.setSize(deviceSize);
|
||||
physicalDisk.setVirtualSize(deviceSize);
|
||||
return physicalDisk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
|
||||
SP_LOG("StorpooolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
|
||||
|
||||
log.debug(String.format("connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
|
||||
|
||||
return attachOrDetachVolume("attach", "volume", volumeUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
|
||||
|
||||
log.debug(String.format("disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
|
||||
return attachOrDetachVolume("detach", "volume", volumeUuid);
|
||||
}
|
||||
|
||||
public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
|
||||
String volumeUuid = volumeToDisconnect.get(DiskTO.UUID);
|
||||
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid);
|
||||
return attachOrDetachVolume("detach", "volume", volumeUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDiskByPath(String localPath) {
|
||||
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDiskByPath: localPath=%s", localPath);
|
||||
|
||||
log.debug(String.format("disconnectPhysicalDiskByPath: localPath=%s", localPath));
|
||||
return attachOrDetachVolume("detach", "volume", localPath);
|
||||
}
|
||||
|
||||
// The following do not apply for StorpoolStorageAdaptor?
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, KVMStoragePool pool, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createPhysicalDisk: uuid=%s, pool=%s, format=%s, size=%d", volumeUuid, pool, format, size);
|
||||
throw new UnsupportedOperationException("Creating a physical disk is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String volumeUuid, KVMStoragePool pool, Storage.ImageFormat format) {
|
||||
// Should only come here when cleaning-up StorPool snapshots associated with CloudStack templates.
|
||||
SP_LOG("StorpooolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format);
|
||||
final String name = getVolumeNameFromPath(volumeUuid, true);
|
||||
if (name == null) {
|
||||
final String err = String.format("StorpooolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid);
|
||||
SP_LOG(err);
|
||||
throw new UnsupportedOperationException(err);
|
||||
}
|
||||
|
||||
Script sc = new Script("storpool", 0, log);
|
||||
sc.add("-M");
|
||||
sc.add("snapshot", name);
|
||||
sc.add("delete", name);
|
||||
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
|
||||
String res = sc.execute(parser);
|
||||
if (res != null) {
|
||||
final String err = String.format("Unable to delete StorPool snapshot '%s'. Error: %s", name, res);
|
||||
SP_LOG(err);
|
||||
log.warn(err);
|
||||
throw new UnsupportedOperationException(err);
|
||||
}
|
||||
return true; // apparently ignored
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool);
|
||||
throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format,
|
||||
ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplate: template=%s, name=%s, fmt=%s, ptype=%s, size=%d, dst_pool=%s, to=%d",
|
||||
template, name, format, provisioningType, size, destPool.getUuid(), 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) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid());
|
||||
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) {
|
||||
SP_LOG("StorpooolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout);
|
||||
throw new UnsupportedOperationException("Copying a disk is not supported in this configuration.");
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid());
|
||||
throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refresh(KVMStoragePool pool) {
|
||||
SP_LOG("StorpooolStorageAdaptor.refresh: pool=%s", pool);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(String uuid, String path) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path);
|
||||
throw new UnsupportedOperationException("A folder cannot be created in this configuration.");
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name,
|
||||
KVMStoragePool destPool, int timeout) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot,
|
||||
snapshotName, name, destPool.getUuid());
|
||||
throw new UnsupportedOperationException(
|
||||
"Creating a disk from a snapshot is not supported in this configuration.");
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name,
|
||||
PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplateBacking: template=%s, name=%s, dst_pool=%s", template,
|
||||
name, destPool.getUuid());
|
||||
throw new UnsupportedOperationException(
|
||||
"Creating a disk from a template is not supported in this configuration.");
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, KVMStoragePool destPool,
|
||||
boolean isIso) {
|
||||
SP_LOG("StorpooolStorageAdaptor.createTemplateFromDirectDownloadFile: templateFilePath=%s, dst_pool=%s",
|
||||
templateFilePath, destPool.getUuid());
|
||||
throw new UnsupportedOperationException(
|
||||
"Creating a template from direct download is not supported in this configuration.");
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath,
|
||||
KVMStoragePool destPool, ImageFormat format, int timeout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(String uuid, String path, String localPath) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
// 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;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
|
||||
public class StorPoolStoragePool 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 StorPoolStoragePool(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 100L*(1024L*1024L*1024L*1024L*1024L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsed() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAvailable() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoragePoolType getType() {
|
||||
return _storagePoolType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthUserName() {
|
||||
return _authUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthSecret() {
|
||||
return _authSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceDir() {
|
||||
return _sourceDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalPath() {
|
||||
return _localPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhysicalDiskFormat getDefaultFormat() {
|
||||
return PhysicalDiskFormat.RAW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size) {
|
||||
return _storageAdaptor.createPhysicalDisk(name, this, format, provisioningType, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, Storage.ProvisioningType provisioningType, long size) {
|
||||
return _storageAdaptor.createPhysicalDisk(name, this, null, provisioningType, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectPhysicalDisk(String name, Map<String, String> details) {
|
||||
return _storageAdaptor.connectPhysicalDisk(name, this, details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid) {
|
||||
return _storageAdaptor.getPhysicalDisk(volumeUuid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnectPhysicalDisk(String volumeUuid) {
|
||||
return _storageAdaptor.disconnectPhysicalDisk(volumeUuid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format) {
|
||||
return _storageAdaptor.deletePhysicalDisk(volumeUuid, this, format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KVMPhysicalDisk> listPhysicalDisks() {
|
||||
return _storageAdaptor.listPhysicalDisks(_uuid, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refresh() {
|
||||
return _storageAdaptor.refresh(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return _storageAdaptor.deleteStoragePool(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(String path) {
|
||||
return _storageAdaptor.createFolder(_uuid, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExternalSnapshot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsConfigDriveIso() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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.collector;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
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.StorPoolHelper;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.db.DB;
|
||||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallbackNoReturn;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class StorPoolAbandonObjectsCollector extends ManagerBase implements Configurable {
|
||||
private static Logger log = Logger.getLogger(StorPoolAbandonObjectsCollector.class);
|
||||
@Inject
|
||||
private PrimaryDataStoreDao storagePoolDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
|
||||
private ScheduledExecutorService _volumeTagsUpdateExecutor;
|
||||
private static final String ABANDON_LOG = "/var/log/cloudstack/management/storpool-abandoned-objects";
|
||||
|
||||
|
||||
static final ConfigKey<Integer> volumeCheckupTagsInterval = new ConfigKey<Integer>("Advanced", Integer.class,
|
||||
"storpool.volume.tags.checkup", "86400",
|
||||
"Minimal interval (in seconds) to check and report if StorPool volume exists in CloudStack volumes database",
|
||||
false);
|
||||
static final ConfigKey<Integer> snapshotCheckupTagsInterval = new ConfigKey<Integer>("Advanced", Integer.class,
|
||||
"storpool.snapshot.tags.checkup", "86400",
|
||||
"Minimal interval (in seconds) to check and report if StorPool snapshot exists in CloudStack snapshots database",
|
||||
false);
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return StorPoolAbandonObjectsCollector.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] { volumeCheckupTagsInterval, snapshotCheckupTagsInterval };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
init();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
_volumeTagsUpdateExecutor = Executors.newScheduledThreadPool(2,
|
||||
new NamedThreadFactory("StorPoolAbandonObjectsCollector"));
|
||||
StorPoolHelper.appendLogger(log, ABANDON_LOG, "abandon");
|
||||
if (volumeCheckupTagsInterval.value() > 0) {
|
||||
_volumeTagsUpdateExecutor.scheduleAtFixedRate(new StorPoolVolumesTagsUpdate(),
|
||||
volumeCheckupTagsInterval.value(), volumeCheckupTagsInterval.value(), TimeUnit.SECONDS);
|
||||
}
|
||||
if (snapshotCheckupTagsInterval.value() > 0) {
|
||||
_volumeTagsUpdateExecutor.scheduleAtFixedRate(new StorPoolSnapshotsTagsUpdate(),
|
||||
snapshotCheckupTagsInterval.value(), snapshotCheckupTagsInterval.value(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
class StorPoolVolumesTagsUpdate extends ManagedContextRunnable {
|
||||
|
||||
@Override
|
||||
@DB
|
||||
protected void runInContext() {
|
||||
List<StoragePoolVO> spPools = storagePoolDao.findPoolsByProvider(StorPoolUtil.SP_PROVIDER_NAME);
|
||||
if (CollectionUtils.isEmpty(spPools)) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> volumes = new HashMap<>();
|
||||
for (StoragePoolVO storagePoolVO : spPools) {
|
||||
try {
|
||||
JsonArray arr = StorPoolUtil.volumesList(StorPoolUtil.getSpConnection(storagePoolVO.getUuid(), storagePoolVO.getId(), storagePoolDetailsDao, storagePoolDao));
|
||||
volumes.putAll(getStorPoolNamesAndCsTag(arr));
|
||||
} catch (Exception e) {
|
||||
log.debug(String.format("Could not collect abandon objects due to %s", e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
Transaction.execute(new TransactionCallbackNoReturn() {
|
||||
@Override
|
||||
public void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
|
||||
|
||||
try {
|
||||
PreparedStatement pstmt = txn.prepareAutoCloseStatement(
|
||||
"CREATE TEMPORARY TABLE `cloud`.`volumes1`(`id` bigint unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL,`tag` varchar(255) NOT NULL, PRIMARY KEY (`id`))");
|
||||
pstmt.executeUpdate();
|
||||
|
||||
pstmt = txn.prepareAutoCloseStatement(
|
||||
"CREATE TEMPORARY TABLE `cloud`.`volumes_on_host1`(`id` bigint unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL,`tag` varchar(255) NOT NULL, PRIMARY KEY (`id`))");
|
||||
pstmt.executeUpdate();
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.info(String.format("[ignored] SQL failed to delete vm work job: %s ",
|
||||
e.getLocalizedMessage()));
|
||||
} catch (Throwable e) {
|
||||
log.info(String.format("[ignored] caught an error during delete vm work job: %s",
|
||||
e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
PreparedStatement pstmt = txn.prepareStatement("INSERT INTO `cloud`.`volumes1` (name, tag) VALUES (?, ?)");
|
||||
PreparedStatement volumesOnHostpstmt = txn.prepareStatement("INSERT INTO `cloud`.`volumes_on_host1` (name, tag) VALUES (?, ?)");
|
||||
for (Map.Entry<String, String> volume : volumes.entrySet()) {
|
||||
if (volume.getValue().equals("volume")) {
|
||||
addRecordToDb(volume.getKey(), pstmt, volume.getValue(), true);
|
||||
} else if (volume.getValue().equals("check-volume-is-on-host")) {
|
||||
addRecordToDb(volume.getKey(), volumesOnHostpstmt, volume.getValue(), true);
|
||||
}
|
||||
}
|
||||
pstmt.executeBatch();
|
||||
volumesOnHostpstmt.executeBatch();
|
||||
String sql = "SELECT f.* FROM `cloud`.`volumes1` f LEFT JOIN `cloud`.`volumes` v ON f.name=v.path where v.path is NULL OR NOT state=?";
|
||||
findMissingRecordsInCS(txn, sql, "volume");
|
||||
|
||||
String sqlVolumeOnHost = "SELECT f.* FROM `cloud`.`volumes_on_host1` f LEFT JOIN `cloud`.`storage_pool_details` v ON f.name=v.value where v.value is NULL";
|
||||
findMissingRecordsInCS(txn, sqlVolumeOnHost, "volumes_on_host");
|
||||
} catch (SQLException e) {
|
||||
log.info(String.format("[ignored] SQL failed due to: %s ",
|
||||
e.getLocalizedMessage()));
|
||||
} catch (Throwable e) {
|
||||
log.info(String.format("[ignored] caught an error: %s",
|
||||
e.getLocalizedMessage()));
|
||||
} finally {
|
||||
try {
|
||||
PreparedStatement pstmt = txn.prepareStatement("DROP TABLE `cloud`.`volumes1`");
|
||||
pstmt.executeUpdate();
|
||||
pstmt = txn.prepareStatement("DROP TABLE `cloud`.`volumes_on_host1`");
|
||||
pstmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
txn.close();
|
||||
log.info(String.format("createTemporaryVolumeTable %s", e.getMessage()));
|
||||
}
|
||||
txn.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class StorPoolSnapshotsTagsUpdate extends ManagedContextRunnable {
|
||||
|
||||
@Override
|
||||
@DB
|
||||
protected void runInContext() {
|
||||
List<StoragePoolVO> spPools = storagePoolDao.findPoolsByProvider(StorPoolUtil.SP_PROVIDER_NAME);
|
||||
Map<String, String> snapshots = new HashMap<String, String>();
|
||||
if (CollectionUtils.isEmpty(spPools)) {
|
||||
return;
|
||||
}
|
||||
for (StoragePoolVO storagePoolVO : spPools) {
|
||||
try {
|
||||
JsonArray arr = StorPoolUtil.snapshotsList(StorPoolUtil.getSpConnection(storagePoolVO.getUuid(), storagePoolVO.getId(), storagePoolDetailsDao, storagePoolDao));
|
||||
snapshots.putAll(getStorPoolNamesAndCsTag(arr));
|
||||
} catch (Exception e) {
|
||||
log.debug(String.format("Could not collect abandon objects due to %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
Transaction.execute(new TransactionCallbackNoReturn() {
|
||||
@Override
|
||||
public void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
|
||||
|
||||
try{
|
||||
PreparedStatement pstmt = txn.prepareAutoCloseStatement(
|
||||
"CREATE TEMPORARY TABLE `cloud`.`snapshots1`(`id` bigint unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL,`tag` varchar(255) NOT NULL, PRIMARY KEY (`id`))");
|
||||
pstmt.executeUpdate();
|
||||
|
||||
pstmt = txn.prepareAutoCloseStatement(
|
||||
"CREATE TEMPORARY TABLE `cloud`.`vm_snapshots1`(`id` bigint unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL,`tag` varchar(255) NOT NULL, PRIMARY KEY (`id`))");
|
||||
pstmt.executeUpdate();
|
||||
|
||||
pstmt = txn.prepareAutoCloseStatement(
|
||||
"CREATE TEMPORARY TABLE `cloud`.`vm_templates1`(`id` bigint unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL,`tag` varchar(255) NOT NULL, PRIMARY KEY (`id`))");
|
||||
pstmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
log.info(String.format("[ignored] SQL failed to delete vm work job: %s ",
|
||||
e.getLocalizedMessage()));
|
||||
} catch (Throwable e) {
|
||||
log.info(String.format("[ignored] caught an error during delete vm work job: %s",
|
||||
e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
PreparedStatement snapshotsPstmt = txn.prepareStatement("INSERT INTO `cloud`.`snapshots1` (name, tag) VALUES (?, ?)");
|
||||
PreparedStatement groupSnapshotsPstmt = txn.prepareStatement("INSERT INTO `cloud`.`vm_snapshots1` (name, tag) VALUES (?, ?)");
|
||||
PreparedStatement templatePstmt = txn.prepareStatement("INSERT INTO `cloud`.`vm_templates1` (name, tag) VALUES (?, ?)");
|
||||
for (Map.Entry<String, String> snapshot : snapshots.entrySet()) {
|
||||
if (!snapshot.getValue().equals("group") && !snapshot.getValue().equals("template")) {
|
||||
addRecordToDb(snapshot.getKey(), snapshotsPstmt, snapshot.getValue(), true);
|
||||
} else if (snapshot.getValue().equals("group")) {
|
||||
addRecordToDb(snapshot.getKey(), groupSnapshotsPstmt, snapshot.getValue(), true);
|
||||
} else if (snapshot.getValue().equals("template")) {
|
||||
addRecordToDb(snapshot.getKey(), templatePstmt, snapshot.getValue(), true);
|
||||
}
|
||||
}
|
||||
snapshotsPstmt.executeBatch();
|
||||
groupSnapshotsPstmt.executeBatch();
|
||||
templatePstmt.executeBatch();
|
||||
|
||||
String sqlSnapshots = "SELECT f.* FROM `cloud`.`snapshots1` f LEFT JOIN `cloud`.`snapshot_details` v ON f.name=v.value where v.value is NULL";
|
||||
findMissingRecordsInCS(txn, sqlSnapshots, "snapshot");
|
||||
|
||||
String sqlVmSnapshots = "SELECT f.* FROM `cloud`.`vm_snapshots1` f LEFT JOIN `cloud`.`vm_snapshot_details` v ON f.name=v.value where v.value is NULL";
|
||||
findMissingRecordsInCS(txn, sqlVmSnapshots, "snapshot");
|
||||
|
||||
String sqlTemplates = "SELECT temp.*"
|
||||
+ " FROM `cloud`.`vm_templates1` temp"
|
||||
+ " LEFT JOIN `cloud`.`template_store_ref` store"
|
||||
+ " ON temp.name=store.local_path"
|
||||
+ " LEFT JOIN `cloud`.`template_spool_ref` spool"
|
||||
+ " ON temp.name=spool.local_path"
|
||||
+ " where store.local_path is NULL"
|
||||
+ " and spool.local_path is NULL";
|
||||
findMissingRecordsInCS(txn, sqlTemplates, "snapshot");
|
||||
} catch (SQLException e) {
|
||||
log.info(String.format("[ignored] SQL failed due to: %s ",
|
||||
e.getLocalizedMessage()));
|
||||
} catch (Throwable e) {
|
||||
log.info(String.format("[ignored] caught an error: %s",
|
||||
e.getLocalizedMessage()));
|
||||
} finally {
|
||||
try {
|
||||
PreparedStatement pstmt = txn.prepareStatement("DROP TABLE `cloud`.`snapshots1`");
|
||||
pstmt.executeUpdate();
|
||||
pstmt = txn.prepareStatement("DROP TABLE `cloud`.`vm_snapshots1`");
|
||||
pstmt.executeUpdate();
|
||||
pstmt = txn.prepareStatement("DROP TABLE `cloud`.`vm_templates1`");
|
||||
pstmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
txn.close();
|
||||
log.info(String.format("createTemporaryVolumeTable %s", e.getMessage()));
|
||||
}
|
||||
txn.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addRecordToDb(String name, PreparedStatement pstmt, String tag, boolean pathNeeded)
|
||||
throws SQLException {
|
||||
name = name.startsWith("~") ? name.split("~")[1] : name;
|
||||
pstmt.setString(1, pathNeeded ? StorPoolUtil.devPath(name) : name);
|
||||
pstmt.setString(2, tag);
|
||||
pstmt.addBatch();
|
||||
}
|
||||
|
||||
private void findMissingRecordsInCS(TransactionLegacy txn, String sql, String object) throws SQLException {
|
||||
ResultSet rs;
|
||||
PreparedStatement pstmt2 = txn.prepareStatement(sql);
|
||||
if (object.equals("volume")) {
|
||||
pstmt2.setString(1, "Ready");
|
||||
}
|
||||
rs = pstmt2.executeQuery();
|
||||
String name = null;
|
||||
while (rs.next()) {
|
||||
name = rs.getString(2);
|
||||
log.info(String.format(
|
||||
"CloudStack does not know about StorPool %s %s, it had to be a %s", object, name, rs.getString(3)));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String,String> getStorPoolNamesAndCsTag(JsonArray arr) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
String name = arr.get(i).getAsJsonObject().get("name").getAsString();
|
||||
String tag = null;
|
||||
if (!name.startsWith("*") && !name.contains("@")) {
|
||||
JsonObject tags = arr.get(i).getAsJsonObject().get("tags").getAsJsonObject();
|
||||
if (tags != null && tags.getAsJsonPrimitive("cs") != null && !(arr.get(i).getAsJsonObject().get("deleted") != null && arr.get(i).getAsJsonObject().get("deleted").getAsBoolean())) {
|
||||
tag = tags.getAsJsonPrimitive("cs").getAsString();
|
||||
map.put(name, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,976 @@
|
||||
/*
|
||||
* 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 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.EndPoint;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
||||
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.TemplateInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.storage.RemoteHostEndPoint;
|
||||
import org.apache.cloudstack.storage.command.CommandResult;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
|
||||
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.storage.volume.VolumeObject;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
|
||||
import com.cloud.agent.api.to.DataObjectType;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.server.ResourceTag;
|
||||
import com.cloud.server.ResourceTag.ResourceObjectType;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.ResizeVolumePayload;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.VMTemplateDetailVO;
|
||||
import com.cloud.storage.VMTemplateStoragePoolVO;
|
||||
import com.cloud.storage.VolumeDetailVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||
import com.cloud.storage.dao.VMTemplateDetailsDao;
|
||||
import com.cloud.storage.dao.VMTemplatePoolDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.dao.VolumeDetailsDao;
|
||||
import com.cloud.tags.dao.ResourceTagDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachineManager;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
|
||||
private static final Logger log = Logger.getLogger(StorPoolPrimaryDataStoreDriver.class);
|
||||
|
||||
@Inject
|
||||
private VolumeDao volumeDao;
|
||||
@Inject
|
||||
private StorageManager storageMgr;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao primaryStoreDao;
|
||||
@Inject
|
||||
private EndPointSelector selector;
|
||||
@Inject
|
||||
private ConfigurationDao configDao;
|
||||
@Inject
|
||||
private TemplateDataStoreDao vmTemplateDataStoreDao;
|
||||
@Inject
|
||||
private VMInstanceDao vmInstanceDao;
|
||||
@Inject
|
||||
private ClusterDao clusterDao;
|
||||
@Inject
|
||||
private HostDao hostDao;
|
||||
@Inject
|
||||
private ResourceTagDao _resourceTagDao;
|
||||
@Inject
|
||||
private SnapshotDetailsDao _snapshotDetailsDao;
|
||||
@Inject
|
||||
private SnapshotDataStoreDao snapshotDataStoreDao;
|
||||
@Inject
|
||||
private VolumeDetailsDao volumeDetailsDao;
|
||||
@Inject
|
||||
private VMTemplateDetailsDao vmTemplateDetailsDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
@Inject
|
||||
private VMTemplatePoolDao vmTemplatePoolDao;
|
||||
|
||||
@Override
|
||||
public Map<String, String> getCapabilities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataTO getTO(DataObject data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStoreTO getStoreTO(DataStore store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsedBytes(StoragePool storagePool) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUsedIops(StoragePool storagePool) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean grantAccess(DataObject data, Host host, DataStore dataStore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeAccess(DataObject data, Host host, DataStore dataStore) {
|
||||
}
|
||||
|
||||
private void updateStoragePool(final long poolId, final long deltaUsedBytes) {
|
||||
StoragePoolVO storagePool = primaryStoreDao.findById(poolId);
|
||||
final long capacity = storagePool.getCapacityBytes();
|
||||
final long used = storagePool.getUsedBytes() + deltaUsedBytes;
|
||||
|
||||
storagePool.setUsedBytes(used < 0 ? 0 : (used > capacity ? capacity : used));
|
||||
primaryStoreDao.update(poolId, storagePool);
|
||||
}
|
||||
|
||||
private String getVMInstanceUUID(Long id) {
|
||||
return id != null ? vmInstanceDao.findById(id).getUuid() : null;
|
||||
}
|
||||
|
||||
protected void _completeResponse(final CreateObjectAnswer answer, final String err, final AsyncCompletionCallback<CommandResult> callback)
|
||||
{
|
||||
final CreateCmdResult res = new CreateCmdResult(null, answer);
|
||||
res.setResult(err);
|
||||
callback.complete(res);
|
||||
}
|
||||
|
||||
protected void completeResponse(final DataTO result, final AsyncCompletionCallback<CommandResult> callback)
|
||||
{
|
||||
_completeResponse(new CreateObjectAnswer(result), null, callback);
|
||||
}
|
||||
|
||||
protected void completeResponse(final String err, final AsyncCompletionCallback<CommandResult> callback)
|
||||
{
|
||||
_completeResponse(new CreateObjectAnswer(err), err, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) {
|
||||
return dataObject.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChapInfo getChapInfo(DataObject dataObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
|
||||
String path = null;
|
||||
String err = null;
|
||||
if (data.getType() == DataObjectType.VOLUME) {
|
||||
try {
|
||||
VolumeInfo vinfo = (VolumeInfo)data;
|
||||
String name = vinfo.getUuid();
|
||||
Long size = vinfo.getSize();
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.createAsync volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", vinfo.getName(), vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), conn.getTemplateName());
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(name, null, size, getVMInstanceUUID(vinfo.getInstanceId()), null, "volume", vinfo.getMaxIops(), conn);
|
||||
if (resp.getError() == null) {
|
||||
String volumeName = StorPoolUtil.getNameFromResponse(resp, false);
|
||||
path = StorPoolUtil.devPath(volumeName);
|
||||
|
||||
VolumeVO volume = volumeDao.findById(vinfo.getId());
|
||||
volume.setPoolId(dataStore.getId());
|
||||
volume.setPoolType(StoragePoolType.SharedMountPoint);
|
||||
volume.setPath(path);
|
||||
volumeDao.update(volume.getId(), volume);
|
||||
|
||||
updateStoragePool(dataStore.getId(), size);
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.createAsync volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", volumeName, vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), conn.getTemplateName());
|
||||
} else {
|
||||
err = String.format("Could not create StorPool volume %s. Error: %s", name, resp.getError());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
err = String.format("Could not create volume due to %s", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
err = String.format("Invalid object type \"%s\" passed to createAsync", data.getType());
|
||||
}
|
||||
|
||||
CreateCmdResult res = new CreateCmdResult(path, new Answer(null, err == null, err));
|
||||
res.setResult(err);
|
||||
if (callback != null) {
|
||||
callback.complete(res);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
|
||||
String path = null;
|
||||
String err = null;
|
||||
ResizeVolumeAnswer answer = null;
|
||||
|
||||
if (data.getType() == DataObjectType.VOLUME) {
|
||||
VolumeObject vol = (VolumeObject)data;
|
||||
StoragePool pool = (StoragePool)data.getDataStore();
|
||||
ResizeVolumePayload payload = (ResizeVolumePayload)vol.getpayload();
|
||||
|
||||
final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true);
|
||||
final long oldSize = vol.getSize();
|
||||
Long oldMaxIops = vol.getMaxIops();
|
||||
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s", name, vol.getUuid(), oldSize, payload.newSize, payload.shrinkOk);
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.volumeUpdate(name, payload.newSize, payload.shrinkOk, payload.newMaxIops, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not resize StorPool volume %s. Error: %s", name, resp.getError());
|
||||
} else {
|
||||
StorPoolResizeVolumeCommand resizeCmd = new StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), payload.newSize, payload.shrinkOk,
|
||||
payload.instanceName, payload.hosts == null ? false : true);
|
||||
answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, payload.hosts, resizeCmd);
|
||||
|
||||
if (answer == null || !answer.getResult()) {
|
||||
err = answer != null ? answer.getDetails() : "return a null answer, resize failed for unknown reason";
|
||||
} else {
|
||||
path = StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false));
|
||||
|
||||
vol.setSize(payload.newSize);
|
||||
vol.update();
|
||||
if (payload.newMaxIops != null) {
|
||||
VolumeVO volume = volumeDao.findById(vol.getId());
|
||||
volume.setMaxIops(payload.newMaxIops);
|
||||
volumeDao.update(volume.getId(), volume);
|
||||
}
|
||||
|
||||
updateStoragePool(vol.getPoolId(), payload.newSize - oldSize);
|
||||
}
|
||||
}
|
||||
if (err != null) {
|
||||
// try restoring volume to its initial size
|
||||
resp = StorPoolUtil.volumeUpdate(name, oldSize, true, oldMaxIops, conn);
|
||||
if (resp.getError() != null) {
|
||||
log.debug(String.format("Could not resize StorPool volume %s back to its original size. Error: %s", name, resp.getError()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("sending resize command failed", e);
|
||||
err = e.toString();
|
||||
}
|
||||
} else {
|
||||
err = String.format("Invalid object type \"%s\" passed to resize", data.getType());
|
||||
}
|
||||
|
||||
CreateCmdResult res = new CreateCmdResult(path, answer);
|
||||
res.setResult(err);
|
||||
callback.complete(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
|
||||
String err = null;
|
||||
if (data.getType() == DataObjectType.VOLUME) {
|
||||
VolumeInfo vinfo = (VolumeInfo)data;
|
||||
String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.deleteAsync delete volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s dataStore=%s", name, vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), dataStore.getUuid());
|
||||
if (name == null) {
|
||||
name = vinfo.getUuid();
|
||||
}
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.volumeDelete(name, conn);
|
||||
if (resp.getError() == null) {
|
||||
updateStoragePool(dataStore.getId(), - vinfo.getSize());
|
||||
VolumeDetailVO detail = volumeDetailsDao.findDetail(vinfo.getId(), StorPoolUtil.SP_PROVIDER_NAME);
|
||||
if (detail != null) {
|
||||
volumeDetailsDao.remove(detail.getId());
|
||||
}
|
||||
} else {
|
||||
if (!resp.getError().getName().equalsIgnoreCase("objectDoesNotExist")) {
|
||||
err = String.format("Could not delete StorPool volume %s. Error: %s", name, resp.getError());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
err = String.format("Could not delete volume due to %s", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
err = String.format("Invalid DataObjectType \"%s\" passed to deleteAsync", data.getType());
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
log.error(err);
|
||||
StorPoolUtil.spLog(err);
|
||||
}
|
||||
|
||||
CommandResult res = new CommandResult();
|
||||
res.setResult(err);
|
||||
callback.complete(res);
|
||||
}
|
||||
|
||||
private void logDataObject(final String pref, DataObject data) {
|
||||
final DataStore dstore = data.getDataStore();
|
||||
String name = null;
|
||||
Long size = null;
|
||||
|
||||
if (data.getType() == DataObjectType.VOLUME) {
|
||||
VolumeInfo vinfo = (VolumeInfo)data;
|
||||
name = vinfo.getName();
|
||||
size = vinfo.getSize();
|
||||
} else if (data.getType() == DataObjectType.SNAPSHOT) {
|
||||
SnapshotInfo sinfo = (SnapshotInfo)data;
|
||||
name = sinfo.getName();
|
||||
size = sinfo.getSize();
|
||||
} else if (data.getType() == DataObjectType.TEMPLATE) {
|
||||
TemplateInfo tinfo = (TemplateInfo)data;
|
||||
name = tinfo.getName();
|
||||
size = tinfo.getSize();
|
||||
}
|
||||
|
||||
StorPoolUtil.spLog("%s: name=%s, size=%s, uuid=%s, type=%s, dstore=%s:%s:%s", pref, name, size, data.getUuid(), data.getType(), dstore.getUuid(), dstore.getName(), dstore.getRole());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCopy(DataObject srcData, DataObject dstData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc:");
|
||||
logDataObject("SRC", srcData);
|
||||
logDataObject("DST", dstData);
|
||||
|
||||
final DataObjectType srcType = srcData.getType();
|
||||
final DataObjectType dstType = dstData.getType();
|
||||
String err = null;
|
||||
Answer answer = null;
|
||||
StorageSubSystemCommand cmd = null;
|
||||
|
||||
try {
|
||||
if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.VOLUME) {
|
||||
SnapshotInfo sinfo = (SnapshotInfo)srcData;
|
||||
final String snapshotName = StorPoolHelper.getSnapshotName(srcData.getId(), srcData.getUuid(), snapshotDataStoreDao, _snapshotDetailsDao);
|
||||
|
||||
VolumeInfo vinfo = (VolumeInfo)dstData;
|
||||
final String volumeName = vinfo.getUuid();
|
||||
final Long size = vinfo.getSize();
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(volumeName, snapshotName, size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn);
|
||||
if (resp.getError() == null) {
|
||||
updateStoragePool(dstData.getDataStore().getId(), size);
|
||||
|
||||
VolumeObjectTO to = (VolumeObjectTO)dstData.getTO();
|
||||
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
to.setSize(size);
|
||||
|
||||
answer = new CopyCmdAnswer(to);
|
||||
StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", StorPoolUtil.getNameFromResponse(resp, false), to.getUuid(), snapshotName, sinfo.getUuid());
|
||||
} else if (resp.getError().getName().equals("objectDoesNotExist")) {
|
||||
//check if snapshot is on secondary storage
|
||||
StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snopshot on secondary storage", snapshotName);
|
||||
SnapshotDataStoreVO snap = snapshotDataStoreDao.findBySnapshot(sinfo.getId(), DataStoreRole.Image);
|
||||
if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
|
||||
resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn);
|
||||
if (resp.getError() == null) {
|
||||
VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO();
|
||||
dstTO.setSize(size);
|
||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
|
||||
|
||||
EndPoint ep = selector.select(srcData, dstData);
|
||||
if (ep == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = ep.sendMessage(cmd);
|
||||
}
|
||||
|
||||
if (answer != null && answer.getResult()) {
|
||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
||||
if (resp2.getError() != null) {
|
||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", srcData.getUuid(), resp2.getError());
|
||||
} else {
|
||||
String name = StorPoolUtil.getNameFromResponse(resp, false);
|
||||
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(sinfo.getId(), sinfo.getUuid());
|
||||
if (snapshotDetails != null) {
|
||||
StorPoolHelper.updateSnapshotDetailsValue(snapshotDetails.getId(), StorPoolUtil.devPath(name), "snapshot");
|
||||
}else {
|
||||
StorPoolHelper.addSnapshotDetails(sinfo.getId(), sinfo.getUuid(), StorPoolUtil.devPath(name), _snapshotDetailsDao);
|
||||
}
|
||||
resp = StorPoolUtil.volumeCreate(volumeName, StorPoolUtil.getNameFromResponse(resp, true), size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn);
|
||||
if (resp.getError() == null) {
|
||||
updateStoragePool(dstData.getDataStore().getId(), size);
|
||||
|
||||
VolumeObjectTO to = (VolumeObjectTO) dstData.getTO();
|
||||
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
to.setSize(size);
|
||||
// successfully downloaded snapshot to primary storage
|
||||
answer = new CopyCmdAnswer(to);
|
||||
StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", name, to.getUuid(), snapshotName, sinfo.getUuid());
|
||||
|
||||
} else {
|
||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
|
||||
}
|
||||
} else {
|
||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
||||
}
|
||||
} else {
|
||||
err = String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", snapshotName);
|
||||
}
|
||||
} else {
|
||||
err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError());
|
||||
}
|
||||
} else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) {
|
||||
// bypass secondary storage
|
||||
if (StorPoolConfigurationManager.BypassSecondaryStorage.value()) {
|
||||
SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData.getTO();
|
||||
answer = new CopyCmdAnswer(snapshot);
|
||||
} else {
|
||||
// copy snapshot to secondary storage (backup snapshot)
|
||||
cmd = new StorPoolBackupSnapshotCommand(srcData.getTO(), dstData.getTO(), StorPoolHelper.getTimeout(StorPoolHelper.BackupSnapshotWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
final String snapName = StorPoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) srcData).getPath(), true);
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
try {
|
||||
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(snapName, clusterDao);
|
||||
EndPoint ep = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData);
|
||||
if (ep == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = ep.sendMessage(cmd);
|
||||
// if error during snapshot backup, cleanup the StorPool snapshot
|
||||
if (answer != null && !answer.getResult()) {
|
||||
StorPoolUtil.spLog(String.format("Error while backing-up snapshot '%s' - cleaning up StorPool snapshot. Error: %s", snapName, answer.getDetails()));
|
||||
SpApiResponse resp = StorPoolUtil.snapshotDelete(snapName, conn);
|
||||
if (resp.getError() != null) {
|
||||
final String err2 = String.format("Failed to cleanup StorPool snapshot '%s'. Error: %s.", snapName, resp.getError());
|
||||
log.error(err2);
|
||||
StorPoolUtil.spLog(err2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CloudRuntimeException e) {
|
||||
err = e.getMessage();
|
||||
}
|
||||
}
|
||||
} else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.TEMPLATE) {
|
||||
// create template from volume
|
||||
VolumeObjectTO volume = (VolumeObjectTO) srcData.getTO();
|
||||
TemplateObjectTO template = (TemplateObjectTO) dstData.getTO();
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
|
||||
|
||||
|
||||
cmd = new StorPoolBackupTemplateFromSnapshotCommand(volume, template,
|
||||
StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
try {
|
||||
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(volumeName, clusterDao);
|
||||
EndPoint ep2 = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData);
|
||||
if (ep2 == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = ep2.sendMessage(cmd);
|
||||
if (answer != null && answer.getResult()) {
|
||||
SpApiResponse resSnapshot = StorPoolUtil.volumeSnapshot(volumeName, template.getUuid(), null, "template", "no", conn);
|
||||
if (resSnapshot.getError() != null) {
|
||||
log.debug(String.format("Could not snapshot volume with ID=%s", volume.getId()));
|
||||
StorPoolUtil.spLog("Volume snapshot failed with error=%s", resSnapshot.getError().getDescr());
|
||||
err = resSnapshot.getError().getDescr();
|
||||
}
|
||||
else {
|
||||
StorPoolHelper.updateVmStoreTemplate(template.getId(), template.getDataStore().getRole(), StorPoolUtil.devPath(StorPoolUtil.getSnapshotNameFromResponse(resSnapshot, false, StorPoolUtil.GLOBAL_ID)), vmTemplateDataStoreDao);
|
||||
vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(srcData.getDataStore().getId()), false));
|
||||
}
|
||||
}else {
|
||||
err = "Could not copy template to secondary " + answer.getResult();
|
||||
}
|
||||
}
|
||||
}catch (CloudRuntimeException e) {
|
||||
err = e.getMessage();
|
||||
}
|
||||
} else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.TEMPLATE) {
|
||||
// copy template to primary storage
|
||||
TemplateInfo tinfo = (TemplateInfo)dstData;
|
||||
Long size = tinfo.getSize();
|
||||
if(size == null || size == 0)
|
||||
size = 1L*1024*1024*1024;
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
TemplateDataStoreVO templDataStoreVO = vmTemplateDataStoreDao.findByTemplate(tinfo.getId(), DataStoreRole.Image);
|
||||
|
||||
String snapshotName = (templDataStoreVO != null && templDataStoreVO.getLocalDownloadPath() != null)
|
||||
? StorPoolStorageAdaptor.getVolumeNameFromPath(templDataStoreVO.getLocalDownloadPath(), true)
|
||||
: null;
|
||||
String name = tinfo.getUuid();
|
||||
|
||||
SpApiResponse resp = null;
|
||||
if (snapshotName != null) {
|
||||
//no need to copy volume from secondary, because we have it already on primary. Just need to create a child snapshot from it.
|
||||
//The child snapshot is needed when configuration "storage.cleanup.enabled" is true, not to clean the base snapshot and to lose everything
|
||||
resp = StorPoolUtil.volumeCreate(name, snapshotName, size, null, "no", "template", null, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
||||
} else {
|
||||
String volumeNameToSnapshot = StorPoolUtil.getNameFromResponse(resp, true);
|
||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(volumeNameToSnapshot, conn);
|
||||
if (resp2.getError() != null) {
|
||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", name, resp2.getError());
|
||||
} else {
|
||||
StorPoolUtil.spLog("Storpool snapshot [%s] for a template exists. Creating template on Storpool with name [%s]", tinfo.getUuid(), name);
|
||||
TemplateObjectTO dstTO = (TemplateObjectTO) dstData.getTO();
|
||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
dstTO.setSize(size);
|
||||
answer = new CopyCmdAnswer(dstTO);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp = StorPoolUtil.volumeCreate(name, null, size, null, "no", "template", null, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
||||
} else {
|
||||
TemplateObjectTO dstTO = (TemplateObjectTO)dstData.getTO();
|
||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
dstTO.setSize(size);
|
||||
|
||||
cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume");
|
||||
|
||||
EndPoint ep = selector.select(srcData, dstData);
|
||||
if (ep == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = ep.sendMessage(cmd);
|
||||
}
|
||||
|
||||
if (answer != null && answer.getResult()) {
|
||||
// successfully downloaded template to primary storage
|
||||
SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
||||
if (resp2.getError() != null) {
|
||||
err = String.format("Could not freeze Storpool volume %s. Error: %s", name, resp2.getError());
|
||||
}
|
||||
} else {
|
||||
err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned.";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err != null) {
|
||||
resp = StorPoolUtil.volumeDelete(StorPoolUtil.getNameFromResponse(resp, true), conn);
|
||||
if (resp.getError() != null) {
|
||||
log.warn(String.format("Could not clean-up Storpool volume %s. Error: %s", name, resp.getError()));
|
||||
}
|
||||
}
|
||||
} else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) {
|
||||
// create volume from template on Storpool PRIMARY
|
||||
TemplateInfo tinfo = (TemplateInfo)srcData;
|
||||
|
||||
VolumeInfo vinfo = (VolumeInfo)dstData;
|
||||
VMTemplateStoragePoolVO templStoragePoolVO = StorPoolHelper.findByPoolTemplate(vinfo.getPoolId(), tinfo.getId());
|
||||
final String parentName = templStoragePoolVO.getLocalDownloadPath() !=null ? StorPoolStorageAdaptor.getVolumeNameFromPath(templStoragePoolVO.getLocalDownloadPath(), true) : StorPoolStorageAdaptor.getVolumeNameFromPath(templStoragePoolVO.getInstallPath(), true);
|
||||
final String name = vinfo.getUuid();
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
Long snapshotSize = StorPoolUtil.snapshotSize(parentName, conn);
|
||||
if (snapshotSize == null) {
|
||||
err = String.format("Snapshot=%s does not exist on StorPool. Will recreate it first on primary", parentName);
|
||||
vmTemplatePoolDao.remove(templStoragePoolVO.getId());
|
||||
}
|
||||
if (err == null) {
|
||||
long size = vinfo.getSize();
|
||||
if( size < snapshotSize )
|
||||
{
|
||||
StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize));
|
||||
size = snapshotSize;
|
||||
}
|
||||
StorPoolUtil.spLog(String.format("volume size is: %d", size));
|
||||
Long vmId = vinfo.getInstanceId();
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId),
|
||||
getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn);
|
||||
if (resp.getError() == null) {
|
||||
updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize());
|
||||
|
||||
VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO();
|
||||
to.setSize(vinfo.getSize());
|
||||
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
|
||||
answer = new CopyCmdAnswer(to);
|
||||
} else {
|
||||
err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError());
|
||||
}
|
||||
}
|
||||
} else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) {
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.copyAsync src Data Store=%s", srcData.getDataStore().getDriver());
|
||||
VolumeInfo dstInfo = (VolumeInfo)dstData;
|
||||
VolumeInfo srcInfo = (VolumeInfo) srcData;
|
||||
|
||||
if( !(srcData.getDataStore().getDriver() instanceof StorPoolPrimaryDataStoreDriver ) ) {
|
||||
// copy "VOLUME" to primary storage
|
||||
String name = dstInfo.getUuid();
|
||||
Long size = dstInfo.getSize();
|
||||
if(size == null || size == 0)
|
||||
size = 1L*1024*1024*1024;
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
Long vmId = srcInfo.getInstanceId();
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(name, null, size, getVMInstanceUUID(vmId), getVcPolicyTag(vmId), "volume", dstInfo.getMaxIops(), conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not create Storpool volume for CS template %s. Error: %s", name, resp.getError());
|
||||
} else {
|
||||
//updateVolume(dstData.getId());
|
||||
VolumeObjectTO dstTO = (VolumeObjectTO)dstData.getTO();
|
||||
dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
|
||||
dstTO.setSize(size);
|
||||
|
||||
cmd = new StorPoolDownloadVolumeCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
EndPoint ep = selector.select(srcData, dstData);
|
||||
|
||||
if( ep == null) {
|
||||
StorPoolUtil.spLog("select(srcData, dstData) returned NULL. trying srcOnly");
|
||||
ep = selector.select(srcData); // Storpool is zone
|
||||
}
|
||||
if (ep == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
StorPoolUtil.spLog("Sending command to %s", ep.getHostAddr());
|
||||
answer = ep.sendMessage(cmd);
|
||||
|
||||
if (answer != null && answer.getResult()) {
|
||||
// successfully downloaded volume to primary storage
|
||||
} else {
|
||||
err = answer != null ? answer.getDetails() : "Unknown error while downloading volume. Null answer returned.";
|
||||
}
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
SpApiResponse resp3 = StorPoolUtil.volumeDelete(name, conn);
|
||||
if (resp3.getError() != null) {
|
||||
log.warn(String.format("Could not clean-up Storpool volume %s. Error: %s", name, resp3.getError()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// download volume - first copies to secondary
|
||||
VolumeObjectTO srcTO = (VolumeObjectTO)srcData.getTO();
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s ", srcTO.getPath());
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST canonicalName=%s ", dstData.getDataStore().getClass().getCanonicalName());
|
||||
PrimaryDataStoreTO checkStoragePool = dstData.getTO().getDataStore() instanceof PrimaryDataStoreTO ? (PrimaryDataStoreTO)dstData.getTO().getDataStore() : null;
|
||||
final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST tmpSnapName=%s ,srcUUID=%s", name, srcTO.getUuid());
|
||||
|
||||
if (checkStoragePool != null && checkStoragePool.getPoolType().equals(StoragePoolType.SharedMountPoint)) {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
|
||||
//uuid tag will be the same as srcData.uuid
|
||||
String volumeName = srcData.getUuid();
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc volumeName=%s, baseOn=%s", volumeName, baseOn);
|
||||
final SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", srcInfo.getMaxIops(), conn);
|
||||
srcTO.setSize(srcData.getSize());
|
||||
srcTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(response, false)));
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST to=%s", srcTO);
|
||||
|
||||
answer = new CopyCmdAnswer(srcTO);
|
||||
} else {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
final SpApiResponse resp = StorPoolUtil.volumeSnapshot(name, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn);
|
||||
String snapshotName = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID);
|
||||
if (resp.getError() == null) {
|
||||
srcTO.setPath(StorPoolUtil.devPath(
|
||||
StorPoolUtil.getSnapshotNameFromResponse(resp, false, StorPoolUtil.GLOBAL_ID)));
|
||||
|
||||
cmd = new StorPoolCopyVolumeToSecondaryCommand(srcTO, dstData.getTO(), StorPoolHelper.getTimeout(StorPoolHelper.CopyVolumeWait, configDao), VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc command=%s ", cmd);
|
||||
|
||||
try {
|
||||
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(snapshotName, clusterDao);
|
||||
EndPoint ep = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData);
|
||||
StorPoolUtil.spLog("selector.select(srcData, dstData) ", ep);
|
||||
if (ep == null) {
|
||||
ep = selector.select(dstData);
|
||||
StorPoolUtil.spLog("selector.select(srcData) ", ep);
|
||||
}
|
||||
|
||||
if (ep == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = ep.sendMessage(cmd);
|
||||
StorPoolUtil.spLog("Answer: details=%s, result=%s", answer.getDetails(), answer.getResult());
|
||||
}
|
||||
} catch (CloudRuntimeException e) {
|
||||
err = e.getMessage();
|
||||
}
|
||||
} else {
|
||||
err = String.format("Failed to create temporary StorPool snapshot while trying to download volume %s (uuid %s). Error: %s", srcTO.getName(), srcTO.getUuid(), resp.getError());
|
||||
}
|
||||
final SpApiResponse resp2 = StorPoolUtil.snapshotDelete(snapshotName, conn);
|
||||
if (resp2.getError() != null) {
|
||||
final String err2 = String.format("Failed to delete temporary StorPool snapshot %s. Error: %s", StorPoolUtil.getNameFromResponse(resp, true), resp2.getError());
|
||||
log.error(err2);
|
||||
StorPoolUtil.spLog(err2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = String.format("Unsupported copy operation from %s (type %s) to %s (type %s)", srcData.getUuid(), srcType, dstData.getUuid(), dstType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
StorPoolUtil.spLog("Caught exception: %s", e.toString());
|
||||
err = e.toString();
|
||||
}
|
||||
|
||||
if (answer != null && !answer.getResult()) {
|
||||
err = answer.getDetails();
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
StorPoolUtil.spLog("Failed due to %s", err);
|
||||
|
||||
log.error(err);
|
||||
answer = new Answer(cmd, false, err);
|
||||
}
|
||||
|
||||
CopyCommandResult res = new CopyCommandResult(null, answer);
|
||||
res.setResult(err);
|
||||
callback.complete(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
|
||||
String snapshotName = snapshot.getUuid();
|
||||
VolumeInfo vinfo = snapshot.getBaseVolume();
|
||||
String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
|
||||
Long vmId = vinfo.getInstanceId();
|
||||
if (volumeName != null) {
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.takeSnapshot volumename=%s vmInstance=%s",volumeName, vmId);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("The path should be: " + StorPoolUtil.SP_DEV_PATH);
|
||||
}
|
||||
|
||||
CreateObjectAnswer answer = null;
|
||||
String err = null;
|
||||
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.volumeSnapshot(volumeName, snapshotName, vmId != null ? getVMInstanceUUID(vmId) : null, "snapshot", null, conn);
|
||||
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not snapshot StorPool volume %s. Error %s", volumeName, resp.getError());
|
||||
answer = new CreateObjectAnswer(err);
|
||||
} else {
|
||||
String name = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID);
|
||||
SnapshotObjectTO snapTo = (SnapshotObjectTO)snapshot.getTO();
|
||||
snapTo.setPath(StorPoolUtil.devPath(name.split("~")[1]));
|
||||
answer = new CreateObjectAnswer(snapTo);
|
||||
StorPoolHelper.addSnapshotDetails(snapshot.getId(), snapshot.getUuid(), snapTo.getPath(), _snapshotDetailsDao);
|
||||
//add primary storage of snapshot
|
||||
StorPoolHelper.addSnapshotDetails(snapshot.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(snapshot.getDataStore().getId()), _snapshotDetailsDao);
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.takeSnapshot: snapshot: name=%s, uuid=%s, volume: name=%s, uuid=%s", name, snapshot.getUuid(), volumeName, vinfo.getUuid());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
err = String.format("Could not take volume snapshot due to %s", e.getMessage());
|
||||
}
|
||||
|
||||
CreateCmdResult res = new CreateCmdResult(null, answer);
|
||||
res.setResult(err);
|
||||
callback.complete(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revertSnapshot(final SnapshotInfo snapshot, final SnapshotInfo snapshotOnPrimaryStore, final AsyncCompletionCallback<CommandResult> callback) {
|
||||
final VolumeInfo vinfo = snapshot.getBaseVolume();
|
||||
final String snapshotName = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), snapshotDataStoreDao, _snapshotDetailsDao);
|
||||
final String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true);
|
||||
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.revertSnapshot: snapshot: name=%s, uuid=%s, volume: name=%s, uuid=%s", snapshotName, snapshot.getUuid(), volumeName, vinfo.getUuid());
|
||||
String err = null;
|
||||
|
||||
SpConnectionDesc conn = null;
|
||||
try {
|
||||
conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
} catch (Exception e) {
|
||||
err = String.format("Could not revert volume due to %s", e.getMessage());
|
||||
completeResponse(err, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
VolumeDetailVO detail = volumeDetailsDao.findDetail(vinfo.getId(), StorPoolUtil.SP_PROVIDER_NAME);
|
||||
if (detail != null) {
|
||||
//Rename volume to its global id only if it was migrated from UUID to global id
|
||||
SpApiResponse updateVolumeResponse = StorPoolUtil.volumeUpdateRename(StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), "", StorPoolStorageAdaptor.getVolumeNameFromPath(detail.getValue(), false), conn);
|
||||
|
||||
if (updateVolumeResponse.getError() != null) {
|
||||
StorPoolUtil.spLog("Could not update StorPool's volume %s to it's globalId due to %s", StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), updateVolumeResponse.getError().getDescr());
|
||||
err = String.format("Could not update StorPool's volume %s to it's globalId due to %s", StorPoolStorageAdaptor.getVolumeNameFromPath(vinfo.getPath(), true), updateVolumeResponse.getError().getDescr());
|
||||
completeResponse(err, callback);
|
||||
return;
|
||||
}
|
||||
volumeDetailsDao.remove(detail.getId());
|
||||
}
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.detachAllForced(volumeName, false, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not detach StorPool volume %s due to %s", volumeName, resp.getError());
|
||||
completeResponse(err, callback);
|
||||
return;
|
||||
}
|
||||
SpApiResponse response = StorPoolUtil.volumeRevert(volumeName, snapshotName, conn);
|
||||
if (response.getError() != null) {
|
||||
err = String.format(
|
||||
"Could not revert StorPool volume %s to the %s snapshot: could not create the new volume: error %s",
|
||||
volumeName, snapshotName, response.getError());
|
||||
completeResponse(err, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vinfo.getMaxIops() != null) {
|
||||
response = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
|
||||
if (response.getError() != null) {
|
||||
StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", response.getError().getDescr());
|
||||
}
|
||||
}
|
||||
|
||||
final VolumeObjectTO to = (VolumeObjectTO)vinfo.getTO();
|
||||
completeResponse(to, callback);
|
||||
}
|
||||
|
||||
private String getVcPolicyTag(Long vmId) {
|
||||
ResourceTag resourceTag = vmId != null ? _resourceTagDao.findByKey(vmId, ResourceObjectType.UserVm, StorPoolUtil.SP_VC_POLICY) : null;
|
||||
return resourceTag != null ? resourceTag.getValue() : "";
|
||||
}
|
||||
|
||||
public void handleQualityOfServiceForVolumeMigration(VolumeInfo arg0, QualityOfServiceState arg1) {
|
||||
StorPoolUtil.spLog("handleQualityOfServiceForVolumeMigration with volume name=%s", arg0.getName());
|
||||
}
|
||||
|
||||
|
||||
public void copyAsync(DataObject srcData, DataObject destData, Host destHost,
|
||||
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||
copyAsync(srcData, destData, callback);
|
||||
}
|
||||
|
||||
public boolean canProvideStorageStats() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean canProvideVolumeStats() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmInfoNeeded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmInfo(long vmId, long volumeId) {
|
||||
VolumeVO volume = volumeDao.findById(volumeId);
|
||||
StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
|
||||
if (poolVO != null) {
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
|
||||
VMInstanceVO userVM = vmInstanceDao.findById(vmId);
|
||||
SpApiResponse resp = StorPoolUtil.volumeUpadateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId));
|
||||
if (resp.getError() != null) {
|
||||
log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVmTagsNeeded(String tagKey) {
|
||||
return tagKey != null && tagKey.equals(StorPoolUtil.SP_VC_POLICY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideVmTags(long vmId, long volumeId, String tagValue) {
|
||||
VolumeVO volume = volumeDao.findById(volumeId);
|
||||
StoragePoolVO poolVO = primaryStoreDao.findById(volume.getPoolId());
|
||||
if (poolVO != null) {
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
|
||||
SpApiResponse resp = StorPoolUtil.volumeUpadateVCTags(volName, conn, getVcPolicyTag(vmId));
|
||||
if (resp.getError() != null) {
|
||||
log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(String.format("Could not update Virtual machine tags due to %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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.lifecycle;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
|
||||
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.ZoneScope;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.StoragePoolInfo;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.resource.ResourceManager;
|
||||
import com.cloud.storage.ScopeType;
|
||||
import com.cloud.storage.SnapshotVO;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.StoragePoolAutomation;
|
||||
import com.cloud.storage.VMTemplateDetailVO;
|
||||
import com.cloud.storage.VMTemplateStoragePoolVO;
|
||||
import com.cloud.storage.dao.SnapshotDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||
import com.cloud.storage.dao.VMTemplateDetailsDao;
|
||||
import com.cloud.storage.dao.VMTemplatePoolDao;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class StorPoolPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle {
|
||||
private static final Logger log = Logger.getLogger(StorPoolPrimaryDataStoreLifeCycle.class);
|
||||
|
||||
@Inject
|
||||
protected PrimaryDataStoreHelper dataStoreHelper;
|
||||
@Inject
|
||||
protected StoragePoolAutomation storagePoolAutmation;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao _primaryDataStoreDao;
|
||||
@Inject
|
||||
private ResourceManager resourceMgr;
|
||||
@Inject
|
||||
private StorageManager storageMgr;
|
||||
@Inject
|
||||
private SnapshotDao snapshotDao;
|
||||
@Inject
|
||||
private SnapshotDetailsDao snapshotDetailsDao;
|
||||
@Inject
|
||||
private VMTemplatePoolDao vmTemplatePoolDao;
|
||||
@Inject
|
||||
private VMTemplateDetailsDao vmTemplateDetailsDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
|
||||
@Override
|
||||
public DataStore initialize(Map<String, Object> dsInfos) {
|
||||
StorPoolUtil.spLog("initialize:");
|
||||
for (Map.Entry<String, Object> e: dsInfos.entrySet()) {
|
||||
StorPoolUtil.spLog(" %s=%s", e.getKey(), e.getValue());
|
||||
}
|
||||
StorPoolUtil.spLog("");
|
||||
|
||||
log.debug("initialize");
|
||||
|
||||
String name = (String)dsInfos.get("name");
|
||||
String providerName = (String)dsInfos.get("providerName");
|
||||
Long zoneId = (Long)dsInfos.get("zoneId");
|
||||
|
||||
String url = (String)dsInfos.get("url");
|
||||
SpConnectionDesc conn = new SpConnectionDesc(url);
|
||||
if (conn.getHostPort() == null)
|
||||
throw new IllegalArgumentException("No SP_API_HTTP");
|
||||
|
||||
if (conn.getAuthToken() == null)
|
||||
throw new IllegalArgumentException("No SP_AUTH_TOKEN");
|
||||
|
||||
if (conn.getTemplateName() == null)
|
||||
throw new IllegalArgumentException("No SP_TEMPLATE");
|
||||
|
||||
if (!StorPoolUtil.templateExists(conn)) {
|
||||
throw new IllegalArgumentException("No such storpool template " + conn.getTemplateName() + " or credentials are invalid");
|
||||
}
|
||||
|
||||
for (StoragePoolVO sp : _primaryDataStoreDao.findPoolsByProvider("StorPool")) {
|
||||
List<StoragePoolDetailVO> spDetails = storagePoolDetailsDao.listDetails(sp.getId());
|
||||
String host = null;
|
||||
String template = null;
|
||||
String authToken = null;
|
||||
SpConnectionDesc old = null;
|
||||
for (StoragePoolDetailVO storagePoolDetailVO : spDetails) {
|
||||
switch (storagePoolDetailVO.getName()) {
|
||||
case StorPoolUtil.SP_AUTH_TOKEN:
|
||||
authToken = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
case StorPoolUtil.SP_HOST_PORT:
|
||||
host = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
case StorPoolUtil.SP_TEMPLATE:
|
||||
template = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (host != null && template != null && authToken != null) {
|
||||
old = new SpConnectionDesc(host, authToken, template);
|
||||
} else {
|
||||
old = new SpConnectionDesc(sp.getUuid());
|
||||
}
|
||||
if( old.getHostPort().equals(conn.getHostPort()) && old.getTemplateName().equals(conn.getTemplateName()) )
|
||||
throw new IllegalArgumentException("StorPool cluster and template already in use by pool " + sp.getName());
|
||||
}
|
||||
|
||||
Long capacityBytes = (Long)dsInfos.get("capacityBytes");
|
||||
if (capacityBytes == null) {
|
||||
throw new IllegalArgumentException("Capcity bytes is required");
|
||||
}
|
||||
|
||||
String tags = (String)dsInfos.get("tags");
|
||||
if (tags == null || tags.isEmpty()) {
|
||||
tags = name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> details = (Map<String, String>)dsInfos.get("details");
|
||||
details.put(StorPoolUtil.SP_AUTH_TOKEN, conn.getAuthToken());
|
||||
details.put(StorPoolUtil.SP_HOST_PORT, conn.getHostPort());
|
||||
details.put(StorPoolUtil.SP_TEMPLATE, conn.getTemplateName());
|
||||
|
||||
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
|
||||
parameters.setName(name);
|
||||
parameters.setUuid(conn.getTemplateName() + ";" + UUID.randomUUID().toString());
|
||||
parameters.setZoneId(zoneId);
|
||||
parameters.setProviderName(providerName);
|
||||
parameters.setType(StoragePoolType.SharedMountPoint);
|
||||
parameters.setHypervisorType(HypervisorType.KVM);
|
||||
parameters.setManaged(false);
|
||||
parameters.setHost("n/a");
|
||||
parameters.setPort(0);
|
||||
parameters.setPath(StorPoolUtil.SP_DEV_PATH);
|
||||
parameters.setUsedBytes(0);
|
||||
parameters.setCapacityBytes(capacityBytes);
|
||||
parameters.setTags(tags);
|
||||
parameters.setDetails(details);
|
||||
|
||||
return dataStoreHelper.createPrimaryDataStore(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
|
||||
StorPoolUtil.spLog("updateStoragePool:");
|
||||
for (Map.Entry<String, String> e: details.entrySet()) {
|
||||
StorPoolUtil.spLog(" %s=%s", e.getKey(), e.getValue());
|
||||
}
|
||||
StorPoolUtil.spLog("");
|
||||
|
||||
log.debug("updateStoragePool");
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
|
||||
log.debug("attachHost");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attachCluster(DataStore store, ClusterScope scope) {
|
||||
log.debug("attachCluster");
|
||||
if (!scope.getScopeType().equals(ScopeType.ZONE)) {
|
||||
throw new UnsupportedOperationException("Only Zone-Wide scope is supported!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
|
||||
log.debug("attachZone");
|
||||
|
||||
if (hypervisorType != HypervisorType.KVM) {
|
||||
throw new UnsupportedOperationException("Only KVM hypervisors supported!");
|
||||
}
|
||||
List<HostVO> kvmHosts = resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(HypervisorType.KVM, scope.getScopeId());
|
||||
for (HostVO host : kvmHosts) {
|
||||
try {
|
||||
storageMgr.connectHostToSharedPool(host.getId(), dataStore.getId());
|
||||
} catch (Exception e) {
|
||||
log.warn(String.format("Unable to establish a connection between host %s and pool %s due to %s", host, dataStore, e));
|
||||
}
|
||||
}
|
||||
dataStoreHelper.attachZone(dataStore, hypervisorType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean maintain(DataStore dataStore) {
|
||||
log.debug("maintain");
|
||||
|
||||
storagePoolAutmation.maintain(dataStore);
|
||||
dataStoreHelper.maintain(dataStore);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelMaintain(DataStore store) {
|
||||
log.debug("cancelMaintain");
|
||||
|
||||
dataStoreHelper.cancelMaintain(store);
|
||||
storagePoolAutmation.cancelMaintain(store);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteDataStore(DataStore store) {
|
||||
log.debug("deleteDataStore");
|
||||
long storagePoolId = store.getId();
|
||||
|
||||
List<SnapshotVO> lstSnapshots = snapshotDao.listAll();
|
||||
|
||||
if (lstSnapshots != null) {
|
||||
for (SnapshotVO snapshot : lstSnapshots) {
|
||||
SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshot.getId(), StorPoolUtil.SP_STORAGE_POOL_ID);
|
||||
|
||||
// if this snapshot belongs to the storagePool that was passed in
|
||||
if (snapshotDetails != null && snapshotDetails.getValue() != null && Long.parseLong(snapshotDetails.getValue()) == storagePoolId) {
|
||||
throw new CloudRuntimeException("This primary storage cannot be deleted because it currently contains one or more snapshots.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<VMTemplateDetailVO> lstTemplateDetails = vmTemplateDetailsDao.listAll();
|
||||
|
||||
if (lstTemplateDetails != null) {
|
||||
for (VMTemplateDetailVO vmTemplateDetailVO : lstTemplateDetails) {
|
||||
if (vmTemplateDetailVO.getName().equals(StorPoolUtil.SP_STORAGE_POOL_ID) && Long.parseLong(vmTemplateDetailVO.getValue()) == storagePoolId) {
|
||||
throw new CloudRuntimeException("This primary storage cannot be deleted because it currently contains one or more template snapshots.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<VMTemplateStoragePoolVO> lstTemplatePoolRefs = vmTemplatePoolDao.listByPoolId(storagePoolId);
|
||||
|
||||
SpConnectionDesc conn = null;
|
||||
try {
|
||||
conn = StorPoolUtil.getSpConnection(store.getUuid(), store.getId(), storagePoolDetailsDao, _primaryDataStoreDao);
|
||||
} catch (CloudRuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (lstTemplatePoolRefs != null) {
|
||||
for (VMTemplateStoragePoolVO templatePoolRef : lstTemplatePoolRefs) {
|
||||
SpApiResponse resp = StorPoolUtil.snapshotDelete(
|
||||
StorPoolStorageAdaptor.getVolumeNameFromPath(templatePoolRef.getLocalDownloadPath(), true), conn);
|
||||
if (resp.getError() != null) {
|
||||
throw new CloudRuntimeException(String.format("Could not delete StorPool's snapshot from template_spool_ref table due to %s", resp.getError()));
|
||||
}
|
||||
vmTemplatePoolDao.remove(templatePoolRef.getId());
|
||||
}
|
||||
}
|
||||
boolean isDeleted = dataStoreHelper.deletePrimaryDataStore(store);
|
||||
if (isDeleted) {
|
||||
List<StoragePoolDetailVO> volumesOnHosts = storagePoolDetailsDao.listDetails(storagePoolId);
|
||||
for (StoragePoolDetailVO storagePoolDetailVO : volumesOnHosts) {
|
||||
if (storagePoolDetailVO.getValue() != null && storagePoolDetailVO.getName().contains(StorPoolUtil.SP_VOLUME_ON_CLUSTER)) {
|
||||
StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(storagePoolDetailVO.getValue(), true), conn);
|
||||
}
|
||||
}
|
||||
storagePoolDetailsDao.removeDetails(storagePoolId);
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean migrateToObjectStore(DataStore store) {
|
||||
log.debug("migrateToObjectStore");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableStoragePool(DataStore dataStore) {
|
||||
log.debug("enableStoragePool");
|
||||
dataStoreHelper.enable(dataStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableStoragePool(DataStore dataStore) {
|
||||
log.debug("disableStoragePool");
|
||||
dataStoreHelper.disable(dataStore);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
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.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolFeaturesAndFixes;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.storage.StorPoolModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.api.storage.StorPoolModifyStoragePoolCommand;
|
||||
import com.cloud.agent.manager.AgentAttache;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.dc.ClusterDetailsDao;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.exception.StorageConflictException;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
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 StorPoolHostListener implements HypervisorHostListener {
|
||||
private static final Logger log = Logger.getLogger(StorPoolHostListener .class);
|
||||
|
||||
@Inject
|
||||
private AgentManager agentMgr;
|
||||
@Inject
|
||||
private DataStoreManager dataStoreMgr;
|
||||
@Inject
|
||||
private AlertManager alertMgr;
|
||||
@Inject
|
||||
private StoragePoolHostDao storagePoolHostDao;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao primaryStoreDao;
|
||||
@Inject
|
||||
private HostDao hostDao;
|
||||
@Inject
|
||||
private ClusterDao clusterDao;
|
||||
@Inject
|
||||
private ClusterDetailsDao clusterDetailsDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
|
||||
@Override
|
||||
public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
|
||||
//Will update storage pool's connection details if they aren't updated in DB, before connecting pool to host
|
||||
StoragePoolVO poolVO = primaryStoreDao.findById(poolId);
|
||||
|
||||
SpConnectionDesc conn = null;
|
||||
try {
|
||||
conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolId, storagePoolDetailsDao, primaryStoreDao);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StoragePool pool = (StoragePool)this.dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
|
||||
|
||||
HostVO host = hostDao.findById(hostId);
|
||||
StoragePoolDetailVO volumeOnPool = verifyVolumeIsOnCluster(poolId, conn, host.getClusterId());
|
||||
if (volumeOnPool == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (host.isInMaintenanceStates()) {
|
||||
addModifyCommandToCommandsAllowedInMaintenanceMode();
|
||||
}
|
||||
|
||||
List<String> driverSupportedFeatures = StorPoolFeaturesAndFixes.getAllClassConstants();
|
||||
List<StoragePoolDetailVO> driverFeaturesBeforeUpgrade = StorPoolHelper.listFeaturesUpdates(storagePoolDetailsDao, poolId);
|
||||
boolean isCurrentVersionSupportsEverythingFromPrevious = StorPoolHelper.isPoolSupportsAllFunctionalityFromPreviousVersion(storagePoolDetailsDao, driverSupportedFeatures, driverFeaturesBeforeUpgrade, poolId);
|
||||
if (!isCurrentVersionSupportsEverythingFromPrevious) {
|
||||
String msg = "The current StorPool driver does not support all functionality from the one before upgrade to CS";
|
||||
StorPoolUtil.spLog("Storage pool [%s] is not connected to host [%s] because the functionality after the upgrade is not full",
|
||||
poolId, hostId);
|
||||
alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
StorPoolModifyStoragePoolCommand cmd = new StorPoolModifyStoragePoolCommand(true, pool, volumeOnPool.getValue());
|
||||
final Answer answer = agentMgr.easySend(hostId, cmd);
|
||||
|
||||
StoragePoolHostVO poolHost = storagePoolHostDao.findByPoolHost(pool.getId(), hostId);
|
||||
|
||||
if (answer == null) {
|
||||
throw new CloudRuntimeException("Unable to get an answer to the modify storage pool command" + pool.getId());
|
||||
}
|
||||
|
||||
if (!answer.getResult()) {
|
||||
if (answer.getDetails() != null) {
|
||||
if (answer.getDetails().equals("objectDoesNotExist")) {
|
||||
StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeOnPool.getValue(), true), conn);
|
||||
storagePoolDetailsDao.remove(volumeOnPool.getId());
|
||||
return false;
|
||||
} else if (answer.getDetails().equals("spNotFound")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
String msg = "Unable to attach storage pool" + poolId + " to the host" + hostId;
|
||||
alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
|
||||
throw new CloudRuntimeException("Unable establish connection from storage head to storage pool " + pool.getId() + " due to " + answer.getDetails() +
|
||||
pool.getId());
|
||||
}
|
||||
|
||||
StorPoolUtil.spLog("hostConnect: hostId=%d, poolId=%d", hostId, poolId);
|
||||
|
||||
StorPoolModifyStoragePoolAnswer mspAnswer = (StorPoolModifyStoragePoolAnswer)answer;
|
||||
if (mspAnswer.getLocalDatastoreName() != null && pool.isShared()) {
|
||||
String datastoreName = mspAnswer.getLocalDatastoreName();
|
||||
List<StoragePoolVO> localStoragePools = primaryStoreDao.listLocalStoragePoolByPath(pool.getDataCenterId(), datastoreName);
|
||||
for (StoragePoolVO localStoragePool : localStoragePools) {
|
||||
if (datastoreName.equals(localStoragePool.getPath())) {
|
||||
log.warn("Storage pool: " + pool.getId() + " has already been added as local storage: " + localStoragePool.getName());
|
||||
throw new StorageConflictException("Cannot add shared storage pool: " + pool.getId() + " because it has already been added as local storage:"
|
||||
+ localStoragePool.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (poolHost == null) {
|
||||
poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
||||
storagePoolHostDao.persist(poolHost);
|
||||
} else {
|
||||
poolHost.setLocalPath(mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
||||
}
|
||||
|
||||
StorPoolHelper.setSpClusterIdIfNeeded(hostId, mspAnswer.getClusterId(), clusterDao, hostDao, clusterDetailsDao);
|
||||
|
||||
log.info("Connection established between storage pool " + pool + " and host " + hostId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized StoragePoolDetailVO verifyVolumeIsOnCluster(long poolId, SpConnectionDesc conn, long clusterId) {
|
||||
StoragePoolDetailVO volumeOnPool = storagePoolDetailsDao.findDetail(poolId, StorPoolUtil.SP_VOLUME_ON_CLUSTER + "-" + clusterId);
|
||||
if (volumeOnPool == null) {
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(conn);
|
||||
if (resp.getError() != null) {
|
||||
return volumeOnPool;
|
||||
}
|
||||
String volumeName = StorPoolUtil.getNameFromResponse(resp, false);
|
||||
volumeOnPool = new StoragePoolDetailVO(poolId, StorPoolUtil.SP_VOLUME_ON_CLUSTER + "-" + clusterId, StorPoolUtil.devPath(volumeName), false);
|
||||
storagePoolDetailsDao.persist(volumeOnPool);
|
||||
}
|
||||
return volumeOnPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostAdded(long hostId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostDisconnected(long hostId, long poolId) {
|
||||
StorPoolUtil.spLog("hostDisconnected: hostId=%d, poolId=%d", hostId, poolId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostAboutToBeRemoved(long hostId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostRemoved(long hostId, long clusterId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//workaround: we need this "hack" to add our command StorPoolModifyStoragePoolCommand in AgentAttache.s_commandsAllowedInMaintenanceMode
|
||||
//which checks the allowed commands when the host is in maintenance mode
|
||||
private void addModifyCommandToCommandsAllowedInMaintenanceMode() {
|
||||
|
||||
Class<AgentAttache> cls = AgentAttache.class;
|
||||
try {
|
||||
Field field = cls.getDeclaredField("s_commandsAllowedInMaintenanceMode");
|
||||
field.setAccessible(true);
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
List<String> allowedCmdsInMaintenance = new ArrayList<String>(Arrays.asList(AgentAttache.s_commandsAllowedInMaintenanceMode));
|
||||
allowedCmdsInMaintenance.add(StorPoolModifyStoragePoolCommand.class.toString());
|
||||
String[] allowedCmdsInMaintenanceNew = new String[allowedCmdsInMaintenance.size()];
|
||||
allowedCmdsInMaintenance.toArray(allowedCmdsInMaintenanceNew);
|
||||
Arrays.sort(allowedCmdsInMaintenanceNew);
|
||||
field.set(null, allowedCmdsInMaintenanceNew);
|
||||
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
|
||||
String err = "Could not add StorPoolModifyStoragePoolCommand to s_commandsAllowedInMaintenanceMode array due to: %s";
|
||||
StorPoolUtil.spLog(err, e.getMessage());
|
||||
log.warn(String.format(err, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hostEnabled(long hostId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
|
||||
import org.apache.cloudstack.storage.datastore.driver.StorPoolPrimaryDataStoreDriver;
|
||||
import org.apache.cloudstack.storage.datastore.lifecycle.StorPoolPrimaryDataStoreLifeCycle;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
|
||||
import com.cloud.utils.component.ComponentContext;
|
||||
|
||||
public class StorPoolPrimaryDataStoreProvider implements PrimaryDataStoreProvider {
|
||||
|
||||
protected DataStoreLifeCycle lifecycle;
|
||||
protected DataStoreDriver driver;
|
||||
protected HypervisorHostListener listener;
|
||||
|
||||
StorPoolPrimaryDataStoreProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return StorPoolUtil.SP_PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStoreLifeCycle getDataStoreLifeCycle() {
|
||||
return lifecycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStoreDriver getDataStoreDriver() {
|
||||
return driver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HypervisorHostListener getHostListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(Map<String, Object> params) {
|
||||
lifecycle = ComponentContext.inject(StorPoolPrimaryDataStoreLifeCycle.class);
|
||||
driver = ComponentContext.inject(StorPoolPrimaryDataStoreDriver.class);
|
||||
listener = ComponentContext.inject(StorPoolHostListener.class);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<DataStoreProviderType> getTypes() {
|
||||
Set<DataStoreProviderType> types = new HashSet<DataStoreProviderType>();
|
||||
types.add(DataStoreProviderType.PRIMARY);
|
||||
return types;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StorPoolFeaturesAndFixes {
|
||||
|
||||
public static List<String> getAllClassConstants() {
|
||||
List<String> constants = new ArrayList<>();
|
||||
|
||||
for (Field field : StorPoolFeaturesAndFixes.class.getDeclaredFields()) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
|
||||
constants.add(field.getName());
|
||||
}
|
||||
}
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.RollingFileAppender;
|
||||
|
||||
import com.cloud.dc.ClusterDetailsDao;
|
||||
import com.cloud.dc.ClusterDetailsVO;
|
||||
import com.cloud.dc.ClusterVO;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.server.ResourceTag;
|
||||
import com.cloud.server.ResourceTag.ResourceObjectType;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.VMTemplateStoragePoolVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.tags.dao.ResourceTagDao;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.utils.db.QueryBuilder;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
public class StorPoolHelper {
|
||||
|
||||
private static final String UPDATE_SNAPSHOT_DETAILS_VALUE = "UPDATE `cloud`.`snapshot_details` SET value=? WHERE id=?";
|
||||
private static final String UPDATE_VOLUME_DETAILS_NAME = "UPDATE `cloud`.`volume_details` SET name=? WHERE id=?";
|
||||
public static final String PrimaryStorageDownloadWait = "primary.storage.download.wait";
|
||||
public static final String CopyVolumeWait = "copy.volume.wait";
|
||||
public static final String BackupSnapshotWait = "backup.snapshot.wait";
|
||||
|
||||
public static void updateVolumeInfo(VolumeObjectTO volumeObjectTO, Long size, SpApiResponse resp,
|
||||
VolumeDao volumeDao) {
|
||||
String volumePath = StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false));
|
||||
VolumeVO volume = volumeDao.findById(volumeObjectTO.getId());
|
||||
if (volume != null) {
|
||||
volumeObjectTO.setSize(size);
|
||||
volumeObjectTO.setPath(volumePath);
|
||||
volume.setSize(size);
|
||||
volume.setPath(volumePath);
|
||||
volumeDao.update(volumeObjectTO.getId(), volume);
|
||||
}
|
||||
}
|
||||
|
||||
// If volume is deleted, CloudStack removes records of snapshots created on Primary storage only in database.
|
||||
// That's why we keep information in snapshot_details table, about all snapshots created on StorPool and we can operate with them
|
||||
public static void addSnapshotDetails(final Long id, final String uuid, final String snapshotName,
|
||||
SnapshotDetailsDao snapshotDetailsDao) {
|
||||
SnapshotDetailsVO details = new SnapshotDetailsVO(id, uuid, snapshotName, false);
|
||||
snapshotDetailsDao.persist(details);
|
||||
}
|
||||
|
||||
public static String getSnapshotName(Long snapshotId, String snapshotUuid, SnapshotDataStoreDao snapshotStoreDao,
|
||||
SnapshotDetailsDao snapshotDetailsDao) {
|
||||
|
||||
SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshotId, snapshotUuid);
|
||||
|
||||
if (snapshotDetails != null) {
|
||||
return StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDetails.getValue(), true);
|
||||
} else {
|
||||
List<SnapshotDataStoreVO> snapshots = snapshotStoreDao.findBySnapshotId(snapshotId);
|
||||
if (!CollectionUtils.isEmpty(snapshots)) {
|
||||
for (SnapshotDataStoreVO snapshotDataStoreVO : snapshots) {
|
||||
String name = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDataStoreVO.getInstallPath(), true);
|
||||
if (name == null) {
|
||||
continue;
|
||||
} else {
|
||||
addSnapshotDetails(snapshotId, snapshotUuid, snapshotDataStoreVO.getInstallPath(), snapshotDetailsDao);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void updateSnapshotDetailsValue(Long id, String valueOrName, String snapshotOrVolume) {
|
||||
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
PreparedStatement pstmt = null;
|
||||
try {
|
||||
String sql = null;
|
||||
if (snapshotOrVolume.equals("snapshot")) {
|
||||
sql = UPDATE_SNAPSHOT_DETAILS_VALUE;
|
||||
} else if (snapshotOrVolume.equals("volume")) {
|
||||
sql = UPDATE_VOLUME_DETAILS_NAME;
|
||||
} else {
|
||||
StorPoolUtil.spLog("Could not update snapshot detail with id=%s", id);
|
||||
}
|
||||
if (sql != null) {
|
||||
pstmt = txn.prepareAutoCloseStatement(sql);
|
||||
pstmt.setString(1, valueOrName);
|
||||
pstmt.setLong(2, id);
|
||||
pstmt.executeUpdate();
|
||||
txn.commit();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
txn.rollback();
|
||||
StorPoolUtil.spLog("Could not update snapshot detail with id=%s", id);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getVcPolicyTag(Long vmId, ResourceTagDao resourceTagDao) {
|
||||
if (vmId != null) {
|
||||
ResourceTag tag = resourceTagDao.findByKey(vmId, ResourceObjectType.UserVm, StorPoolUtil.SP_VC_POLICY);
|
||||
if (tag != null) {
|
||||
return tag.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getVMInstanceUUID(Long id, VMInstanceDao vmInstanceDao) {
|
||||
if (id != null) {
|
||||
VMInstanceVO vmInstance = vmInstanceDao.findById(id);
|
||||
if (vmInstance != null) {
|
||||
return vmInstance.getUuid();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Map<String, String> addStorPoolTags(String name, String vmUuid, String csTag, String vcPolicy) {
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
tags.put("uuid", name);
|
||||
tags.put("cvm", vmUuid);
|
||||
tags.put(StorPoolUtil.SP_VC_POLICY, vcPolicy);
|
||||
if (csTag != null) {
|
||||
tags.put("cs", csTag);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
// Initialize custom logger for updated volume and snapshots
|
||||
public static void appendLogger(Logger log, String filePath, String kindOfLog) {
|
||||
Appender appender = null;
|
||||
PatternLayout patternLayout = new PatternLayout();
|
||||
patternLayout.setConversionPattern("%d{YYYY-MM-dd HH:mm:ss.SSS} %m%n");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
|
||||
String path = filePath + "-" + sdf.format(timestamp) + ".log";
|
||||
try {
|
||||
appender = new RollingFileAppender(patternLayout, path);
|
||||
log.setAdditivity(false);
|
||||
log.addAppender(appender);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (kindOfLog.equals("update")) {
|
||||
StorPoolUtil.spLog(
|
||||
"You can find information about volumes and snapshots, which will be updated in Database with their globalIs in %s log file",
|
||||
path);
|
||||
} else if (kindOfLog.equals("abandon")) {
|
||||
StorPoolUtil.spLog(
|
||||
"You can find information about volumes and snapshots, for which CloudStack doesn't have information in %s log file",
|
||||
path);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSpClusterIdIfNeeded(long hostId, String clusterId, ClusterDao clusterDao, HostDao hostDao,
|
||||
ClusterDetailsDao clusterDetails) {
|
||||
HostVO host = hostDao.findById(hostId);
|
||||
if (host != null && host.getClusterId() != null) {
|
||||
ClusterVO cluster = clusterDao.findById(host.getClusterId());
|
||||
ClusterDetailsVO clusterDetailsVo = clusterDetails.findDetail(cluster.getId(),
|
||||
StorPoolConfigurationManager.StorPoolClusterId.key());
|
||||
if (clusterDetailsVo == null) {
|
||||
clusterDetails.persist(
|
||||
new ClusterDetailsVO(cluster.getId(), StorPoolConfigurationManager.StorPoolClusterId.key(), clusterId));
|
||||
} else if (clusterDetailsVo.getValue() == null || !clusterDetailsVo.getValue().equals(clusterId)) {
|
||||
clusterDetailsVo.setValue(clusterId);
|
||||
clusterDetails.update(clusterDetailsVo.getId(), clusterDetailsVo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Long findClusterIdByGlobalId(String globalId, ClusterDao clusterDao) {
|
||||
List<ClusterVO> clusterVo = clusterDao.listAll();
|
||||
if (clusterVo.size() == 1) {
|
||||
StorPoolUtil.spLog("There is only one cluster, sending backup to secondary command");
|
||||
return null;
|
||||
}
|
||||
for (ClusterVO clusterVO2 : clusterVo) {
|
||||
if (globalId != null && StorPoolConfigurationManager.StorPoolClusterId.valueIn(clusterVO2.getId()) != null
|
||||
&& globalId.contains(StorPoolConfigurationManager.StorPoolClusterId.valueIn(clusterVO2.getId()).toString())) {
|
||||
StorPoolUtil.spLog("Found cluster with id=%s for object with globalId=%s", clusterVO2.getId(),
|
||||
globalId);
|
||||
return clusterVO2.getId();
|
||||
}
|
||||
}
|
||||
throw new CloudRuntimeException(
|
||||
"Could not find the right clusterId. to send command. To use snapshot backup to secondary for each CloudStack cluster in its settings set the value of StorPool's cluster-id in \"sp.cluster.id\".");
|
||||
}
|
||||
|
||||
public static HostVO findHostByCluster(Long clusterId, HostDao hostDao) {
|
||||
List<HostVO> host = hostDao.findByClusterId(clusterId);
|
||||
return host != null ? host.get(0) : null;
|
||||
}
|
||||
|
||||
public static int getTimeout(String cfg, ConfigurationDao configDao) {
|
||||
final ConfigurationVO value = configDao.findByName(cfg);
|
||||
return NumbersUtil.parseInt(value.getValue(), Integer.parseInt(value.getDefaultValue()));
|
||||
}
|
||||
|
||||
public static VMTemplateStoragePoolVO findByPoolTemplate(long poolId, long templateId) {
|
||||
QueryBuilder<VMTemplateStoragePoolVO> sc = QueryBuilder.create(VMTemplateStoragePoolVO.class);
|
||||
sc.and(sc.entity().getPoolId(), Op.EQ, poolId);
|
||||
sc.and(sc.entity().getTemplateId(), Op.EQ, templateId);
|
||||
return sc.find();
|
||||
}
|
||||
|
||||
public static void updateVmStoreTemplate(Long id, DataStoreRole role, String path,
|
||||
TemplateDataStoreDao templStoreDao) {
|
||||
TemplateDataStoreVO templ = templStoreDao.findByTemplate(id, role);
|
||||
templ.setLocalDownloadPath(path);
|
||||
templStoreDao.persist(templ);
|
||||
}
|
||||
|
||||
public static List<StoragePoolDetailVO> listFeaturesUpdates(StoragePoolDetailsDao storagePoolDetails, long poolId) {
|
||||
SearchBuilder<StoragePoolDetailVO> sb = storagePoolDetails.createSearchBuilder();
|
||||
sb.and("pool_id", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
|
||||
sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE);
|
||||
SearchCriteria<StoragePoolDetailVO> sc = sb.create();
|
||||
sc.setParameters("pool_id", poolId);
|
||||
sc.setParameters("name", "SP-FEATURE" + "%");
|
||||
return storagePoolDetails.search(sc, null);
|
||||
}
|
||||
|
||||
public static boolean isPoolSupportsAllFunctionalityFromPreviousVersion(StoragePoolDetailsDao storagePoolDetails, List<String> currentPluginFeatures, List<StoragePoolDetailVO> poolFeaturesBeforeUpgrade, long poolId) {
|
||||
if (CollectionUtils.isEmpty(currentPluginFeatures) && CollectionUtils.isEmpty(poolFeaturesBeforeUpgrade)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<String> poolDetails = poolFeaturesBeforeUpgrade.stream().map(StoragePoolDetailVO::getName).collect(Collectors.toList());
|
||||
List<String> detailsNotContainedInCurrent = new ArrayList<>(CollectionUtils.removeAll(poolDetails, currentPluginFeatures));
|
||||
List<String> detailsNotContainedInDataBase = new ArrayList<>(CollectionUtils.removeAll(currentPluginFeatures, poolDetails));
|
||||
if (!CollectionUtils.isEmpty(detailsNotContainedInCurrent)) {
|
||||
return false;
|
||||
} else if (!CollectionUtils.isEmpty(detailsNotContainedInDataBase)) {
|
||||
for (String features : detailsNotContainedInDataBase) {
|
||||
StoragePoolDetailVO storageNewFeatures = new StoragePoolDetailVO(poolId, features, features, false);
|
||||
storagePoolDetails.persist(storageNewFeatures);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,609 @@
|
||||
/*
|
||||
* 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.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.OutputInterpreter;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
public class StorPoolUtil {
|
||||
private static final Logger log = Logger.getLogger(StorPoolUtil.class);
|
||||
|
||||
private static final File spLogFile = new File("/var/log/cloudstack/management/storpool-plugin.log");
|
||||
private static PrintWriter spLogPrinterWriter = spLogFileInitialize();
|
||||
|
||||
private static PrintWriter spLogFileInitialize() {
|
||||
try {
|
||||
log.info("INITIALIZE SP-LOG_FILE");
|
||||
if (spLogFile.exists()) {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
|
||||
final File spLogFileRename = new File(spLogFile + "-" + sdf.format(timestamp));
|
||||
final boolean ret = spLogFile.renameTo(spLogFileRename);
|
||||
if (!ret) {
|
||||
log.warn("Unable to rename" + spLogFile + " to " + spLogFileRename);
|
||||
} else {
|
||||
log.debug("Renamed " + spLogFile + " to " + spLogFileRename);
|
||||
}
|
||||
} else {
|
||||
spLogFile.getParentFile().mkdirs();
|
||||
}
|
||||
return new PrintWriter(spLogFile);
|
||||
} catch (Exception e) {
|
||||
log.info("INITIALIZE SP-LOG_FILE: " + e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void spLog(String fmt, Object... args) {
|
||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,ms").format(Calendar.getInstance().getTime());
|
||||
spLogPrinterWriter.println(String.format(timeStamp + " " + fmt, args));
|
||||
spLogPrinterWriter.flush();
|
||||
if (spLogFile.length() > 107374182400L) {
|
||||
spLogPrinterWriter.close();
|
||||
spLogPrinterWriter = spLogFileInitialize();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String SP_PROVIDER_NAME = "StorPool";
|
||||
public static final String SP_DEV_PATH = "/dev/storpool-byid/";
|
||||
public static final String SP_OLD_PATH = "/dev/storpool/";
|
||||
public static final String SP_VC_POLICY = "vc-policy";
|
||||
public static final String GLOBAL_ID = "snapshotGlobalId";
|
||||
public static final String UPDATED_DETAIL = "renamed";
|
||||
public static final String SP_STORAGE_POOL_ID = "spStoragePoolId";
|
||||
|
||||
public static final String SP_HOST_PORT = "SP_API_HTTP_HOST";
|
||||
|
||||
public static final String SP_TEMPLATE = "SP_TEMPLATE";
|
||||
|
||||
public static final String SP_AUTH_TOKEN = "SP_AUTH_TOKEN";
|
||||
|
||||
public static final String SP_VOLUME_ON_CLUSTER = "SP_VOLUME_ON_CLUSTER";
|
||||
|
||||
public static enum StorpoolRights {
|
||||
RO("ro"), RW("rw"), DETACH("detach");
|
||||
|
||||
private final String name;
|
||||
|
||||
private StorpoolRights(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SpApiError {
|
||||
private String name;
|
||||
private String descr;
|
||||
|
||||
public SpApiError() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescr() {
|
||||
return this.descr;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDescr(String descr) {
|
||||
this.descr = descr;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s: %s", name, descr);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpConnectionDesc {
|
||||
private String hostPort;
|
||||
private String authToken;
|
||||
private String templateName;
|
||||
|
||||
public SpConnectionDesc(String url) {
|
||||
String[] urlSplit = url.split(";");
|
||||
if (urlSplit.length == 1 && !urlSplit[0].contains("=")) {
|
||||
this.templateName = url;
|
||||
|
||||
Script sc = new Script("storpool_confget", 0, log);
|
||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||
|
||||
final String err = sc.execute(parser);
|
||||
if (err != null) {
|
||||
final String errMsg = String.format("Could not execute storpool_confget. Error: %s", err);
|
||||
log.warn(errMsg);
|
||||
throw new CloudRuntimeException(errMsg);
|
||||
}
|
||||
|
||||
String SP_API_HOST = null;
|
||||
String SP_API_PORT = null;
|
||||
|
||||
for (String line : parser.getLines().split("\n")) {
|
||||
String[] toks = line.split("=");
|
||||
if (toks.length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (toks[0]) {
|
||||
case "SP_API_HTTP_HOST":
|
||||
SP_API_HOST = toks[1];
|
||||
break;
|
||||
|
||||
case "SP_API_HTTP_PORT":
|
||||
SP_API_PORT = toks[1];
|
||||
break;
|
||||
|
||||
case "SP_AUTH_TOKEN":
|
||||
this.authToken = toks[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (SP_API_HOST == null)
|
||||
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_HOST");
|
||||
if (SP_API_PORT == null)
|
||||
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_PORT");
|
||||
if (this.authToken == null)
|
||||
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_AUTH_TOKEN");
|
||||
|
||||
this.hostPort = SP_API_HOST + ":" + SP_API_PORT;
|
||||
} else {
|
||||
for (String kv : urlSplit) {
|
||||
String[] toks = kv.split("=");
|
||||
if (toks.length != 2)
|
||||
continue;
|
||||
switch (toks[0]) {
|
||||
case "SP_API_HTTP":
|
||||
this.hostPort = toks[1];
|
||||
break;
|
||||
|
||||
case "SP_AUTH_TOKEN":
|
||||
this.authToken = toks[1];
|
||||
break;
|
||||
|
||||
case "SP_TEMPLATE":
|
||||
this.templateName = toks[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SpConnectionDesc(String host, String authToken2, String templateName2) {
|
||||
this.hostPort = host;
|
||||
this.authToken = authToken2;
|
||||
this.templateName = templateName2;
|
||||
}
|
||||
|
||||
public String getHostPort() {
|
||||
return this.hostPort;
|
||||
}
|
||||
|
||||
public String getAuthToken() {
|
||||
return this.authToken;
|
||||
}
|
||||
|
||||
public String getTemplateName() {
|
||||
return this.templateName;
|
||||
}
|
||||
}
|
||||
|
||||
public static SpConnectionDesc getSpConnection(String url, long poolId, StoragePoolDetailsDao poolDetails,
|
||||
PrimaryDataStoreDao storagePool) {
|
||||
boolean isAlternateEndpointEnabled = StorPoolConfigurationManager.AlternativeEndPointEnabled.valueIn(poolId);
|
||||
if (isAlternateEndpointEnabled) {
|
||||
String alternateEndpoint = StorPoolConfigurationManager.AlternativeEndpoint.valueIn(poolId);
|
||||
if (StringUtils.isNotEmpty(alternateEndpoint)) {
|
||||
return new SpConnectionDesc(alternateEndpoint);
|
||||
} else {
|
||||
throw new CloudRuntimeException(String.format("Using an alternative endpoint of StorPool primary storage with id [%s] is enabled but no endpoint URL is provided", poolId));
|
||||
}
|
||||
}
|
||||
List<StoragePoolDetailVO> details = poolDetails.listDetails(poolId);
|
||||
String host = null;
|
||||
String authToken = null;
|
||||
String templateName = null;
|
||||
for (StoragePoolDetailVO storagePoolDetailVO : details) {
|
||||
switch (storagePoolDetailVO.getName()) {
|
||||
case SP_HOST_PORT:
|
||||
host = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
case SP_AUTH_TOKEN:
|
||||
authToken = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
case SP_TEMPLATE:
|
||||
templateName = storagePoolDetailVO.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (host != null && authToken != null && templateName != null) {
|
||||
return new SpConnectionDesc(host, authToken, templateName);
|
||||
} else {
|
||||
return updateStorageAndStorageDetails(url, poolId, poolDetails, storagePool);
|
||||
}
|
||||
}
|
||||
|
||||
private static SpConnectionDesc updateStorageAndStorageDetails(String url, long poolId,
|
||||
StoragePoolDetailsDao poolDetails, PrimaryDataStoreDao storagePool) {
|
||||
SpConnectionDesc conn = new SpConnectionDesc(url);
|
||||
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_HOST_PORT, conn.getHostPort(), false));
|
||||
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_AUTH_TOKEN, conn.getAuthToken(), false));
|
||||
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_TEMPLATE, conn.getTemplateName(), false));
|
||||
StoragePoolVO pool = storagePool.findById(poolId);
|
||||
pool.setUuid(conn.getTemplateName() + ";" + UUID.randomUUID().toString());
|
||||
storagePool.update(poolId, pool);
|
||||
StorPoolUtil.spLog(
|
||||
"Storage pool with id=%s and template's name=%s was updated and its connection details are hidden from UI.",
|
||||
pool.getId(), conn.getTemplateName());
|
||||
return conn;
|
||||
}
|
||||
|
||||
public static class SpApiResponse {
|
||||
private SpApiError error;
|
||||
public JsonElement fullJson;
|
||||
|
||||
public SpApiResponse() {
|
||||
}
|
||||
|
||||
public SpApiError getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public void setError(SpApiError error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static String devPath(final String name) {
|
||||
return String.format("%s%s", SP_DEV_PATH, name);
|
||||
}
|
||||
|
||||
private static SpApiResponse spApiRequest(HttpRequestBase req, String query, SpConnectionDesc conn) {
|
||||
|
||||
if (conn == null)
|
||||
conn = new SpConnectionDesc("");
|
||||
|
||||
if (conn.getHostPort() == null) {
|
||||
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_HOST");
|
||||
}
|
||||
|
||||
if (conn.getAuthToken() == null) {
|
||||
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_AUTH_TOKEN");
|
||||
}
|
||||
|
||||
try (CloseableHttpClient httpclient = HttpClientBuilder.create().build()) {
|
||||
final String qry = String.format("http://%s/ctrl/1.0/%s", conn.getHostPort(), query);
|
||||
final URI uri = new URI(qry);
|
||||
|
||||
req.setURI(uri);
|
||||
req.addHeader("Authorization", String.format("Storpool v1:%s", conn.getAuthToken()));
|
||||
|
||||
final HttpResponse resp = httpclient.execute(req);
|
||||
|
||||
Gson gson = new Gson();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
|
||||
|
||||
JsonElement el = new JsonParser().parse(br);
|
||||
|
||||
SpApiResponse apiResp = gson.fromJson(el, SpApiResponse.class);
|
||||
apiResp.fullJson = el;
|
||||
return apiResp;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw new CloudRuntimeException(ex.getMessage());
|
||||
} catch (ClientProtocolException ex) {
|
||||
throw new CloudRuntimeException(ex.getMessage());
|
||||
} catch (IOException ex) {
|
||||
throw new CloudRuntimeException(ex.getMessage());
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new CloudRuntimeException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static SpApiResponse GET(String query, SpConnectionDesc conn) {
|
||||
return spApiRequest(new HttpGet(), query, conn);
|
||||
}
|
||||
|
||||
private static SpApiResponse POST(String query, Object json, SpConnectionDesc conn) {
|
||||
HttpPost req = new HttpPost();
|
||||
if (json != null) {
|
||||
Gson gson = new Gson();
|
||||
String js = gson.toJson(json);
|
||||
StringEntity input = new StringEntity(js, ContentType.APPLICATION_JSON);
|
||||
log.info("Request:" + js);
|
||||
req.setEntity(input);
|
||||
}
|
||||
|
||||
return spApiRequest(req, query, conn);
|
||||
}
|
||||
|
||||
public static boolean templateExists(SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("VolumeTemplateDescribe/" + conn.getTemplateName(), conn);
|
||||
return resp.getError() == null ? true : objectExists(resp.getError());
|
||||
}
|
||||
|
||||
public static boolean snapshotExists(final String name, SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
|
||||
return resp.getError() == null ? true : objectExists(resp.getError());
|
||||
}
|
||||
|
||||
public static JsonArray snapshotsList(SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/SnapshotsList", conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
JsonArray data = obj.getAsJsonArray("data");
|
||||
return data;
|
||||
}
|
||||
|
||||
public static JsonArray volumesList(SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/VolumesList", conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
JsonArray data = obj.getAsJsonArray("data");
|
||||
return data;
|
||||
}
|
||||
|
||||
private static boolean objectExists(SpApiError err) {
|
||||
if (!err.getName().equals("objectDoesNotExist")) {
|
||||
throw new CloudRuntimeException(err.getDescr());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Long snapshotSize(final String name, SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
|
||||
if (resp.getError() != null && !objectExists(resp.getError())) {
|
||||
return null;
|
||||
}
|
||||
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
|
||||
return data.getAsJsonPrimitive("size").getAsLong();
|
||||
}
|
||||
|
||||
public static String getSnapshotClusterID(String name, SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
|
||||
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
|
||||
JsonPrimitive clusterId = data.getAsJsonPrimitive("clusterId");
|
||||
return clusterId != null ? clusterId.getAsString() : null;
|
||||
}
|
||||
|
||||
public static String getVolumeClusterID(String name, SpConnectionDesc conn) {
|
||||
SpApiResponse resp = GET("MultiCluster/Volume/" + name, conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
|
||||
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
|
||||
JsonPrimitive clusterId = data.getAsJsonPrimitive("clusterId");
|
||||
return clusterId != null ? clusterId.getAsString() : null;
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeCreate(final String name, final String parentName, final Long size, String vmUuid,
|
||||
String vcPolicy, String csTag, Long iops, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new LinkedHashMap<>();
|
||||
json.put("name", "");
|
||||
json.put("iops", iops);
|
||||
json.put("parent", parentName);
|
||||
json.put("size", size);
|
||||
json.put("template", conn.getTemplateName());
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, vmUuid, csTag, vcPolicy);
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeCreate", json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeCreate(SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new LinkedHashMap<>();
|
||||
json.put("name", "");
|
||||
json.put("size", 512);
|
||||
json.put("template", conn.getTemplateName());
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
tags.put("cs", "check-volume-is-on-host");
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeCreate", json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops,
|
||||
SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("baseOn", baseOn);
|
||||
if (iops != null) {
|
||||
json.put("iops", iops);
|
||||
}
|
||||
json.put("template", conn.getTemplateName());
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, null, csTag, null);
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeCreate", json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeUpdateRename(final String name, String newName, String uuid,
|
||||
SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("rename", newName);
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
tags.put("uuid", uuid);
|
||||
json.put("tags", tags);
|
||||
|
||||
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeUpdate(final String name, final Long newSize, final Boolean shrinkOk, Long iops,
|
||||
SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("iops", iops);
|
||||
json.put("size", newSize);
|
||||
json.put("shrinkOk", shrinkOk);
|
||||
|
||||
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeUpadateTags(final String name, final String uuid, Long iops,
|
||||
SpConnectionDesc conn, String vcPolicy) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy);
|
||||
json.put("iops", iops);
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeUpadateCvmTags(final String name, final String uuid, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null);
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeUpadateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy);
|
||||
json.put("tags", tags);
|
||||
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeSnapshot(final String volumeName, final String snapshotName, String vmUuid,
|
||||
String csTag, String vcPolicy, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy);
|
||||
json.put("name", "");
|
||||
json.put("tags", tags);
|
||||
|
||||
return POST("MultiCluster/VolumeSnapshot/" + volumeName, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumesGroupSnapshot(final List<VolumeObjectTO> volumeTOs, final String vmUuid,
|
||||
final String snapshotName, String csTag, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new LinkedHashMap<>();
|
||||
Map<String, String> tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null);
|
||||
List<Map<String, Object>> volumes = new ArrayList<>();
|
||||
for (VolumeObjectTO volumeTO : volumeTOs) {
|
||||
Map<String, Object> vol = new LinkedHashMap<>();
|
||||
String name = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeTO.getPath(), true);
|
||||
vol.put("name", "");
|
||||
vol.put("volume", name);
|
||||
volumes.add(vol);
|
||||
}
|
||||
json.put("tags", tags);
|
||||
json.put("volumes", volumes);
|
||||
log.info("json:" + json);
|
||||
return POST("MultiCluster/VolumesGroupSnapshot", json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeRevert(final String name, final String snapshotName, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("toSnapshot", snapshotName);
|
||||
return POST("MultiCluster/VolumeRevert/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeFreeze(final String volumeName, SpConnectionDesc conn) {
|
||||
return POST("MultiCluster/VolumeFreeze/" + volumeName, null, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeAcquire(final String volumeName, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("onRemoteAttached", "detachForce");
|
||||
return POST("MultiCluster/VolumeAcquire/" + volumeName, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse volumeDelete(final String name, SpConnectionDesc conn) {
|
||||
Map<String, Object> json = new HashMap<>();
|
||||
json.put("onAttached", "detachForce");
|
||||
return POST("MultiCluster/VolumeDelete/" + name, json, conn);
|
||||
}
|
||||
|
||||
public static SpApiResponse snapshotDelete(final String name, SpConnectionDesc conn) {
|
||||
SpApiResponse resp = detachAllForced(name, true, conn);
|
||||
return resp.getError() == null ? POST("MultiCluster/SnapshotDelete/" + name, null, conn) : resp;
|
||||
}
|
||||
|
||||
public static SpApiResponse detachAllForced(final String name, final boolean snapshot, SpConnectionDesc conn) {
|
||||
final String type = snapshot ? "snapshot" : "volume";
|
||||
List<Map<String, Object>> json = new ArrayList<>();
|
||||
Map<String, Object> reassignDesc = new HashMap<>();
|
||||
reassignDesc.put(type, name);
|
||||
reassignDesc.put("detach", "all");
|
||||
reassignDesc.put("force", true);
|
||||
json.add(reassignDesc);
|
||||
|
||||
return POST("MultiCluster/VolumesReassign", json, conn);
|
||||
}
|
||||
|
||||
public static String getSnapshotNameFromResponse(SpApiResponse resp, boolean tildeNeeded, String globalIdOrRemote) {
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
JsonPrimitive data = obj.getAsJsonObject("data").getAsJsonPrimitive(globalIdOrRemote);
|
||||
String name = data != null ? data.getAsString() : null;
|
||||
name = name != null ? !tildeNeeded ? name : "~" + name : name;
|
||||
return name;
|
||||
}
|
||||
|
||||
public static String getNameFromResponse(SpApiResponse resp, boolean tildeNeeded) {
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
JsonPrimitive data = obj.getAsJsonObject("data").getAsJsonPrimitive("name");
|
||||
String name = data != null ? data.getAsString() : null;
|
||||
name = name != null ? name.startsWith("~") && !tildeNeeded ? name.split("~")[1] : name : name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,575 @@
|
||||
/*
|
||||
* 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.motion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
|
||||
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.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
|
||||
import org.apache.cloudstack.framework.async.AsyncCallFuture;
|
||||
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.storage.RemoteHostEndPoint;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.MigrateAnswer;
|
||||
import com.cloud.agent.api.MigrateCommand;
|
||||
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
|
||||
import com.cloud.agent.api.ModifyTargetsAnswer;
|
||||
import com.cloud.agent.api.ModifyTargetsCommand;
|
||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||
import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
|
||||
import com.cloud.agent.api.to.DataObjectType;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.VMTemplateDetailVO;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.GuestOSCategoryDao;
|
||||
import com.cloud.storage.dao.GuestOSDao;
|
||||
import com.cloud.storage.dao.SnapshotDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||
import com.cloud.storage.dao.VMTemplateDetailsDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachineManager;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
@Component
|
||||
public class StorPoolDataMotionStrategy implements DataMotionStrategy {
|
||||
private static final Logger log = Logger.getLogger(StorPoolDataMotionStrategy.class);
|
||||
|
||||
@Inject
|
||||
private SnapshotDataFactory _snapshotDataFactory;
|
||||
@Inject
|
||||
private DataStoreManager _dataStore;
|
||||
@Inject
|
||||
private ConfigurationDao _configDao;
|
||||
@Inject
|
||||
private EndPointSelector _selector;
|
||||
@Inject
|
||||
private TemplateDataStoreDao _templStoreDao;
|
||||
@Inject
|
||||
private ClusterDao _clusterDao;
|
||||
@Inject
|
||||
private HostDao _hostDao;
|
||||
@Inject
|
||||
private SnapshotDetailsDao _snapshotDetailsDao;
|
||||
@Inject
|
||||
private VMTemplateDetailsDao _vmTemplateDetailsDao;
|
||||
@Inject
|
||||
private SnapshotDataStoreDao _snapshotStoreDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao _storagePoolDetails;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao _storagePool;
|
||||
@Inject
|
||||
private VolumeDao _volumeDao;
|
||||
@Inject
|
||||
private VolumeDataFactory _volumeDataFactory;
|
||||
@Inject
|
||||
private VMInstanceDao _vmDao;
|
||||
@Inject
|
||||
private GuestOSDao _guestOsDao;
|
||||
@Inject
|
||||
private VolumeService _volumeService;
|
||||
@Inject
|
||||
private GuestOSCategoryDao _guestOsCategoryDao;
|
||||
@Inject
|
||||
private SnapshotDao _snapshotDao;
|
||||
@Inject
|
||||
private AgentManager _agentManager;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao _storagePoolDao;
|
||||
|
||||
@Override
|
||||
public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
|
||||
DataObjectType srcType = srcData.getType();
|
||||
DataObjectType dstType = destData.getType();
|
||||
if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.TEMPLATE
|
||||
&& StorPoolConfigurationManager.BypassSecondaryStorage.value()) {
|
||||
SnapshotInfo sinfo = (SnapshotInfo) srcData;
|
||||
VolumeInfo volume = sinfo.getBaseVolume();
|
||||
StoragePoolVO storagePool = _storagePool.findById(volume.getPoolId());
|
||||
if (!storagePool.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
String snapshotName = StorPoolHelper.getSnapshotName(sinfo.getId(), sinfo.getUuid(), _snapshotStoreDao,
|
||||
_snapshotDetailsDao);
|
||||
StorPoolUtil.spLog("StorPoolDataMotionStrategy.canHandle snapshot name=%s", snapshotName);
|
||||
if (snapshotName != null) {
|
||||
return StrategyPriority.HIGHEST;
|
||||
}
|
||||
}
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAsync(DataObject srcData, DataObject destData, Host destHost,
|
||||
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||
SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData.getTO();
|
||||
TemplateObjectTO template = (TemplateObjectTO) destData.getTO();
|
||||
DataStore store = _dataStore.getDataStore(snapshot.getVolume().getDataStore().getUuid(),
|
||||
snapshot.getVolume().getDataStore().getRole());
|
||||
SnapshotInfo sInfo = _snapshotDataFactory.getSnapshot(snapshot.getId(), store);
|
||||
|
||||
VolumeInfo vInfo = sInfo.getBaseVolume();
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(vInfo.getDataStore().getUuid(),
|
||||
vInfo.getDataStore().getId(), _storagePoolDetails, _storagePool);
|
||||
String name = template.getUuid();
|
||||
String volumeName = "";
|
||||
|
||||
String parentName = StorPoolHelper.getSnapshotName(sInfo.getId(), sInfo.getUuid(), _snapshotStoreDao,
|
||||
_snapshotDetailsDao);
|
||||
// TODO volume tags cs - template
|
||||
SpApiResponse res = StorPoolUtil.volumeCreate(name, parentName, sInfo.getSize(), null, "no", "template", null,
|
||||
conn);
|
||||
CopyCmdAnswer answer = null;
|
||||
String err = null;
|
||||
if (res.getError() != null) {
|
||||
log.debug(String.format("Could not create volume from snapshot with ID=%s", snapshot.getId()));
|
||||
StorPoolUtil.spLog("Volume create failed with error=%s", res.getError().getDescr());
|
||||
err = res.getError().getDescr();
|
||||
} else {
|
||||
volumeName = StorPoolUtil.getNameFromResponse(res, true);
|
||||
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(sInfo.getId(), sInfo.getUuid());
|
||||
|
||||
snapshot.setPath(snapshotDetails.getValue());
|
||||
Command backupSnapshot = new StorPoolBackupTemplateFromSnapshotCommand(snapshot, template,
|
||||
StorPoolHelper.getTimeout(StorPoolHelper.BackupSnapshotWait, _configDao),
|
||||
VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
try {
|
||||
// final String snapName =
|
||||
// StorpoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo)
|
||||
// srcData).getPath(), true);
|
||||
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(parentName, _clusterDao);
|
||||
EndPoint ep2 = clusterId != null
|
||||
? RemoteHostEndPoint
|
||||
.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, _hostDao))
|
||||
: _selector.select(sInfo, destData);
|
||||
if (ep2 == null) {
|
||||
err = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||
} else {
|
||||
answer = (CopyCmdAnswer) ep2.sendMessage(backupSnapshot);
|
||||
if (answer != null && answer.getResult()) {
|
||||
SpApiResponse resSnapshot = StorPoolUtil.volumeFreeze(volumeName, conn);
|
||||
if (resSnapshot.getError() != null) {
|
||||
log.debug(String.format("Could not snapshot volume with ID=%s", snapshot.getId()));
|
||||
StorPoolUtil.spLog("Volume freeze failed with error=%s", resSnapshot.getError().getDescr());
|
||||
err = resSnapshot.getError().getDescr();
|
||||
StorPoolUtil.volumeDelete(volumeName, conn);
|
||||
} else {
|
||||
StorPoolHelper.updateVmStoreTemplate(template.getId(), template.getDataStore().getRole(),
|
||||
StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(res, false)), _templStoreDao);
|
||||
}
|
||||
} else {
|
||||
err = "Could not copy template to secondary " + answer.getResult();
|
||||
StorPoolUtil.volumeDelete(StorPoolUtil.getNameFromResponse(res, true), conn);
|
||||
}
|
||||
}
|
||||
} catch (CloudRuntimeException e) {
|
||||
err = e.getMessage();
|
||||
}
|
||||
}
|
||||
_vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID,
|
||||
String.valueOf(vInfo.getDataStore().getId()), false));
|
||||
StorPoolUtil.spLog("StorPoolDataMotionStrategy.copyAsync Creating snapshot=%s for StorPool template=%s",
|
||||
volumeName, conn.getTemplateName());
|
||||
final CopyCommandResult cmd = new CopyCommandResult(null, answer);
|
||||
cmd.setResult(err);
|
||||
callback.complete(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
|
||||
return canHandleLiveMigrationOnStorPool(volumeMap, srcHost, destHost);
|
||||
}
|
||||
|
||||
final StrategyPriority canHandleLiveMigrationOnStorPool(Map<VolumeInfo, DataStore> volumeMap, Host srcHost,
|
||||
Host destHost) {
|
||||
if (srcHost.getId() != destHost.getId() && isDestinationStorPoolPrimaryStorage(volumeMap)) {
|
||||
return StrategyPriority.HIGHEST;
|
||||
}
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
|
||||
private boolean isDestinationStorPoolPrimaryStorage(Map<VolumeInfo, DataStore> volumeMap) {
|
||||
if (MapUtils.isNotEmpty(volumeMap)) {
|
||||
for (DataStore dataStore : volumeMap.values()) {
|
||||
StoragePoolVO storagePoolVO = _storagePool.findById(dataStore.getId());
|
||||
if (storagePoolVO == null
|
||||
|| !storagePoolVO.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost,
|
||||
Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||
String errMsg = null;
|
||||
String newVolName = null;
|
||||
SpConnectionDesc conn = null;
|
||||
|
||||
try {
|
||||
if (srcHost.getHypervisorType() != HypervisorType.KVM) {
|
||||
throw new CloudRuntimeException(String.format("Invalid hypervisor type [%s]. Only KVM supported", srcHost.getHypervisorType()));
|
||||
}
|
||||
|
||||
VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId());
|
||||
vmTO.setState(vmInstance.getState());
|
||||
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<MigrateDiskInfo>();
|
||||
|
||||
Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
|
||||
Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
|
||||
|
||||
for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
|
||||
VolumeInfo srcVolumeInfo = entry.getKey();
|
||||
DataStore destDataStore = entry.getValue();
|
||||
|
||||
VolumeVO srcVolume = _volumeDao.findById(srcVolumeInfo.getId());
|
||||
StoragePoolVO destStoragePool = _storagePool.findById(destDataStore.getId());
|
||||
|
||||
VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
|
||||
|
||||
VolumeInfo destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
|
||||
|
||||
destVolumeInfo.processEvent(Event.MigrationCopyRequested);
|
||||
destVolumeInfo.processEvent(Event.MigrationCopySucceeded);
|
||||
destVolumeInfo.processEvent(Event.MigrationRequested);
|
||||
|
||||
conn = StorPoolUtil.getSpConnection(destDataStore.getUuid(), destDataStore.getId(), _storagePoolDetails,
|
||||
_storagePool);
|
||||
SpApiResponse resp = StorPoolUtil.volumeCreate(srcVolume.getUuid(), null, srcVolume.getSize(),
|
||||
vmTO.getUuid(), null, "volume", srcVolume.getMaxIops(), conn);
|
||||
|
||||
if (resp.getError() == null) {
|
||||
newVolName = StorPoolUtil.getNameFromResponse(resp, true);
|
||||
}
|
||||
|
||||
String volumeName = StorPoolUtil.getNameFromResponse(resp, false);
|
||||
destVolume.setPath(StorPoolUtil.devPath(volumeName));
|
||||
_volumeDao.update(destVolume.getId(), destVolume);
|
||||
destVolume = _volumeDao.findById(destVolume.getId());
|
||||
|
||||
destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
|
||||
|
||||
String destPath = generateDestPath(destHost, destStoragePool, destVolumeInfo);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath);
|
||||
migrateDiskInfoList.add(migrateDiskInfo);
|
||||
|
||||
migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
|
||||
|
||||
srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo);
|
||||
}
|
||||
|
||||
PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
|
||||
|
||||
try {
|
||||
Answer pfma = _agentManager.send(destHost.getId(), pfmc);
|
||||
|
||||
if (pfma == null || !pfma.getResult()) {
|
||||
String details = pfma != null ? pfma.getDetails() : "null answer returned";
|
||||
errMsg = String.format("Unable to prepare for migration due to the following: %s", details);
|
||||
|
||||
throw new AgentUnavailableException(errMsg, destHost.getId());
|
||||
}
|
||||
} catch (final OperationTimedoutException e) {
|
||||
errMsg = String.format("Operation timed out due to %s", e.getMessage());
|
||||
throw new AgentUnavailableException(errMsg, destHost.getId());
|
||||
}
|
||||
|
||||
VMInstanceVO vm = _vmDao.findById(vmTO.getId());
|
||||
boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId())
|
||||
.getName().equalsIgnoreCase("Windows");
|
||||
|
||||
MigrateCommand migrateCommand = new MigrateCommand(vmTO.getName(),
|
||||
destHost.getPrivateIpAddress(), isWindows, vmTO, true);
|
||||
migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value());
|
||||
migrateCommand.setMigrateStorage(migrateStorage);
|
||||
migrateCommand.setMigrateStorageManaged(true);
|
||||
migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList);
|
||||
|
||||
boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
|
||||
|
||||
migrateCommand.setAutoConvergence(kvmAutoConvergence);
|
||||
|
||||
MigrateAnswer migrateAnswer = (MigrateAnswer) _agentManager.send(srcHost.getId(), migrateCommand);
|
||||
|
||||
boolean success = migrateAnswer != null && migrateAnswer.getResult();
|
||||
|
||||
handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost);
|
||||
|
||||
if (migrateAnswer == null) {
|
||||
throw new CloudRuntimeException("Unable to get an answer to the migrate command");
|
||||
}
|
||||
|
||||
if (!migrateAnswer.getResult()) {
|
||||
errMsg = migrateAnswer.getDetails();
|
||||
|
||||
throw new CloudRuntimeException(errMsg);
|
||||
}
|
||||
} catch (AgentUnavailableException | OperationTimedoutException | CloudRuntimeException ex) {
|
||||
|
||||
errMsg = String.format(
|
||||
"Copy volume(s) of VM [%s] to storage(s) [%s] and VM to host [%s] failed in StorPoolDataMotionStrategy.copyAsync. Error message: [%s].",
|
||||
vmTO.getId(), srcHost.getId(), destHost.getId(), ex.getMessage());
|
||||
log.error(errMsg, ex);
|
||||
|
||||
throw new CloudRuntimeException(errMsg);
|
||||
} finally {
|
||||
if (errMsg != null) {
|
||||
deleteVolumeOnFail(newVolName, conn);
|
||||
}
|
||||
CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||
|
||||
CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
|
||||
|
||||
result.setResult(errMsg);
|
||||
|
||||
callback.complete(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteVolumeOnFail(String newVolName, SpConnectionDesc conn) {
|
||||
if (newVolName != null && conn != null) {
|
||||
StorPoolUtil.volumeDelete(newVolName, conn);
|
||||
}
|
||||
}
|
||||
|
||||
private VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePoolVO storagePoolVO) {
|
||||
Long lastPoolId = volume.getPoolId();
|
||||
|
||||
VolumeVO newVol = new VolumeVO(volume);
|
||||
|
||||
newVol.setInstanceId(null);
|
||||
newVol.setChainInfo(null);
|
||||
newVol.setPath(null);
|
||||
newVol.setFolder(null);
|
||||
newVol.setPodId(storagePoolVO.getPodId());
|
||||
newVol.setPoolId(storagePoolVO.getId());
|
||||
newVol.setLastPoolId(lastPoolId);
|
||||
|
||||
return _volumeDao.persist(newVol);
|
||||
}
|
||||
|
||||
private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo,
|
||||
VirtualMachineTO vmTO, Host destHost) {
|
||||
if (!success) {
|
||||
try {
|
||||
PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
|
||||
|
||||
pfmc.setRollback(true);
|
||||
|
||||
Answer pfma = _agentManager.send(destHost.getId(), pfmc);
|
||||
|
||||
if (pfma == null || !pfma.getResult()) {
|
||||
String details = pfma != null ? pfma.getDetails() : "null answer returned";
|
||||
String msg = "Unable to rollback prepare for migration due to the following: " + details;
|
||||
|
||||
throw new AgentUnavailableException(msg, destHost.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to disconnect one or more (original) dest volumes", e);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<VolumeInfo, VolumeInfo> entry : srcVolumeInfoToDestVolumeInfo.entrySet()) {
|
||||
VolumeInfo srcVolumeInfo = entry.getKey();
|
||||
VolumeInfo destVolumeInfo = entry.getValue();
|
||||
|
||||
if (success) {
|
||||
srcVolumeInfo.processEvent(Event.OperationSuccessed);
|
||||
destVolumeInfo.processEvent(Event.OperationSuccessed);
|
||||
|
||||
_volumeDao.updateUuid(srcVolumeInfo.getId(), destVolumeInfo.getId());
|
||||
|
||||
VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
||||
|
||||
volumeVO.setFormat(ImageFormat.QCOW2);
|
||||
|
||||
_volumeDao.update(volumeVO.getId(), volumeVO);
|
||||
|
||||
try {
|
||||
_volumeService.destroyVolume(srcVolumeInfo.getId());
|
||||
|
||||
srcVolumeInfo = _volumeDataFactory.getVolume(srcVolumeInfo.getId());
|
||||
|
||||
AsyncCallFuture<VolumeApiResult> destroyFuture = _volumeService.expungeVolumeAsync(srcVolumeInfo);
|
||||
|
||||
if (destroyFuture.get().isFailed()) {
|
||||
log.debug("Failed to clean up source volume on storage");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to clean up source volume on storage", e);
|
||||
}
|
||||
|
||||
// Update the volume ID for snapshots on secondary storage
|
||||
if (!_snapshotDao.listByVolumeId(srcVolumeInfo.getId()).isEmpty()) {
|
||||
_snapshotDao.updateVolumeIds(srcVolumeInfo.getId(), destVolumeInfo.getId());
|
||||
_snapshotStoreDao.updateVolumeIds(srcVolumeInfo.getId(), destVolumeInfo.getId());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
disconnectHostFromVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.getPath());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to disconnect (new) dest volume", e);
|
||||
}
|
||||
|
||||
try {
|
||||
_volumeService.revokeAccess(destVolumeInfo, destHost, destVolumeInfo.getDataStore());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to revoke access from dest volume", e);
|
||||
}
|
||||
|
||||
destVolumeInfo.processEvent(Event.OperationFailed);
|
||||
srcVolumeInfo.processEvent(Event.OperationFailed);
|
||||
|
||||
try {
|
||||
_volumeService.destroyVolume(destVolumeInfo.getId());
|
||||
|
||||
destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId());
|
||||
|
||||
AsyncCallFuture<VolumeApiResult> destroyFuture = _volumeService.expungeVolumeAsync(destVolumeInfo);
|
||||
|
||||
if (destroyFuture.get().isFailed()) {
|
||||
log.debug("Failed to clean up dest volume on storage");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to clean up dest volume on storage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String generateDestPath(Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
|
||||
return connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.getPath());
|
||||
}
|
||||
|
||||
private String connectHostToVolume(Host host, long storagePoolId, String iqn) {
|
||||
ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, true);
|
||||
|
||||
return sendModifyTargetsCommand(modifyTargetsCommand, host.getId()).get(0);
|
||||
}
|
||||
|
||||
private void disconnectHostFromVolume(Host host, long storagePoolId, String iqn) {
|
||||
ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, false);
|
||||
|
||||
sendModifyTargetsCommand(modifyTargetsCommand, host.getId());
|
||||
}
|
||||
|
||||
private ModifyTargetsCommand getModifyTargetsCommand(long storagePoolId, String iqn, boolean add) {
|
||||
StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
|
||||
|
||||
Map<String, String> details = new HashMap<>();
|
||||
|
||||
details.put(ModifyTargetsCommand.IQN, iqn);
|
||||
details.put(ModifyTargetsCommand.STORAGE_TYPE, storagePool.getPoolType().name());
|
||||
details.put(ModifyTargetsCommand.STORAGE_UUID, storagePool.getUuid());
|
||||
details.put(ModifyTargetsCommand.STORAGE_HOST, storagePool.getHostAddress());
|
||||
details.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePool.getPort()));
|
||||
|
||||
ModifyTargetsCommand cmd = new ModifyTargetsCommand();
|
||||
|
||||
List<Map<String, String>> targets = new ArrayList<>();
|
||||
|
||||
targets.add(details);
|
||||
|
||||
cmd.setTargets(targets);
|
||||
cmd.setApplyToAllHostsInCluster(true);
|
||||
cmd.setAdd(add);
|
||||
cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private List<String> sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
|
||||
ModifyTargetsAnswer modifyTargetsAnswer = (ModifyTargetsAnswer) _agentManager.easySend(hostId, cmd);
|
||||
|
||||
if (modifyTargetsAnswer == null) {
|
||||
throw new CloudRuntimeException("Unable to get an answer to the modify targets command");
|
||||
}
|
||||
|
||||
if (!modifyTargetsAnswer.getResult()) {
|
||||
String msg = "Unable to modify targets on the following host: " + hostId;
|
||||
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
return modifyTargetsAnswer.getConnectedPaths();
|
||||
}
|
||||
|
||||
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
|
||||
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, MigrateCommand.MigrateDiskInfo.DriverType.RAW,
|
||||
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.snapshot;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
||||
public class StorPoolConfigurationManager implements Configurable {
|
||||
|
||||
public static final ConfigKey<Boolean> BypassSecondaryStorage = new ConfigKey<Boolean>(Boolean.class, "sp.bypass.secondary.storage", "Advanced", "false",
|
||||
"For StorPool Managed storage backup to secondary", true, ConfigKey.Scope.Global, null);
|
||||
public static final ConfigKey<String> StorPoolClusterId = new ConfigKey<String>(String.class, "sp.cluster.id", "Advanced", "n/a",
|
||||
"For StorPool multi cluster authorization", true, ConfigKey.Scope.Cluster, null);
|
||||
public static final ConfigKey<Boolean> AlternativeEndPointEnabled = new ConfigKey<Boolean>(Boolean.class, "sp.enable.alternative.endpoint", "Advanced", "false",
|
||||
"Used for StorPool primary storage, definse if there is a need to be used alternative endpoint", true, ConfigKey.Scope.StoragePool, null);
|
||||
|
||||
public static final ConfigKey<String> AlternativeEndpoint = new ConfigKey<String>(String.class, "sp.alternative.endpoint", "Advanced", "",
|
||||
"Used for StorPool primary storage for an alternative endpoint. Structure of the endpoint is - SP_API_HTTP=address:port;SP_AUTH_TOKEN=token;SP_TEMPLATE=template_name", true, ConfigKey.Scope.StoragePool, null);
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return StorPoolConfigurationManager.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] { BypassSecondaryStorage, StorPoolClusterId, AlternativeEndPointEnabled, AlternativeEndpoint };
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,289 @@
|
||||
// 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.snapshot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.Snapshot;
|
||||
import com.cloud.storage.SnapshotVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.SnapshotDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||
import com.cloud.storage.dao.SnapshotDetailsVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.fsm.NoTransitionException;
|
||||
|
||||
|
||||
@Component
|
||||
public class StorPoolSnapshotStrategy implements SnapshotStrategy {
|
||||
private static final Logger log = Logger.getLogger(StorPoolSnapshotStrategy.class);
|
||||
|
||||
@Inject
|
||||
private SnapshotDao _snapshotDao;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao _primaryDataStoreDao;
|
||||
@Inject
|
||||
private VolumeDao _volumeDao;
|
||||
@Inject
|
||||
private SnapshotDataStoreDao _snapshotStoreDao;
|
||||
@Inject
|
||||
private SnapshotDetailsDao _snapshotDetailsDao;
|
||||
@Inject
|
||||
private SnapshotService snapshotSvr;
|
||||
@Inject
|
||||
private SnapshotDataFactory snapshotDataFactory;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
|
||||
@Override
|
||||
public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
|
||||
SnapshotObject snapshotObj = (SnapshotObject) snapshotInfo;
|
||||
try {
|
||||
snapshotObj.processEvent(Snapshot.Event.BackupToSecondary);
|
||||
snapshotObj.processEvent(Snapshot.Event.OperationSucceeded);
|
||||
} catch (NoTransitionException ex) {
|
||||
StorPoolUtil.spLog("Failed to change state: " + ex.toString());
|
||||
try {
|
||||
snapshotObj.processEvent(Snapshot.Event.OperationFailed);
|
||||
} catch (NoTransitionException ex2) {
|
||||
StorPoolUtil.spLog("Failed to change state: " + ex2.toString());
|
||||
}
|
||||
}
|
||||
return snapshotInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSnapshot(Long snapshotId) {
|
||||
|
||||
final SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
|
||||
VolumeVO volume = _volumeDao.findByIdIncludingRemoved(snapshotVO.getVolumeId());
|
||||
String name = StorPoolHelper.getSnapshotName(snapshotId, snapshotVO.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
|
||||
boolean res = false;
|
||||
// clean-up snapshot from Storpool storage pools
|
||||
StoragePoolVO storage = _primaryDataStoreDao.findById(volume.getPoolId());
|
||||
if (storage.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
|
||||
try {
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(storage.getUuid(), storage.getId(), storagePoolDetailsDao, _primaryDataStoreDao);
|
||||
SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn);
|
||||
if (resp.getError() != null) {
|
||||
final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError());
|
||||
StorPoolUtil.spLog(err);
|
||||
} else {
|
||||
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
|
||||
if (snapshotDetails != null) {
|
||||
_snapshotDetailsDao.removeDetails(snapshotId);
|
||||
}
|
||||
res = deleteSnapshotFromDb(snapshotId);
|
||||
StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfuly=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errMsg = String.format("Cannot delete snapshot due to %s", e.getMessage());
|
||||
throw new CloudRuntimeException(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
|
||||
StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op);
|
||||
|
||||
if (op != SnapshotOperation.DELETE) {
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
|
||||
String name = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
|
||||
if (name != null) {
|
||||
StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: globalId=%s", name);
|
||||
|
||||
return StrategyPriority.HIGHEST;
|
||||
}
|
||||
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshot.getId(), snapshot.getUuid());
|
||||
if (snapshotDetails != null) {
|
||||
_snapshotDetailsDao.remove(snapshotDetails.getId());
|
||||
}
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
|
||||
private boolean deleteSnapshotChain(SnapshotInfo snapshot) {
|
||||
log.debug("delete snapshot chain for snapshot: " + snapshot.getId());
|
||||
boolean result = false;
|
||||
boolean resultIsSet = false;
|
||||
try {
|
||||
while (snapshot != null &&
|
||||
(snapshot.getState() == Snapshot.State.Destroying || snapshot.getState() == Snapshot.State.Destroyed || snapshot.getState() == Snapshot.State.Error)) {
|
||||
SnapshotInfo child = snapshot.getChild();
|
||||
|
||||
if (child != null) {
|
||||
log.debug("the snapshot has child, can't delete it on the storage");
|
||||
break;
|
||||
}
|
||||
log.debug("Snapshot: " + snapshot.getId() + " doesn't have children, so it's ok to delete it and its parents");
|
||||
SnapshotInfo parent = snapshot.getParent();
|
||||
boolean deleted = false;
|
||||
if (parent != null) {
|
||||
if (parent.getPath() != null && parent.getPath().equalsIgnoreCase(snapshot.getPath())) {
|
||||
log.debug("for empty delta snapshot, only mark it as destroyed in db");
|
||||
snapshot.processEvent(Event.DestroyRequested);
|
||||
snapshot.processEvent(Event.OperationSuccessed);
|
||||
deleted = true;
|
||||
if (!resultIsSet) {
|
||||
result = true;
|
||||
resultIsSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!deleted) {
|
||||
SnapshotInfo snap = snapshotDataFactory.getSnapshot(snapshot.getId(), DataStoreRole.Image);
|
||||
if (StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getPath(), true) == null) {
|
||||
try {
|
||||
boolean r = snapshotSvr.deleteSnapshot(snapshot);
|
||||
if (r) {
|
||||
List<SnapshotInfo> cacheSnaps = snapshotDataFactory.listSnapshotOnCache(snapshot.getId());
|
||||
for (SnapshotInfo cacheSnap : cacheSnaps) {
|
||||
log.debug("Delete snapshot " + snapshot.getId() + " from image cache store: " + cacheSnap.getDataStore().getName());
|
||||
cacheSnap.delete();
|
||||
}
|
||||
}
|
||||
if (!resultIsSet) {
|
||||
result = r;
|
||||
resultIsSet = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to delete snapshot on storage. ", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
snapshot = parent;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("delete snapshot failed: ", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean deleteSnapshotFromDb(Long snapshotId) {
|
||||
SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
|
||||
|
||||
if (snapshotVO.getState() == Snapshot.State.Allocated) {
|
||||
_snapshotDao.remove(snapshotId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (snapshotVO.getState() == Snapshot.State.Destroyed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
|
||||
List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
|
||||
for (SnapshotDataStoreVO ref : storeRefs) {
|
||||
_snapshotStoreDao.expunge(ref.getId());
|
||||
}
|
||||
_snapshotDao.remove(snapshotId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (snapshotVO.getState() == Snapshot.State.CreatedOnPrimary) {
|
||||
snapshotVO.setState(Snapshot.State.Destroyed);
|
||||
_snapshotDao.update(snapshotId, snapshotVO);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) && !Snapshot.State.Error.equals(snapshotVO.getState()) &&
|
||||
!Snapshot.State.Destroying.equals(snapshotVO.getState())) {
|
||||
throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
|
||||
}
|
||||
|
||||
SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image);
|
||||
if (snapshotOnImage == null) {
|
||||
log.debug("Can't find snapshot on backup storage, delete it in db");
|
||||
_snapshotDao.remove(snapshotId);
|
||||
return true;
|
||||
}
|
||||
|
||||
SnapshotObject obj = (SnapshotObject)snapshotOnImage;
|
||||
try {
|
||||
obj.processEvent(Snapshot.Event.DestroyRequested);
|
||||
} catch (NoTransitionException e) {
|
||||
log.debug("Failed to set the state to destroying: ", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean result = deleteSnapshotChain(snapshotOnImage);
|
||||
obj.processEvent(Snapshot.Event.OperationSucceeded);
|
||||
if (result) {
|
||||
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
|
||||
if (snapshotOnPrimary != null) {
|
||||
snapshotOnPrimary.setState(State.Destroyed);
|
||||
_snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to delete snapshot: ", e);
|
||||
try {
|
||||
obj.processEvent(Snapshot.Event.OperationFailed);
|
||||
} catch (NoTransitionException e1) {
|
||||
log.debug("Failed to change snapshot state: " + e.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revertSnapshot(SnapshotInfo snapshot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSnapshotCreation(SnapshotInfo snapshot) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,387 @@
|
||||
//
|
||||
//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.snapshot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
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.StrategyPriority;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
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.StorPoolUtil;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
|
||||
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy;
|
||||
import org.apache.cloudstack.storage.vmsnapshot.VMSnapshotHelper;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.agent.api.VMSnapshotTO;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.event.UsageEventUtils;
|
||||
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
|
||||
import com.cloud.storage.DiskOfferingVO;
|
||||
import com.cloud.storage.VolumeDetailVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.dao.VolumeDetailsDao;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.fsm.NoTransitionException;
|
||||
import com.cloud.vm.UserVmVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.snapshot.VMSnapshot;
|
||||
import com.cloud.vm.snapshot.VMSnapshotDetailsVO;
|
||||
import com.cloud.vm.snapshot.VMSnapshotVO;
|
||||
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
||||
import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@Component
|
||||
public class StorPoolVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
|
||||
private static final Logger log = Logger.getLogger(StorPoolVMSnapshotStrategy.class);
|
||||
|
||||
@Inject
|
||||
private VMSnapshotHelper vmSnapshotHelper;
|
||||
@Inject
|
||||
private UserVmDao userVmDao;
|
||||
@Inject
|
||||
private VMSnapshotDao vmSnapshotDao;
|
||||
@Inject
|
||||
private VolumeDao volumeDao;
|
||||
@Inject
|
||||
private DiskOfferingDao diskOfferingDao;
|
||||
@Inject
|
||||
private PrimaryDataStoreDao storagePool;
|
||||
@Inject
|
||||
private VMSnapshotDetailsDao vmSnapshotDetailsDao;
|
||||
@Inject
|
||||
private VolumeDataFactory volFactory;
|
||||
@Inject
|
||||
private VolumeDetailsDao volumeDetailsDao;
|
||||
@Inject
|
||||
private StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
@Inject
|
||||
private DataStoreManager dataStoreManager;
|
||||
int _wait;
|
||||
|
||||
@Override
|
||||
public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
|
||||
log.info("KVMVMSnapshotStrategy take snapshot");
|
||||
UserVm userVm = userVmDao.findById(vmSnapshot.getVmId());
|
||||
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
|
||||
|
||||
try {
|
||||
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested);
|
||||
} catch (NoTransitionException e) {
|
||||
throw new CloudRuntimeException("No transiontion " + e.getMessage());
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
try {
|
||||
|
||||
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
|
||||
DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
|
||||
|
||||
long prev_chain_size = 0;
|
||||
long virtual_size = 0;
|
||||
for (VolumeObjectTO volume : volumeTOs) {
|
||||
virtual_size += volume.getSize();
|
||||
VolumeVO volumeVO = volumeDao.findById(volume.getId());
|
||||
prev_chain_size += volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize();
|
||||
}
|
||||
|
||||
VMSnapshotTO current = null;
|
||||
VMSnapshotVO currentSnapshot = vmSnapshotDao.findCurrentSnapshotByVmId(userVm.getId());
|
||||
if (currentSnapshot != null) {
|
||||
current = vmSnapshotHelper.getSnapshotWithParents(currentSnapshot);
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
vmSnapshotVO.setParent(null);
|
||||
} else {
|
||||
vmSnapshotVO.setParent(current.getId());
|
||||
}
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.volumesGroupSnapshot(volumeTOs, userVm.getUuid(), vmSnapshotVO.getUuid(), "group", conn);
|
||||
JsonObject obj = resp.fullJson.getAsJsonObject();
|
||||
JsonArray snapshots = obj.getAsJsonObject("data").getAsJsonArray("snapshots");
|
||||
StorPoolUtil.spLog("Volumes=%s attached to virtual machine", volumeTOs.toString());
|
||||
for (VolumeObjectTO vol : volumeTOs) {
|
||||
for (JsonElement jsonElement : snapshots) {
|
||||
JsonObject snapshotObject = jsonElement.getAsJsonObject();
|
||||
String snapshot = StorPoolUtil
|
||||
.devPath(snapshotObject.getAsJsonPrimitive(StorPoolUtil.GLOBAL_ID).getAsString());
|
||||
if (snapshotObject.getAsJsonPrimitive("volume").getAsString().equals(StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true))
|
||||
|| snapshotObject.getAsJsonPrimitive("volumeGlobalId").getAsString().equals(StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), false))) {
|
||||
VMSnapshotDetailsVO vmSnapshotDetailsVO = new VMSnapshotDetailsVO(vmSnapshot.getId(), vol.getUuid(), snapshot, false);
|
||||
vmSnapshotDetailsDao.persist(vmSnapshotDetailsVO);
|
||||
Long poolId = volumeDao.findById(vol.getId()).getPoolId();
|
||||
if (poolId != null) {
|
||||
VMSnapshotDetailsVO vmSnapshotDetailStoragePoolId = new VMSnapshotDetailsVO(
|
||||
vmSnapshot.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, String.valueOf(poolId), false);
|
||||
vmSnapshotDetailsDao.persist(vmSnapshotDetailStoragePoolId);
|
||||
}
|
||||
StorPoolUtil.spLog("Snapshot=%s of volume=%s for a group snapshot=%s.", snapshot, vol.getUuid(), vmSnapshot.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resp.getError() == null) {
|
||||
StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.takeSnapshot answer=%s", resp.getError());
|
||||
finalizeCreate(vmSnapshotVO, volumeTOs);
|
||||
result = vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
|
||||
long new_chain_size = 0;
|
||||
for (VolumeObjectTO volumeObjectTO : volumeTOs) {
|
||||
publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_CREATE, vmSnapshot, userVm, volumeObjectTO);
|
||||
new_chain_size += volumeObjectTO.getSize();
|
||||
log.info("EventTypes.EVENT_VM_SNAPSHOT_CREATE publishUsageEvent" + volumeObjectTO);
|
||||
}
|
||||
publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY, vmSnapshot, userVm, new_chain_size - prev_chain_size, virtual_size);
|
||||
} else {
|
||||
throw new CloudRuntimeException("Could not create vm snapshot");
|
||||
}
|
||||
return vmSnapshot;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not create VM snapshot:" + e.getMessage());
|
||||
throw new CloudRuntimeException("Could not create VM snapshot:" + e.getMessage());
|
||||
} finally {
|
||||
if (!result) {
|
||||
try {
|
||||
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
|
||||
log.info(String.format("VMSnapshot.Event.OperationFailed vmSnapshot=%s", vmSnapshot));
|
||||
} catch (NoTransitionException nte) {
|
||||
log.error("Cannot set vm state:" + nte.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
|
||||
return areAllVolumesOnStorPool(vmSnapshot.getVmId());
|
||||
}
|
||||
|
||||
public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) {
|
||||
if (snapshotMemory) {
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
return areAllVolumesOnStorPool(vmId);
|
||||
}
|
||||
|
||||
private StrategyPriority areAllVolumesOnStorPool(Long vmId) {
|
||||
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmId);
|
||||
if (volumeTOs == null || volumeTOs.isEmpty()) {
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
for (VolumeObjectTO volumeTO : volumeTOs) {
|
||||
Long poolId = volumeTO.getPoolId();
|
||||
StoragePoolVO pool = storagePool.findById(poolId);
|
||||
if (!pool.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) {
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
}
|
||||
return StrategyPriority.HIGHEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) {
|
||||
UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
|
||||
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
|
||||
try {
|
||||
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.ExpungeRequested);
|
||||
} catch (NoTransitionException e) {
|
||||
log.debug("Failed to change vm snapshot state with event ExpungeRequested");
|
||||
throw new CloudRuntimeException(
|
||||
"Failed to change vm snapshot state with event ExpungeRequested: " + e.getMessage());
|
||||
}
|
||||
|
||||
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
|
||||
DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
|
||||
SpConnectionDesc conn = null;
|
||||
try {
|
||||
conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
|
||||
} catch (CloudRuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
SpApiResponse resp = null;
|
||||
for (VolumeObjectTO volumeObjectTO : volumeTOs) {
|
||||
String err = null;
|
||||
VMSnapshotDetailsVO snapshotDetailsVO = vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(), volumeObjectTO.getUuid());
|
||||
String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDetailsVO.getValue(), true);
|
||||
if (snapshotName == null) {
|
||||
err = String.format("Could not find StorPool's snapshot vm snapshot uuid=%s and volume uui=%s",
|
||||
vmSnapshot.getUuid(), volumeObjectTO.getUuid());
|
||||
log.error("Could not delete snapshot for vm:" + err);
|
||||
}
|
||||
StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.deleteVMSnapshot snapshotName=%s", snapshotName);
|
||||
resp = StorPoolUtil.snapshotDelete(snapshotName, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not delete storpool vm error=%s", resp.getError());
|
||||
log.error("Could not delete snapshot for vm:" + err);
|
||||
} else {
|
||||
// do we need to clean database?
|
||||
if (snapshotDetailsVO != null) {
|
||||
vmSnapshotDetailsDao.remove(snapshotDetailsVO.getId());
|
||||
}
|
||||
}
|
||||
if (err != null) {
|
||||
StorPoolUtil.spLog(
|
||||
"StorpoolVMSnapshotStrategy.deleteVMSnapshot delete snapshot=%s of gropusnapshot=%s failed due to %s",
|
||||
snapshotName, userVm.getInstanceName(), err);
|
||||
throw new CloudRuntimeException("Delete vm snapshot " + vmSnapshot.getName() + " of vm "
|
||||
+ userVm.getInstanceName() + " failed due to " + err);
|
||||
}
|
||||
}
|
||||
vmSnapshotDetailsDao.removeDetails(vmSnapshot.getId());
|
||||
|
||||
finalizeDelete(vmSnapshotVO, volumeTOs);
|
||||
vmSnapshotDao.remove(vmSnapshot.getId());
|
||||
|
||||
long full_chain_size = 0;
|
||||
for (VolumeObjectTO volumeTo : volumeTOs) {
|
||||
publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo);
|
||||
full_chain_size += volumeTo.getSize();
|
||||
}
|
||||
publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revertVMSnapshot(VMSnapshot vmSnapshot) {
|
||||
log.debug("Revert vm snapshot");
|
||||
VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot;
|
||||
UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
|
||||
|
||||
if (userVm.getState() == VirtualMachine.State.Running && vmSnapshotVO.getType() == VMSnapshot.Type.Disk) {
|
||||
throw new CloudRuntimeException("Virtual machine should be in stopped state for revert operation");
|
||||
}
|
||||
|
||||
try {
|
||||
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.RevertRequested);
|
||||
} catch (NoTransitionException e) {
|
||||
throw new CloudRuntimeException(e.getMessage());
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
try {
|
||||
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId());
|
||||
|
||||
DataStore dataStore = dataStoreManager.getPrimaryDataStore(volumeTOs.get(0).getDataStore().getUuid());
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, storagePool);
|
||||
for (VolumeObjectTO volumeObjectTO : volumeTOs) {
|
||||
String err = null;
|
||||
VMSnapshotDetailsVO snapshotDetailsVO = vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(),
|
||||
volumeObjectTO.getUuid());
|
||||
String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshotDetailsVO.getValue(), true);
|
||||
if (snapshotName == null) {
|
||||
err = String.format("Could not find StorPool's snapshot vm snapshot uuid=%s and volume uui=%s",
|
||||
vmSnapshot.getUuid(), volumeObjectTO.getUuid());
|
||||
log.error("Could not delete snapshot for vm:" + err);
|
||||
}
|
||||
String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeObjectTO.getPath(), true);
|
||||
VolumeDetailVO detail = volumeDetailsDao.findDetail(volumeObjectTO.getId(), StorPoolUtil.SP_PROVIDER_NAME);
|
||||
if (detail != null) {
|
||||
SpApiResponse updateVolumeResponse = StorPoolUtil.volumeUpdateRename(volumeName, "", StorPoolStorageAdaptor.getVolumeNameFromPath(detail.getValue(), false), conn);
|
||||
|
||||
if (updateVolumeResponse.getError() != null) {
|
||||
StorPoolUtil.spLog("StorpoolVMSnapshotStrategy.canHandle - Could not update StorPool's volume %s to it's globalId due to %s", volumeName, updateVolumeResponse.getError().getDescr());
|
||||
err = String.format("StorpoolVMSnapshotStrategy.canHandle - Could not update StorPool's volume %s to it's globalId due to %s", volumeName, updateVolumeResponse.getError().getDescr());
|
||||
} else {
|
||||
volumeDetailsDao.remove(detail.getId());
|
||||
}
|
||||
}
|
||||
|
||||
SpApiResponse resp = StorPoolUtil.detachAllForced(volumeName, false, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Could not detach StorPool volume %s from a group snapshot, due to %s",
|
||||
volumeName, resp.getError());
|
||||
throw new CloudRuntimeException(err);
|
||||
}
|
||||
resp = StorPoolUtil.volumeRevert(volumeName, snapshotName, conn);
|
||||
if (resp.getError() != null) {
|
||||
err = String.format("Create Could not complete revert task for volumeName=%s , and snapshotName=%s",
|
||||
volumeName, snapshotName);
|
||||
throw new CloudRuntimeException(err);
|
||||
}
|
||||
VolumeInfo vinfo = volFactory.getVolume(volumeObjectTO.getId());
|
||||
if (vinfo.getMaxIops() != null) {
|
||||
resp = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
|
||||
|
||||
if (resp.getError() != null) {
|
||||
StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s",
|
||||
resp.getError().getDescr());
|
||||
}
|
||||
}
|
||||
}
|
||||
finalizeRevert(vmSnapshotVO, volumeTOs);
|
||||
result = vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded);
|
||||
} catch (CloudRuntimeException | NoTransitionException e) {
|
||||
String errMsg = String.format("Error while finalize create vm snapshot [%s] due to %s", vmSnapshot.getName(), e.getMessage());
|
||||
log.error(errMsg, e);
|
||||
throw new CloudRuntimeException(errMsg);
|
||||
} finally {
|
||||
if (!result) {
|
||||
try {
|
||||
vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed);
|
||||
} catch (NoTransitionException e1) {
|
||||
log.error("Cannot set vm snapshot state due to: " + e1.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void publishUsageEvents(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeObjectTO volumeTo) {
|
||||
VolumeVO volume = volumeDao.findById(volumeTo.getId());
|
||||
Long diskOfferingId = volume.getDiskOfferingId();
|
||||
Long offeringId = null;
|
||||
if (diskOfferingId != null) {
|
||||
DiskOfferingVO offering = diskOfferingDao.findById(diskOfferingId);
|
||||
if (offering != null && offering.isComputeOnly()) {
|
||||
offeringId = offering.getId();
|
||||
}
|
||||
}
|
||||
UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
|
||||
vmSnapshot.getName(), offeringId, volume.getId(), volumeTo.getSize(), VMSnapshot.class.getName(), vmSnapshot.getUuid());
|
||||
}
|
||||
|
||||
private void publishUsageEvents(String type, VMSnapshot vmSnapshot, UserVm userVm, Long vmSnapSize, Long virtualSize) {
|
||||
try {
|
||||
UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
|
||||
vmSnapshot.getName(), 0L, 0L, vmSnapSize, virtualSize, VMSnapshot.class.getName(),
|
||||
vmSnapshot.getUuid());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to publis usage event " + type, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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-storpool
|
||||
parent=storage
|
||||
@ -0,0 +1,38 @@
|
||||
<!-- 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="storpoolPrimaryDataStoreProvider"
|
||||
class="org.apache.cloudstack.storage.datastore.provider.StorPoolPrimaryDataStoreProvider" />
|
||||
|
||||
<bean id="storpoolSnapshotStrategy"
|
||||
class="org.apache.cloudstack.storage.snapshot.StorPoolSnapshotStrategy" />
|
||||
|
||||
<bean id="storpoolVMSnapshotStrategy"
|
||||
class="org.apache.cloudstack.storage.snapshot.StorPoolVMSnapshotStrategy" />
|
||||
|
||||
<bean id="storpoolConfigManager"
|
||||
class="org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager" />
|
||||
|
||||
<bean id="storpoolDataMotionStrategy"
|
||||
class="org.apache.cloudstack.storage.motion.StorPoolDataMotionStrategy" />
|
||||
|
||||
<bean id="cleanupTags"
|
||||
class="org.apache.cloudstack.storage.collector.StorPoolAbandonObjectsCollector" />
|
||||
</beans>
|
||||
@ -57,10 +57,12 @@ import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationSer
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||
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.DataStoreDriver;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
|
||||
@ -2639,6 +2641,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
if (volume.getPoolId() != null) {
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary);
|
||||
volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore);
|
||||
provideVMInfo(dataStore, vmId, volumeId);
|
||||
}
|
||||
if (volumePool != null && hostId != null) {
|
||||
handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName());
|
||||
@ -3884,6 +3887,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
if (attached) {
|
||||
ev = Volume.Event.OperationSucceeded;
|
||||
s_logger.debug("Volume: " + volInfo.getName() + " successfully attached to VM: " + volInfo.getAttachedVmName());
|
||||
provideVMInfo(dataStore, vm.getId(), volInfo.getId());
|
||||
} else {
|
||||
s_logger.debug("Volume: " + volInfo.getName() + " failed to attach to VM: " + volInfo.getAttachedVmName());
|
||||
}
|
||||
@ -3892,6 +3896,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
return _volsDao.findById(volumeToAttach.getId());
|
||||
}
|
||||
|
||||
private void provideVMInfo(DataStore dataStore, long vmId, Long volumeId) {
|
||||
DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
|
||||
|
||||
if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
|
||||
PrimaryDataStoreDriver storageDriver = (PrimaryDataStoreDriver)dataStoreDriver;
|
||||
if (storageDriver.isVmInfoNeeded()) {
|
||||
storageDriver.provideVmInfo(vmId, volumeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxDataVolumesSupported(UserVmVO vm) {
|
||||
Long hostId = vm.getHostId();
|
||||
if (hostId == null) {
|
||||
|
||||
@ -25,8 +25,10 @@ import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import com.cloud.server.ResourceManagerUtil;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
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.PrimaryDataStoreDriver;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
@ -40,11 +42,14 @@ import com.cloud.network.vpc.NetworkACLItemVO;
|
||||
import com.cloud.network.vpc.NetworkACLVO;
|
||||
import com.cloud.network.vpc.VpcVO;
|
||||
import com.cloud.projects.ProjectVO;
|
||||
import com.cloud.server.ResourceManagerUtil;
|
||||
import com.cloud.server.ResourceTag;
|
||||
import com.cloud.server.ResourceTag.ResourceObjectType;
|
||||
import com.cloud.server.TaggedResourceService;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.SnapshotPolicyVO;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.tags.dao.ResourceTagDao;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
@ -78,6 +83,10 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
|
||||
AccountDao _accountDao;
|
||||
@Inject
|
||||
ResourceManagerUtil resourceManagerUtil;
|
||||
@Inject
|
||||
VolumeDao volumeDao;
|
||||
@Inject
|
||||
DataStoreManager dataStoreMgr;
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
@ -196,6 +205,9 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
|
||||
throw new CloudRuntimeException(String.format("tag %s already on %s with id %s", resourceTag.getKey(), resourceType.toString(), resourceId),e);
|
||||
}
|
||||
resourceTags.add(resourceTag);
|
||||
if (ResourceObjectType.UserVm.equals(resourceType)) {
|
||||
informStoragePoolForVmTags(id, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,6 +287,9 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
|
||||
_resourceTagDao.remove(tagToRemove.getId());
|
||||
s_logger.debug("Removed the tag '" + tagToRemove + "' for resources (" +
|
||||
String.join(", ", resourceIds) + ")");
|
||||
if (ResourceObjectType.UserVm.equals(resourceType)) {
|
||||
informStoragePoolForVmTags(tagToRemove.getResourceId(), tagToRemove.getKey(), tagToRemove.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -292,4 +307,19 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
|
||||
List<? extends ResourceTag> listResourceTags = listByResourceTypeAndId(type, resourceId);
|
||||
return listResourceTags == null ? null : listResourceTags.stream().collect(Collectors.toMap(ResourceTag::getKey, ResourceTag::getValue));
|
||||
}
|
||||
|
||||
|
||||
private void informStoragePoolForVmTags(long vmId, String key, String value) {
|
||||
List<VolumeVO> volumeVos = volumeDao.findByInstance(vmId);
|
||||
for (VolumeVO volume : volumeVos) {
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary);
|
||||
if (dataStore == null || !(dataStore.getDriver() instanceof PrimaryDataStoreDriver)) {
|
||||
continue;
|
||||
}
|
||||
PrimaryDataStoreDriver dataStoreDriver = (PrimaryDataStoreDriver) dataStore.getDriver();
|
||||
if (dataStoreDriver.isVmTagsNeeded(key)) {
|
||||
dataStoreDriver.provideVmTags(vmId, volume.getId(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
439
test/integration/plugins/storpool/MigrateVolumeToStorPool.py
Normal file
439
test/integration/plugins/storpool/MigrateVolumeToStorPool.py
Normal file
@ -0,0 +1,439 @@
|
||||
# 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.
|
||||
|
||||
# Import Local Modules
|
||||
import pprint
|
||||
import random
|
||||
import subprocess
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from marvin.cloudstackAPI import (listOsTypes,
|
||||
listTemplates,
|
||||
listHosts,
|
||||
createTemplate,
|
||||
createVolume,
|
||||
resizeVolume,
|
||||
startVirtualMachine,
|
||||
migrateVirtualMachine,
|
||||
migrateVolume
|
||||
)
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING
|
||||
from marvin.configGenerator import configuration, cluster
|
||||
from marvin.lib.base import (Account,
|
||||
Configurations,
|
||||
ServiceOffering,
|
||||
Snapshot,
|
||||
StoragePool,
|
||||
Template,
|
||||
Tag,
|
||||
VirtualMachine,
|
||||
VmSnapshot,
|
||||
Volume,
|
||||
SecurityGroup,
|
||||
)
|
||||
from marvin.lib.common import (get_zone,
|
||||
get_domain,
|
||||
get_template,
|
||||
list_disk_offering,
|
||||
list_snapshots,
|
||||
list_storage_pools,
|
||||
list_volumes,
|
||||
list_virtual_machines,
|
||||
list_configurations,
|
||||
list_service_offering,
|
||||
list_clusters,
|
||||
list_zones)
|
||||
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from storpool import spapi
|
||||
from sp_util import (TestData, StorPoolHelper)
|
||||
|
||||
|
||||
class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMigrateVolumeToAnotherPool, cls).setUpClass()
|
||||
try:
|
||||
cls.setUpCloudStack()
|
||||
except Exception:
|
||||
cls.cleanUpCloudStack()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def setUpCloudStack(cls):
|
||||
cls.spapi = spapi.Api(host="10.2.23.248", port="81", auth="6549874687", multiCluster=True)
|
||||
testClient = super(TestMigrateVolumeToAnotherPool, cls).getClsTestClient()
|
||||
cls.apiclient = testClient.getApiClient()
|
||||
|
||||
cls._cleanup = []
|
||||
|
||||
cls.unsupportedHypervisor = False
|
||||
cls.hypervisor = testClient.getHypervisorInfo()
|
||||
if cls.hypervisor.lower() in ("hyperv", "lxc"):
|
||||
cls.unsupportedHypervisor = True
|
||||
return
|
||||
|
||||
cls.services = testClient.getParsedTestDataConfig()
|
||||
# Get Zone, Domain and templates
|
||||
cls.domain = get_domain(cls.apiclient)
|
||||
cls.zone = None
|
||||
zones = list_zones(cls.apiclient)
|
||||
|
||||
for z in zones:
|
||||
if z.name == cls.getClsConfig().mgtSvr[0].zone:
|
||||
cls.zone = z
|
||||
|
||||
assert cls.zone is not None
|
||||
|
||||
td = TestData()
|
||||
cls.testdata = td.testdata
|
||||
cls.helper = StorPoolHelper()
|
||||
storpool_primary_storage = cls.testdata[TestData.primaryStorage]
|
||||
cls.template_name = storpool_primary_storage.get("name")
|
||||
storpool_service_offerings = cls.testdata[TestData.serviceOffering]
|
||||
|
||||
nfs_service_offerings = cls.testdata[TestData.serviceOfferingsPrimary]
|
||||
ceph_service_offerings = cls.testdata[TestData.serviceOfferingsCeph]
|
||||
|
||||
|
||||
storage_pool = list_storage_pools(
|
||||
cls.apiclient,
|
||||
name=cls.template_name
|
||||
)
|
||||
|
||||
nfs_storage_pool = list_storage_pools(
|
||||
cls.apiclient,
|
||||
name='nfs'
|
||||
)
|
||||
|
||||
ceph_primary_storage = cls.testdata[TestData.primaryStorage4]
|
||||
|
||||
cls.ceph_storage_pool = list_storage_pools(
|
||||
cls.apiclient,
|
||||
name=ceph_primary_storage.get("name")
|
||||
)[0]
|
||||
|
||||
service_offerings = list_service_offering(
|
||||
cls.apiclient,
|
||||
name=cls.template_name
|
||||
)
|
||||
nfs_service_offering = list_service_offering(
|
||||
cls.apiclient,
|
||||
name='nfs'
|
||||
)
|
||||
|
||||
ceph_service_offering = list_service_offering(
|
||||
cls.apiclient,
|
||||
name=ceph_primary_storage.get("name")
|
||||
)
|
||||
|
||||
disk_offerings = list_disk_offering(
|
||||
cls.apiclient,
|
||||
name="ssd"
|
||||
)
|
||||
|
||||
cls.disk_offerings = disk_offerings[0]
|
||||
if storage_pool is None:
|
||||
storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage)
|
||||
else:
|
||||
storage_pool = storage_pool[0]
|
||||
cls.storage_pool = storage_pool
|
||||
if service_offerings is None:
|
||||
service_offerings = ServiceOffering.create(cls.apiclient, storpool_service_offerings)
|
||||
else:
|
||||
service_offerings = service_offerings[0]
|
||||
if nfs_service_offering is None:
|
||||
nfs_service_offering = ServiceOffering.create(cls.apiclient, nfs_service_offerings)
|
||||
else:
|
||||
nfs_service_offering = nfs_service_offering[0]
|
||||
|
||||
if ceph_service_offering is None:
|
||||
ceph_service_offering = ServiceOffering.create(cls.apiclient, ceph_service_offerings)
|
||||
else:
|
||||
ceph_service_offering = ceph_service_offering[0]
|
||||
#The version of CentOS has to be supported
|
||||
template = get_template(
|
||||
cls.apiclient,
|
||||
cls.zone.id,
|
||||
account = "system"
|
||||
)
|
||||
|
||||
cls.nfs_storage_pool = nfs_storage_pool[0]
|
||||
if cls.nfs_storage_pool.state == "Maintenance":
|
||||
cls.nfs_storage_pool = StoragePool.cancelMaintenance(cls.apiclient, cls.nfs_storage_pool.id)
|
||||
|
||||
if cls.ceph_storage_pool.state == "Maintenance":
|
||||
cls.ceph_storage_pool = StoragePool.cancelMaintenance(cls.apiclient, cls.ceph_storage_pool.id)
|
||||
|
||||
cls.account = cls.helper.create_account(
|
||||
cls.apiclient,
|
||||
cls.services["account"],
|
||||
accounttype = 1,
|
||||
domainid=cls.domain.id,
|
||||
roleid = 1
|
||||
)
|
||||
cls._cleanup.append(cls.account)
|
||||
|
||||
securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0]
|
||||
cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id)
|
||||
|
||||
cls.vm = VirtualMachine.create(cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=nfs_service_offering.id,
|
||||
hypervisor=cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.vm2 = VirtualMachine.create(cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=nfs_service_offering.id,
|
||||
hypervisor= cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.vm3 = VirtualMachine.create(cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=nfs_service_offering.id,
|
||||
hypervisor= cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.vm4 = VirtualMachine.create(cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=ceph_service_offering.id,
|
||||
hypervisor= cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.vm5 = VirtualMachine.create(cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=ceph_service_offering.id,
|
||||
hypervisor= cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.storage_pool = StoragePool.update(cls.apiclient,
|
||||
id=cls.storage_pool.id,
|
||||
tags = ["ssd, nfs"])
|
||||
|
||||
|
||||
if template == FAILED:
|
||||
assert False, "get_template() failed to return template\
|
||||
with description %s" % cls.services["ostype"]
|
||||
|
||||
cls.services["domainid"] = cls.domain.id
|
||||
cls.services["small"]["zoneid"] = cls.zone.id
|
||||
cls.services["templates"]["ostypeid"] = template.ostypeid
|
||||
cls.services["zoneid"] = cls.zone.id
|
||||
|
||||
|
||||
cls.service_offering = service_offerings
|
||||
cls.nfs_service_offering = nfs_service_offering
|
||||
|
||||
cls.template = template
|
||||
cls.random_data_0 = random_gen(size=100)
|
||||
cls.test_dir = "/tmp"
|
||||
cls.random_data = "random.data"
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.cleanUpCloudStack()
|
||||
|
||||
@classmethod
|
||||
def cleanUpCloudStack(cls):
|
||||
try:
|
||||
if cls.nfs_storage_pool.state is not "Maintenance":
|
||||
cls.nfs_storage_pool = StoragePool.enableMaintenance(cls.apiclient, cls.nfs_storage_pool.id)
|
||||
|
||||
if cls.ceph_storage_pool.state is not "Maintenance":
|
||||
cls.ceph_storage_pool = StoragePool.enableMaintenance(cls.apiclient, cls.ceph_storage_pool.id)
|
||||
|
||||
cls.storage_pool = StoragePool.update(cls.apiclient,
|
||||
id=cls.storage_pool.id,
|
||||
tags = ["ssd"])
|
||||
# Cleanup resources used
|
||||
cleanup_resources(cls.apiclient, cls._cleanup)
|
||||
except Exception as e:
|
||||
raise Exception("Warning: Exception during cleanup : %s" % e)
|
||||
return
|
||||
|
||||
def setUp(self):
|
||||
self.apiclient = self.testClient.getApiClient()
|
||||
self.dbclient = self.testClient.getDbConnection()
|
||||
|
||||
if self.unsupportedHypervisor:
|
||||
self.skipTest("Skipping test because unsupported hypervisor\
|
||||
%s" % self.hypervisor)
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_1_migrate_vm_from_nfs_to_storpool(self):
|
||||
''' Test migrate virtual machine from NFS primary storage to StorPool'''
|
||||
|
||||
self.vm.stop(self.apiclient, forced=True)
|
||||
cmd = migrateVirtualMachine.migrateVirtualMachineCmd()
|
||||
cmd.virtualmachineid = self.vm.id
|
||||
cmd.storageid = self.storage_pool.id
|
||||
migrated_vm = self.apiclient.migrateVirtualMachine(cmd)
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = migrated_vm.id,
|
||||
listall=True
|
||||
)
|
||||
for v in volumes:
|
||||
name = v.path.split("/")[3]
|
||||
try:
|
||||
sp_volume = self.spapi.volumeList(volumeName="~" + name)
|
||||
except spapi.ApiError as err:
|
||||
raise Exception(err)
|
||||
|
||||
self.assertEqual(v.storageid, self.storage_pool.id, "Did not migrate virtual machine from NFS to StorPool")
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_2_migrate_volume_from_nfs_to_storpool(self):
|
||||
''' Test migrate volume from NFS primary storage to StorPool'''
|
||||
|
||||
self.vm2.stop(self.apiclient, forced=True)
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.vm2.id,
|
||||
listall=True
|
||||
)
|
||||
for v in volumes:
|
||||
cmd = migrateVolume.migrateVolumeCmd()
|
||||
cmd.storageid = self.storage_pool.id
|
||||
cmd.volumeid = v.id
|
||||
volume = self.apiclient.migrateVolume(cmd)
|
||||
self.assertEqual(volume.storageid, self.storage_pool.id, "Did not migrate volume from NFS to StorPool")
|
||||
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.vm2.id,
|
||||
listall=True
|
||||
)
|
||||
for v in volumes:
|
||||
name = v.path.split("/")[3]
|
||||
try:
|
||||
sp_volume = self.spapi.volumeList(volumeName="~" + name)
|
||||
except spapi.ApiError as err:
|
||||
raise Exception(err)
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_3_migrate_volume_from_nfs_to_storpool(self):
|
||||
'''Test write on disk before migrating volume from NFS primary storage
|
||||
Check that data is on disk after migration'''
|
||||
|
||||
try:
|
||||
# Login to VM and write data to file system
|
||||
ssh_client = self.vm3.get_ssh_client(reconnect = True)
|
||||
|
||||
cmds = [
|
||||
"echo %s > %s/%s" %
|
||||
(self.random_data_0, self.test_dir, self.random_data),
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"cat %s/%s" %
|
||||
(self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.vm3.ipaddress)
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data has be write into temp file!"
|
||||
)
|
||||
|
||||
self.vm3.stop(self.apiclient, forced=True)
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.vm3.id,
|
||||
listall=True
|
||||
)
|
||||
time.sleep(30)
|
||||
for v in volumes:
|
||||
cmd = migrateVolume.migrateVolumeCmd()
|
||||
cmd.storageid = self.storage_pool.id
|
||||
cmd.volumeid = v.id
|
||||
volume = self.apiclient.migrateVolume(cmd)
|
||||
self.assertEqual(volume.storageid, self.storage_pool.id, "Did not migrate volume from NFS to StorPool")
|
||||
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.vm3.id,
|
||||
listall=True
|
||||
)
|
||||
for v in volumes:
|
||||
name = v.path.split("/")[3]
|
||||
try:
|
||||
sp_volume = self.spapi.volumeList(volumeName="~" + name)
|
||||
except spapi.ApiError as err:
|
||||
raise Exception(err)
|
||||
|
||||
self.vm3.start(self.apiclient)
|
||||
try:
|
||||
ssh_client = self.vm3.get_ssh_client(reconnect=True)
|
||||
|
||||
cmds = [
|
||||
"cat %s/%s" % (self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.vm3.ipaddress)
|
||||
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data is equal with the ramdom file!"
|
||||
)
|
||||
2153
test/integration/plugins/storpool/TestStorPoolVolumes.py
Normal file
2153
test/integration/plugins/storpool/TestStorPoolVolumes.py
Normal file
File diff suppressed because it is too large
Load Diff
576
test/integration/plugins/storpool/TestTagsOnStorPool.py
Normal file
576
test/integration/plugins/storpool/TestTagsOnStorPool.py
Normal file
@ -0,0 +1,576 @@
|
||||
# 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.
|
||||
|
||||
# Import Local Modules
|
||||
import pprint
|
||||
import random
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
from marvin.cloudstackAPI import (listOsTypes,
|
||||
listTemplates,
|
||||
listHosts,
|
||||
createTemplate,
|
||||
createVolume,
|
||||
resizeVolume,
|
||||
revertSnapshot,
|
||||
startVirtualMachine)
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING
|
||||
from marvin.configGenerator import configuration, cluster
|
||||
from marvin.lib.base import (Account,
|
||||
Configurations,
|
||||
ServiceOffering,
|
||||
Snapshot,
|
||||
StoragePool,
|
||||
Template,
|
||||
Tag,
|
||||
VirtualMachine,
|
||||
VmSnapshot,
|
||||
Volume,
|
||||
SecurityGroup,
|
||||
)
|
||||
from marvin.lib.common import (get_zone,
|
||||
get_domain,
|
||||
get_template,
|
||||
list_disk_offering,
|
||||
list_snapshots,
|
||||
list_storage_pools,
|
||||
list_volumes,
|
||||
list_virtual_machines,
|
||||
list_configurations,
|
||||
list_service_offering,
|
||||
list_clusters,
|
||||
list_zones)
|
||||
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from storpool import spapi
|
||||
import uuid
|
||||
from sp_util import (TestData, StorPoolHelper)
|
||||
|
||||
class TestStoragePool(cloudstackTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStoragePool, cls).setUpClass()
|
||||
try:
|
||||
cls.setUpCloudStack()
|
||||
except Exception:
|
||||
cls.cleanUpCloudStack()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def setUpCloudStack(cls):
|
||||
cls.spapi = spapi.Api(host="10.2.23.248", port="81", auth="6549874687", multiCluster=True)
|
||||
testClient = super(TestStoragePool, cls).getClsTestClient()
|
||||
cls.apiclient = testClient.getApiClient()
|
||||
cls.unsupportedHypervisor = False
|
||||
cls.hypervisor = testClient.getHypervisorInfo()
|
||||
if cls.hypervisor.lower() in ("hyperv", "lxc"):
|
||||
cls.unsupportedHypervisor = True
|
||||
return
|
||||
|
||||
cls._cleanup = []
|
||||
|
||||
cls.services = testClient.getParsedTestDataConfig()
|
||||
# Get Zone, Domain and templates
|
||||
cls.domain = get_domain(cls.apiclient)
|
||||
cls.zone = None
|
||||
zones = list_zones(cls.apiclient)
|
||||
|
||||
for z in zones:
|
||||
if z.name == cls.getClsConfig().mgtSvr[0].zone:
|
||||
cls.zone = z
|
||||
|
||||
assert cls.zone is not None
|
||||
|
||||
td = TestData()
|
||||
cls.testdata = td.testdata
|
||||
cls.helper = StorPoolHelper()
|
||||
|
||||
cls.account = cls.helper.create_account(
|
||||
cls.apiclient,
|
||||
cls.services["account"],
|
||||
accounttype = 1,
|
||||
domainid=cls.domain.id,
|
||||
roleid = 1
|
||||
)
|
||||
cls._cleanup.append(cls.account)
|
||||
|
||||
securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0]
|
||||
cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id)
|
||||
|
||||
storpool_primary_storage = cls.testdata[TestData.primaryStorage]
|
||||
|
||||
storpool_service_offerings = cls.testdata[TestData.serviceOffering]
|
||||
|
||||
cls.template_name = storpool_primary_storage.get("name")
|
||||
|
||||
storage_pool = list_storage_pools(
|
||||
cls.apiclient,
|
||||
name=cls.template_name
|
||||
)
|
||||
|
||||
service_offerings = list_service_offering(
|
||||
cls.apiclient,
|
||||
name=cls.template_name
|
||||
)
|
||||
|
||||
disk_offerings = list_disk_offering(
|
||||
cls.apiclient,
|
||||
name="ssd"
|
||||
)
|
||||
|
||||
cls.disk_offerings = disk_offerings[0]
|
||||
if storage_pool is None:
|
||||
storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage)
|
||||
else:
|
||||
storage_pool = storage_pool[0]
|
||||
cls.storage_pool = storage_pool
|
||||
cls.debug(pprint.pformat(storage_pool))
|
||||
if service_offerings is None:
|
||||
service_offerings = ServiceOffering.create(cls.apiclient, storpool_service_offerings)
|
||||
else:
|
||||
service_offerings = service_offerings[0]
|
||||
#The version of CentOS has to be supported
|
||||
template = get_template(
|
||||
cls.apiclient,
|
||||
cls.zone.id,
|
||||
account = "system"
|
||||
)
|
||||
|
||||
cls.debug(pprint.pformat(template))
|
||||
cls.debug(pprint.pformat(cls.hypervisor))
|
||||
|
||||
if template == FAILED:
|
||||
assert False, "get_template() failed to return template\
|
||||
with description %s" % cls.services["ostype"]
|
||||
|
||||
cls.services["domainid"] = cls.domain.id
|
||||
cls.services["small"]["zoneid"] = cls.zone.id
|
||||
cls.services["templates"]["ostypeid"] = template.ostypeid
|
||||
cls.services["zoneid"] = cls.zone.id
|
||||
cls.services["diskofferingid"] = cls.disk_offerings.id
|
||||
|
||||
cls.service_offering = service_offerings
|
||||
cls.debug(pprint.pformat(cls.service_offering))
|
||||
|
||||
cls.volume_1 = Volume.create(
|
||||
cls.apiclient,
|
||||
cls.services,
|
||||
account=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
size=5
|
||||
)
|
||||
cls.volume_2 = Volume.create(
|
||||
cls.apiclient,
|
||||
cls.services,
|
||||
account=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
size =5
|
||||
)
|
||||
cls.volume = Volume.create(
|
||||
cls.apiclient,
|
||||
cls.services,
|
||||
account=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
size=5
|
||||
)
|
||||
cls.virtual_machine = VirtualMachine.create(
|
||||
cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=cls.service_offering.id,
|
||||
hypervisor=cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.virtual_machine2= VirtualMachine.create(
|
||||
cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=cls.service_offering.id,
|
||||
hypervisor=cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
cls.template = template
|
||||
cls.random_data_0 = random_gen(size=100)
|
||||
cls.test_dir = "/tmp"
|
||||
cls.random_data = "random.data"
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.cleanUpCloudStack()
|
||||
|
||||
@classmethod
|
||||
def cleanUpCloudStack(cls):
|
||||
try:
|
||||
# Cleanup resources used
|
||||
cleanup_resources(cls.apiclient, cls._cleanup)
|
||||
except Exception as e:
|
||||
raise Exception("Warning: Exception during cleanup : %s" % e)
|
||||
return
|
||||
|
||||
def setUp(self):
|
||||
self.apiclient = self.testClient.getApiClient()
|
||||
self.dbclient = self.testClient.getDbConnection()
|
||||
|
||||
if self.unsupportedHypervisor:
|
||||
self.skipTest("Skipping test because unsupported hypervisor\
|
||||
%s" % self.hypervisor)
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_01_set_vcpolicy_tag_to_vm_with_attached_disks(self):
|
||||
''' Test set vc-policy tag to VM with one attached disk
|
||||
'''
|
||||
volume_attached = self.virtual_machine.attach_volume(
|
||||
self.apiclient,
|
||||
self.volume_1
|
||||
)
|
||||
tag = Tag.create(
|
||||
self.apiclient,
|
||||
resourceIds=self.virtual_machine.id,
|
||||
resourceType='UserVm',
|
||||
tags={'vc-policy': 'testing_vc-policy'}
|
||||
)
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine.id, listall=True
|
||||
)
|
||||
|
||||
self.vc_policy_tags(volumes, vm_tags, vm)
|
||||
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_02_set_vcpolicy_tag_to_attached_disk(self):
|
||||
""" Test set vc-policy tag to new disk attached to VM"""
|
||||
volume_attached = self.virtual_machine.attach_volume(
|
||||
self.apiclient,
|
||||
self.volume_2
|
||||
)
|
||||
volume = list_volumes(self.apiclient, id = volume_attached.id, listall=True)
|
||||
name = volume[0].path.split("/")[3]
|
||||
sp_volume = self.spapi.volumeList(volumeName="~" + name)
|
||||
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
for vm_tag in vm_tags:
|
||||
for sp_tag in sp_volume[0].tags:
|
||||
if sp_tag == vm_tag.key:
|
||||
self.assertEqual(sp_tag, vm_tag.key, "StorPool tag is not the same as the Virtual Machine tag")
|
||||
self.assertEqual(sp_volume[0].tags[sp_tag], vm_tag.value, "StorPool tag value is not the same as the Virtual Machine tag value")
|
||||
if sp_tag == 'cvm':
|
||||
self.assertEqual(sp_volume[0].tags[sp_tag], vm[0].id, "cvm tag is not the expected value")
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_03_create_vm_snapshot_vc_policy_tag(self):
|
||||
"""Test to create VM snapshots with VC policy tags
|
||||
"""
|
||||
volume_attached = self.virtual_machine.attach_volume(
|
||||
self.apiclient,
|
||||
self.volume
|
||||
)
|
||||
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine.id,
|
||||
listall=True)
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
|
||||
self.vc_policy_tags(volumes, vm_tags, vm)
|
||||
|
||||
|
||||
self.assertEqual(volume_attached.id, self.volume.id, "Is not the same volume ")
|
||||
try:
|
||||
# Login to VM and write data to file system
|
||||
ssh_client = self.virtual_machine.get_ssh_client()
|
||||
|
||||
cmds = [
|
||||
"echo %s > %s/%s" %
|
||||
(self.random_data_0, self.test_dir, self.random_data),
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"cat %s/%s" %
|
||||
(self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data has be write into temp file!"
|
||||
)
|
||||
|
||||
time.sleep(30)
|
||||
MemorySnapshot = False
|
||||
vm_snapshot = VmSnapshot.create(
|
||||
self.apiclient,
|
||||
self.virtual_machine.id,
|
||||
MemorySnapshot,
|
||||
"TestSnapshot",
|
||||
"Display Text"
|
||||
)
|
||||
self.assertEqual(
|
||||
vm_snapshot.state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_04_revert_vm_snapshots_vc_policy_tag(self):
|
||||
"""Test to revert VM snapshots with VC policy tag
|
||||
"""
|
||||
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client()
|
||||
|
||||
cmds = [
|
||||
"rm -rf %s/%s" % (self.test_dir, self.random_data),
|
||||
"ls %s/%s" % (self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
|
||||
if str(result[0]).index("No such file or directory") == -1:
|
||||
self.fail("Check the random data has be delete from temp file!")
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
list_snapshot_response[0].state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
self.virtual_machine.stop(self.apiclient, forced=True)
|
||||
|
||||
VmSnapshot.revertToSnapshot(
|
||||
self.apiclient,
|
||||
list_snapshot_response[0].id
|
||||
)
|
||||
|
||||
self.virtual_machine.start(self.apiclient)
|
||||
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
|
||||
cmds = [
|
||||
"cat %s/%s" % (self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine.id, listall=True
|
||||
)
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
|
||||
self.vc_policy_tags(volumes, vm_tags, vm)
|
||||
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data is equal with the ramdom file!"
|
||||
)
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_05_delete_vm_snapshots(self):
|
||||
"""Test to delete vm snapshots
|
||||
"""
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
VmSnapshot.deleteVMSnapshot(
|
||||
self.apiclient,
|
||||
list_snapshot_response[0].id)
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
#vmid=self.virtual_machine.id,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=False)
|
||||
self.debug('list_snapshot_response -------------------- %s' % list_snapshot_response)
|
||||
|
||||
self.assertIsNone(list_snapshot_response, "snapshot is already deleted")
|
||||
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_06_remove_vcpolicy_tag_when_disk_detached(self):
|
||||
""" Test remove vc-policy tag to disk detached from VM"""
|
||||
time.sleep(60)
|
||||
volume_detached = self.virtual_machine.detach_volume(
|
||||
self.apiclient,
|
||||
self.volume_2
|
||||
)
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine.id, listall=True
|
||||
)
|
||||
|
||||
self.vc_policy_tags( volumes, vm_tags, vm)
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_07_delete_vcpolicy_tag(self):
|
||||
""" Test delete vc-policy tag of VM"""
|
||||
Tag.delete(self.apiclient,
|
||||
resourceIds=self.virtual_machine.id,
|
||||
resourceType='UserVm',
|
||||
tags={'vc-policy': 'testing_vc-policy'})
|
||||
|
||||
volumes = list_volumes(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine.id, listall=True
|
||||
)
|
||||
for v in volumes:
|
||||
name = v.path.split("/")[3]
|
||||
spvolume = self.spapi.volumeList(volumeName="~" + name)
|
||||
tags = spvolume[0].tags
|
||||
for t in tags:
|
||||
self.assertFalse(t.lower() == 'vc-policy'.lower(), "There is VC Policy tag")
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_08_vcpolicy_tag_to_reverted_disk(self):
|
||||
tag = Tag.create(
|
||||
self.apiclient,
|
||||
resourceIds=self.virtual_machine2.id,
|
||||
resourceType='UserVm',
|
||||
tags={'vc-policy': 'testing_vc-policy'}
|
||||
)
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine2.id, listall=True)
|
||||
vm_tags = vm[0].tags
|
||||
|
||||
volume = Volume.list(
|
||||
self.apiclient,
|
||||
virtualmachineid = self.virtual_machine2.id, listall=True,
|
||||
type = "ROOT"
|
||||
)
|
||||
self.vc_policy_tags(volume, vm_tags, vm)
|
||||
|
||||
snapshot = Snapshot.create(
|
||||
self.apiclient,
|
||||
volume[0].id,
|
||||
account=self.account.name,
|
||||
domainid=self.account.domainid
|
||||
)
|
||||
|
||||
virtual_machine = self.virtual_machine2.stop(
|
||||
self.apiclient,
|
||||
forced=True
|
||||
)
|
||||
|
||||
cmd = revertSnapshot.revertSnapshotCmd()
|
||||
cmd.id = snapshot.id
|
||||
revertedn = self.apiclient.revertSnapshot(cmd)
|
||||
|
||||
vm = list_virtual_machines(self.apiclient,id = self.virtual_machine2.id)
|
||||
vm_tags = vm[0].tags
|
||||
|
||||
vol = list_volumes(self.apiclient, id = snapshot.volumeid, listall=True)
|
||||
self.vc_policy_tags(vol, vm_tags, vm)
|
||||
|
||||
|
||||
def vc_policy_tags(self, volumes, vm_tags, vm):
|
||||
flag = False
|
||||
for v in volumes:
|
||||
name = v.path.split("/")[3]
|
||||
spvolume = self.spapi.volumeList(volumeName="~" + name)
|
||||
tags = spvolume[0].tags
|
||||
for t in tags:
|
||||
for vm_tag in vm_tags:
|
||||
if t == vm_tag.key:
|
||||
flag = True
|
||||
self.assertEqual(tags[t], vm_tag.value, "Tags are not equal")
|
||||
if t == 'cvm':
|
||||
self.assertEqual(tags[t], vm[0].id, "CVM tag is not the same as vm UUID")
|
||||
#self.assertEqual(tag.tags., second, msg)
|
||||
self.assertTrue(flag, "There aren't volumes with vm tags")
|
||||
369
test/integration/plugins/storpool/TestVmSnapshots.py
Normal file
369
test/integration/plugins/storpool/TestVmSnapshots.py
Normal file
@ -0,0 +1,369 @@
|
||||
# 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.
|
||||
|
||||
# Import Local Modules
|
||||
import random
|
||||
import time
|
||||
|
||||
from marvin.cloudstackAPI import (listTemplates)
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING
|
||||
from marvin.lib.base import (Account,
|
||||
ServiceOffering,
|
||||
VirtualMachine,
|
||||
VmSnapshot,
|
||||
User,
|
||||
Volume,
|
||||
SecurityGroup,
|
||||
)
|
||||
from marvin.lib.common import (get_zone,
|
||||
get_domain,
|
||||
get_template,
|
||||
list_clusters,
|
||||
list_snapshots,
|
||||
list_virtual_machines,
|
||||
list_configurations,
|
||||
list_disk_offering,
|
||||
list_accounts,
|
||||
list_storage_pools,
|
||||
list_service_offering,
|
||||
list_zones
|
||||
)
|
||||
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual, get_hypervisor_type
|
||||
from nose.plugins.attrib import attr
|
||||
import uuid
|
||||
from sp_util import (TestData, StorPoolHelper)
|
||||
|
||||
class TestVmSnapshot(cloudstackTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestVmSnapshot, cls).setUpClass()
|
||||
try:
|
||||
cls.setUpCloudStack()
|
||||
except Exception:
|
||||
cls.cleanUpCloudStack()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def setUpCloudStack(cls):
|
||||
testClient = super(TestVmSnapshot, cls).getClsTestClient()
|
||||
cls.apiclient = testClient.getApiClient()
|
||||
cls._cleanup = []
|
||||
cls.unsupportedHypervisor = False
|
||||
|
||||
# Setup test data
|
||||
td = TestData()
|
||||
cls.testdata = td.testdata
|
||||
cls.helper = StorPoolHelper()
|
||||
|
||||
|
||||
cls.services = testClient.getParsedTestDataConfig()
|
||||
# Get Zone, Domain and templates
|
||||
cls.domain = get_domain(cls.apiclient)
|
||||
cls.zone = None
|
||||
zones = list_zones(cls.apiclient)
|
||||
|
||||
for z in zones:
|
||||
if z.name == cls.getClsConfig().mgtSvr[0].zone:
|
||||
cls.zone = z
|
||||
|
||||
assert cls.zone is not None
|
||||
|
||||
cls.cluster = list_clusters(cls.apiclient)[0]
|
||||
cls.hypervisor = get_hypervisor_type(cls.apiclient)
|
||||
|
||||
#The version of CentOS has to be supported
|
||||
template = get_template(
|
||||
cls.apiclient,
|
||||
cls.zone.id,
|
||||
account = "system"
|
||||
)
|
||||
|
||||
|
||||
if template == FAILED:
|
||||
assert False, "get_template() failed to return template\
|
||||
with description %s" % cls.services["ostype"]
|
||||
|
||||
cls.template = template
|
||||
|
||||
cls.account = cls.helper.create_account(
|
||||
cls.apiclient,
|
||||
cls.services["account"],
|
||||
accounttype = 1,
|
||||
domainid=cls.domain.id,
|
||||
roleid = 1
|
||||
)
|
||||
cls._cleanup.append(cls.account)
|
||||
|
||||
securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0]
|
||||
cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id)
|
||||
|
||||
primarystorage = cls.testdata[TestData.primaryStorage]
|
||||
|
||||
serviceOffering = cls.testdata[TestData.serviceOffering]
|
||||
storage_pool = list_storage_pools(
|
||||
cls.apiclient,
|
||||
name = primarystorage.get("name")
|
||||
)
|
||||
cls.primary_storage = storage_pool[0]
|
||||
|
||||
disk_offering = list_disk_offering(
|
||||
cls.apiclient,
|
||||
name="ssd"
|
||||
)
|
||||
|
||||
assert disk_offering is not None
|
||||
|
||||
|
||||
service_offering_only = list_service_offering(
|
||||
cls.apiclient,
|
||||
name="ssd"
|
||||
)
|
||||
if service_offering_only is not None:
|
||||
cls.service_offering_only = service_offering_only[0]
|
||||
else:
|
||||
cls.service_offering_only = ServiceOffering.create(
|
||||
cls.apiclient,
|
||||
serviceOffering)
|
||||
assert cls.service_offering_only is not None
|
||||
|
||||
cls.disk_offering = disk_offering[0]
|
||||
|
||||
# Create 1 data volume_1
|
||||
cls.volume = Volume.create(
|
||||
cls.apiclient,
|
||||
cls.testdata[TestData.volume_1],
|
||||
account=cls.account.name,
|
||||
domainid=cls.domain.id,
|
||||
zoneid=cls.zone.id,
|
||||
diskofferingid=cls.disk_offering.id,
|
||||
size=10
|
||||
)
|
||||
|
||||
cls.virtual_machine = VirtualMachine.create(
|
||||
cls.apiclient,
|
||||
{"name":"StorPool-%s" % uuid.uuid4() },
|
||||
zoneid=cls.zone.id,
|
||||
templateid=cls.template.id,
|
||||
accountid=cls.account.name,
|
||||
domainid=cls.account.domainid,
|
||||
serviceofferingid=cls.service_offering_only.id,
|
||||
hypervisor=cls.hypervisor,
|
||||
rootdisksize=10
|
||||
)
|
||||
|
||||
cls.random_data_0 = random_gen(size=100)
|
||||
cls.test_dir = "/tmp"
|
||||
cls.random_data = "random.data"
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.cleanUpCloudStack()
|
||||
|
||||
@classmethod
|
||||
def cleanUpCloudStack(cls):
|
||||
try:
|
||||
# Cleanup resources used
|
||||
cleanup_resources(cls.apiclient, cls._cleanup)
|
||||
except Exception as e:
|
||||
raise Exception("Warning: Exception during cleanup : %s" % e)
|
||||
return
|
||||
|
||||
def setUp(self):
|
||||
self.apiclient = self.testClient.getApiClient()
|
||||
self.dbclient = self.testClient.getDbConnection()
|
||||
|
||||
if self.unsupportedHypervisor:
|
||||
self.skipTest("Skipping test because unsupported hypervisor\
|
||||
%s" % self.hypervisor)
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_01_create_vm_snapshots(self):
|
||||
"""Test to create VM snapshots
|
||||
"""
|
||||
volume_attached = self.virtual_machine.attach_volume(
|
||||
self.apiclient,
|
||||
self.volume
|
||||
)
|
||||
|
||||
self.assertEqual(volume_attached.id, self.volume.id, "Is not the same volume ")
|
||||
try:
|
||||
# Login to VM and write data to file system
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
|
||||
cmds = [
|
||||
"echo %s > %s/%s" %
|
||||
(self.random_data_0, self.test_dir, self.random_data),
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"sync",
|
||||
"sleep 1",
|
||||
"cat %s/%s" %
|
||||
(self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data has be write into temp file!"
|
||||
)
|
||||
|
||||
time.sleep(30)
|
||||
MemorySnapshot = False
|
||||
vm_snapshot = VmSnapshot.create(
|
||||
self.apiclient,
|
||||
self.virtual_machine.id,
|
||||
MemorySnapshot,
|
||||
"TestSnapshot",
|
||||
"Display Text"
|
||||
)
|
||||
self.assertEqual(
|
||||
vm_snapshot.state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_02_revert_vm_snapshots(self):
|
||||
"""Test to revert VM snapshots
|
||||
"""
|
||||
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
|
||||
cmds = [
|
||||
"rm -rf %s/%s" % (self.test_dir, self.random_data),
|
||||
"ls %s/%s" % (self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
|
||||
if str(result[0]).index("No such file or directory") == -1:
|
||||
self.fail("Check the random data has be delete from temp file!")
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
list_snapshot_response[0].state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
self.virtual_machine.stop(self.apiclient, forced=True)
|
||||
|
||||
VmSnapshot.revertToSnapshot(
|
||||
self.apiclient,
|
||||
list_snapshot_response[0].id
|
||||
)
|
||||
|
||||
self.virtual_machine.start(self.apiclient)
|
||||
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
|
||||
cmds = [
|
||||
"cat %s/%s" % (self.test_dir, self.random_data)
|
||||
]
|
||||
|
||||
for c in cmds:
|
||||
self.debug(c)
|
||||
result = ssh_client.execute(c)
|
||||
self.debug(result)
|
||||
|
||||
except Exception:
|
||||
self.fail("SSH failed for Virtual machine: %s" %
|
||||
self.virtual_machine.ipaddress)
|
||||
|
||||
self.assertEqual(
|
||||
self.random_data_0,
|
||||
result[0],
|
||||
"Check the random data is equal with the ramdom file!"
|
||||
)
|
||||
|
||||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
|
||||
def test_03_delete_vm_snapshots(self):
|
||||
"""Test to delete vm snapshots
|
||||
"""
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
VmSnapshot.deleteVMSnapshot(
|
||||
self.apiclient,
|
||||
list_snapshot_response[0].id)
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiclient,
|
||||
#vmid=self.virtual_machine.id,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=False)
|
||||
self.debug('list_snapshot_response -------------------- %s' % list_snapshot_response)
|
||||
|
||||
self.assertIsNone(list_snapshot_response, "snapshot is already deleted")
|
||||
748
test/integration/plugins/storpool/sp_util.py
Normal file
748
test/integration/plugins/storpool/sp_util.py
Normal file
@ -0,0 +1,748 @@
|
||||
# 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.
|
||||
from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING
|
||||
from nose.plugins.attrib import attr
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual
|
||||
from marvin.lib.base import (Account,
|
||||
Cluster,
|
||||
Configurations,
|
||||
ServiceOffering,
|
||||
Snapshot,
|
||||
StoragePool,
|
||||
Template,
|
||||
VirtualMachine,
|
||||
VmSnapshot,
|
||||
Volume)
|
||||
from marvin.lib.common import (get_zone,
|
||||
get_domain,
|
||||
get_template,
|
||||
list_disk_offering,
|
||||
list_hosts,
|
||||
list_snapshots,
|
||||
list_storage_pools,
|
||||
list_volumes,
|
||||
list_virtual_machines,
|
||||
list_configurations,
|
||||
list_service_offering,
|
||||
list_clusters,
|
||||
list_zones)
|
||||
from marvin.cloudstackAPI import (listOsTypes,
|
||||
listTemplates,
|
||||
listHosts,
|
||||
createTemplate,
|
||||
createVolume,
|
||||
getVolumeSnapshotDetails,
|
||||
resizeVolume,
|
||||
authorizeSecurityGroupIngress,
|
||||
migrateVirtualMachineWithVolume,
|
||||
destroyVirtualMachine,
|
||||
deployVirtualMachine,
|
||||
createAccount,
|
||||
startVirtualMachine,
|
||||
)
|
||||
import time
|
||||
import pprint
|
||||
import random
|
||||
from marvin.configGenerator import configuration
|
||||
import uuid
|
||||
import logging
|
||||
import subprocess
|
||||
import json
|
||||
from storpool import spapi
|
||||
from storpool import sptypes
|
||||
|
||||
class TestData():
|
||||
account = "account"
|
||||
capacityBytes = "capacitybytes"
|
||||
capacityIops = "capacityiops"
|
||||
clusterId = "clusterId"
|
||||
diskName = "diskname"
|
||||
diskOffering = "diskoffering"
|
||||
diskOffering2 = "diskoffering2"
|
||||
cephDiskOffering = "cephDiskOffering"
|
||||
nfsDiskOffering = "nfsDiskOffering"
|
||||
domainId = "domainId"
|
||||
hypervisor = "hypervisor"
|
||||
login = "login"
|
||||
mvip = "mvip"
|
||||
password = "password"
|
||||
port = "port"
|
||||
primaryStorage = "primarystorage"
|
||||
primaryStorage2 = "primarystorage2"
|
||||
primaryStorage3 = "primarystorage3"
|
||||
primaryStorage4 = "primaryStorage4"
|
||||
provider = "provider"
|
||||
serviceOffering = "serviceOffering"
|
||||
serviceOfferingssd2 = "serviceOffering-ssd2"
|
||||
serviceOfferingsPrimary = "serviceOfferingsPrimary"
|
||||
serviceOfferingsIops = "serviceOfferingsIops"
|
||||
serviceOfferingsCeph = "serviceOfferingsCeph"
|
||||
scope = "scope"
|
||||
StorPool = "StorPool"
|
||||
storageTag = ["ssd", "ssd2"]
|
||||
tags = "tags"
|
||||
virtualMachine = "virtualmachine"
|
||||
virtualMachine2 = "virtualmachine2"
|
||||
volume_1 = "volume_1"
|
||||
volume_2 = "volume_2"
|
||||
volume_3 = "volume_3"
|
||||
volume_4 = "volume_4"
|
||||
volume_5 = "volume_5"
|
||||
volume_6 = "volume_6"
|
||||
volume_7 = "volume_7"
|
||||
zoneId = "zoneId"
|
||||
|
||||
def __init__(self):
|
||||
sp_template_1 = 'ssd'
|
||||
sp_template_2 = 'ssd2'
|
||||
sp_template_3 = 'test-primary'
|
||||
self.testdata = {
|
||||
TestData.primaryStorage: {
|
||||
"name": sp_template_1,
|
||||
TestData.scope: "ZONE",
|
||||
"url": sp_template_1,
|
||||
TestData.provider: "StorPool",
|
||||
"path": "/dev/storpool",
|
||||
TestData.capacityBytes: 2251799813685248,
|
||||
TestData.hypervisor: "KVM"
|
||||
},
|
||||
TestData.primaryStorage2: {
|
||||
"name": sp_template_2,
|
||||
TestData.scope: "ZONE",
|
||||
"url": sp_template_2,
|
||||
TestData.provider: "StorPool",
|
||||
"path": "/dev/storpool",
|
||||
TestData.capacityBytes: 2251799813685248,
|
||||
TestData.hypervisor: "KVM"
|
||||
},
|
||||
TestData.primaryStorage3: {
|
||||
"name": sp_template_3,
|
||||
TestData.scope: "ZONE",
|
||||
"url": sp_template_3,
|
||||
TestData.provider: "StorPool",
|
||||
"path": "/dev/storpool",
|
||||
TestData.capacityBytes: 2251799813685248,
|
||||
TestData.hypervisor: "KVM"
|
||||
},
|
||||
TestData.primaryStorage4: {
|
||||
"name": "ceph",
|
||||
TestData.scope: "ZONE",
|
||||
TestData.provider: "RBD",
|
||||
TestData.hypervisor: "KVM"
|
||||
},
|
||||
TestData.virtualMachine: {
|
||||
"name": "TestVM",
|
||||
"displayname": "TestVM",
|
||||
"privateport": 22,
|
||||
"publicport": 22,
|
||||
"protocol": "tcp"
|
||||
},
|
||||
TestData.virtualMachine2: {
|
||||
"name": "TestVM2",
|
||||
"displayname": "TestVM2",
|
||||
"privateport": 22,
|
||||
"publicport": 22,
|
||||
"protocol": "tcp"
|
||||
},
|
||||
TestData.serviceOffering:{
|
||||
"name": sp_template_1,
|
||||
"displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 500,
|
||||
"memory": 512,
|
||||
"storagetype": "shared",
|
||||
"customizediops": False,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
"tags": sp_template_1
|
||||
},
|
||||
TestData.serviceOfferingssd2:{
|
||||
"name": sp_template_2,
|
||||
"displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 500,
|
||||
"memory": 512,
|
||||
"storagetype": "shared",
|
||||
"customizediops": False,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
"tags": sp_template_2
|
||||
},
|
||||
TestData.serviceOfferingsPrimary:{
|
||||
"name": "nfs",
|
||||
"displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 500,
|
||||
"memory": 512,
|
||||
"storagetype": "shared",
|
||||
"customizediops": False,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
"tags": "nfs"
|
||||
},
|
||||
TestData.serviceOfferingsCeph:{
|
||||
"name": "ceph",
|
||||
"displaytext": "Ceph Service offerings",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 500,
|
||||
"memory": 512,
|
||||
"storagetype": "shared",
|
||||
"customizediops": False,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
"tags": "ceph"
|
||||
},
|
||||
TestData.serviceOfferingsIops:{
|
||||
"name": "iops",
|
||||
"displaytext": "Testing IOPS on StorPool",
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 500,
|
||||
"memory": 512,
|
||||
"storagetype": "shared",
|
||||
"customizediops": True,
|
||||
"tags": sp_template_1,
|
||||
},
|
||||
TestData.diskOffering: {
|
||||
"name": "SP_DO_1",
|
||||
"displaytext": "SP_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)",
|
||||
"disksize": 5,
|
||||
"customizediops": False,
|
||||
"miniops": 300,
|
||||
"maxiops": 500,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
TestData.tags: sp_template_1,
|
||||
"storagetype": "shared"
|
||||
},
|
||||
TestData.diskOffering2: {
|
||||
"name": "SP_DO_1",
|
||||
"displaytext": "SP_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)",
|
||||
"disksize": 5,
|
||||
"customizediops": False,
|
||||
"miniops": 300,
|
||||
"maxiops": 500,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
TestData.tags: sp_template_2,
|
||||
"storagetype": "shared"
|
||||
},
|
||||
TestData.cephDiskOffering: {
|
||||
"name": "ceph",
|
||||
"displaytext": "Ceph fixed disk offering",
|
||||
"disksize": 5,
|
||||
"customizediops": False,
|
||||
"miniops": 300,
|
||||
"maxiops": 500,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
TestData.tags: "ceph",
|
||||
"storagetype": "shared"
|
||||
},
|
||||
TestData.nfsDiskOffering: {
|
||||
"name": "nfs",
|
||||
"displaytext": "NFS fixed disk offering",
|
||||
"disksize": 5,
|
||||
"customizediops": False,
|
||||
"miniops": 300,
|
||||
"maxiops": 500,
|
||||
"hypervisorsnapshotreserve": 200,
|
||||
TestData.tags: "nfs",
|
||||
"storagetype": "shared"
|
||||
},
|
||||
TestData.volume_1: {
|
||||
TestData.diskName: "test-volume-1",
|
||||
},
|
||||
TestData.volume_2: {
|
||||
TestData.diskName: "test-volume-2",
|
||||
},
|
||||
TestData.volume_3: {
|
||||
TestData.diskName: "test-volume-3",
|
||||
},
|
||||
TestData.volume_4: {
|
||||
TestData.diskName: "test-volume-4",
|
||||
},
|
||||
TestData.volume_5: {
|
||||
TestData.diskName: "test-volume-5",
|
||||
},
|
||||
TestData.volume_6: {
|
||||
TestData.diskName: "test-volume-6",
|
||||
},
|
||||
TestData.volume_7: {
|
||||
TestData.diskName: "test-volume-7",
|
||||
},
|
||||
}
|
||||
class StorPoolHelper():
|
||||
|
||||
@classmethod
|
||||
def create_template_from_snapshot(self, apiclient, services, snapshotid=None, volumeid=None):
|
||||
"""Create template from Volume"""
|
||||
# Create template from Virtual machine and Volume ID
|
||||
cmd = createTemplate.createTemplateCmd()
|
||||
cmd.displaytext = "StorPool_Template"
|
||||
cmd.name = "-".join(["StorPool-", random_gen()])
|
||||
if "ostypeid" in services:
|
||||
cmd.ostypeid = services["ostypeid"]
|
||||
elif "ostype" in services:
|
||||
# Find OSTypeId from Os type
|
||||
sub_cmd = listOsTypes.listOsTypesCmd()
|
||||
sub_cmd.description = services["ostype"]
|
||||
ostypes = apiclient.listOsTypes(sub_cmd)
|
||||
|
||||
if not isinstance(ostypes, list):
|
||||
raise Exception(
|
||||
"Unable to find Ostype id with desc: %s" %
|
||||
services["ostype"])
|
||||
cmd.ostypeid = ostypes[0].id
|
||||
else:
|
||||
raise Exception(
|
||||
"Unable to find Ostype is required for creating template")
|
||||
|
||||
cmd.isfeatured = True
|
||||
cmd.ispublic = True
|
||||
cmd.isextractable = False
|
||||
|
||||
if snapshotid:
|
||||
cmd.snapshotid = snapshotid
|
||||
if volumeid:
|
||||
cmd.volumeid = volumeid
|
||||
return Template(apiclient.createTemplate(cmd).__dict__)
|
||||
|
||||
@classmethod
|
||||
def getCfgFromUrl(self, url):
|
||||
cfg = dict([
|
||||
option.split('=')
|
||||
for option in url.split(';')
|
||||
])
|
||||
host, port = cfg['SP_API_HTTP'].split(':')
|
||||
auth = cfg['SP_AUTH_TOKEN']
|
||||
return host, int(port), auth
|
||||
|
||||
@classmethod
|
||||
def get_remote_storpool_cluster(cls):
|
||||
logging.debug("######################## get_remote_storpool_cluster")
|
||||
|
||||
storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID']).strip()
|
||||
clusterid = storpool_clusterid.split("=")[1].split(".")[1]
|
||||
logging.debug("######################## %s" % storpool_clusterid)
|
||||
cmd = ["storpool", "-j", "cluster", "list"]
|
||||
proc = subprocess.Popen(cmd,stdout=subprocess.PIPE).stdout.read()
|
||||
csl = json.loads(proc)
|
||||
logging.debug("######################## %s" % csl)
|
||||
|
||||
clusters = csl.get("data").get("clusters")
|
||||
logging.debug("######################## %s" % clusters)
|
||||
|
||||
for c in clusters:
|
||||
c_id = c.get("id")
|
||||
if c_id != clusterid:
|
||||
return c.get("name")
|
||||
|
||||
@classmethod
|
||||
def get_local_cluster(cls, apiclient, zoneid):
|
||||
storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID'])
|
||||
clusterid = storpool_clusterid.split("=")
|
||||
logging.debug(storpool_clusterid)
|
||||
clusters = list_clusters(apiclient, zoneid = zoneid)
|
||||
for c in clusters:
|
||||
configuration = list_configurations(
|
||||
apiclient,
|
||||
clusterid = c.id
|
||||
)
|
||||
for conf in configuration:
|
||||
if conf.name == 'sp.cluster.id' and (conf.value in clusterid[1]):
|
||||
return c
|
||||
|
||||
@classmethod
|
||||
def get_remote_cluster(cls, apiclient, zoneid):
|
||||
storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID'])
|
||||
clusterid = storpool_clusterid.split("=")
|
||||
logging.debug(storpool_clusterid)
|
||||
clusters = list_clusters(apiclient, zoneid = zoneid)
|
||||
for c in clusters:
|
||||
configuration = list_configurations(
|
||||
apiclient,
|
||||
clusterid = c.id
|
||||
)
|
||||
for conf in configuration:
|
||||
if conf.name == 'sp.cluster.id' and (conf.value not in clusterid[1]):
|
||||
return c
|
||||
|
||||
@classmethod
|
||||
def get_snapshot_template_id(self, apiclient, snapshot, storage_pool_id):
|
||||
try:
|
||||
cmd = getVolumeSnapshotDetails.getVolumeSnapshotDetailsCmd()
|
||||
cmd.snapshotid = snapshot.id
|
||||
snapshot_details = apiclient.getVolumeSnapshotDetails(cmd)
|
||||
logging.debug("Snapshot details %s" % snapshot_details)
|
||||
logging.debug("Snapshot with uuid %s" % snapshot.id)
|
||||
for s in snapshot_details:
|
||||
if s["snapshotDetailsName"] == storage_pool_id:
|
||||
return s["snapshotDetailsValue"]
|
||||
except Exception as err:
|
||||
raise Exception(err)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def getDestinationHost(self, hostsToavoid, hosts):
|
||||
destinationHost = None
|
||||
for host in hosts:
|
||||
if host.id not in hostsToavoid:
|
||||
destinationHost = host
|
||||
break
|
||||
return destinationHost
|
||||
|
||||
|
||||
@classmethod
|
||||
def getDestinationPool(self,
|
||||
poolsToavoid,
|
||||
migrateto,
|
||||
pools
|
||||
):
|
||||
""" Get destination pool which has scope same as migrateto
|
||||
and which is not in avoid set
|
||||
"""
|
||||
|
||||
destinationPool = None
|
||||
|
||||
# Get Storage Pool Id to migrate to
|
||||
for storagePool in pools:
|
||||
if storagePool.scope == migrateto:
|
||||
if storagePool.name not in poolsToavoid:
|
||||
destinationPool = storagePool
|
||||
break
|
||||
|
||||
return destinationPool
|
||||
|
||||
@classmethod
|
||||
def get_destination_pools_hosts(self, apiclient, vm, hosts):
|
||||
vol_list = list_volumes(
|
||||
apiclient,
|
||||
virtualmachineid=vm.id,
|
||||
listall=True)
|
||||
# Get destination host
|
||||
destinationHost = self.getDestinationHost(vm.hostid, hosts)
|
||||
return destinationHost, vol_list
|
||||
|
||||
@classmethod
|
||||
def list_hosts_by_cluster_id(cls, apiclient, clusterid):
|
||||
"""List all Hosts matching criteria"""
|
||||
cmd = listHosts.listHostsCmd()
|
||||
cmd.clusterid = clusterid
|
||||
return(apiclient.listHosts(cmd))
|
||||
|
||||
@classmethod
|
||||
def set_securityGroups(cls, apiclient, account, domainid, id):
|
||||
cmd = authorizeSecurityGroupIngress.authorizeSecurityGroupIngressCmd()
|
||||
cmd.protocol = 'TCP'
|
||||
cmd.startport = 22
|
||||
cmd.endport = 22
|
||||
cmd.cidrlist = '0.0.0.0/0'
|
||||
cmd.securitygroupid = id
|
||||
cmd.account = account
|
||||
cmd.domainid = domainid
|
||||
|
||||
apiclient.authorizeSecurityGroupIngress(cmd)
|
||||
|
||||
cmd.protocol = 'ICMP'
|
||||
cmd.icmptype = "-1"
|
||||
cmd.icmpcode = "-1"
|
||||
# Authorize to only account not CIDR
|
||||
cmd.securitygroupid = id
|
||||
cmd.account = account
|
||||
cmd.domainid = domainid
|
||||
apiclient.authorizeSecurityGroupIngress(cmd)
|
||||
|
||||
@classmethod
|
||||
def migrateVm(self, apiclient, vm, destinationHost):
|
||||
"""
|
||||
This method is to migrate a VM using migrate virtual machine API
|
||||
"""
|
||||
|
||||
vm.migrate(
|
||||
apiclient,
|
||||
hostid=destinationHost.id,
|
||||
)
|
||||
vm.getState(
|
||||
apiclient,
|
||||
"Running"
|
||||
)
|
||||
# check for the VM's host and volume's storage post migration
|
||||
migrated_vm_response = list_virtual_machines(apiclient, id=vm.id)
|
||||
assert isinstance(migrated_vm_response, list), "Check list virtual machines response for valid list"
|
||||
|
||||
assert migrated_vm_response[0].hostid == destinationHost.id, "VM did not migrate to a specified host"
|
||||
return migrated_vm_response[0]
|
||||
|
||||
@classmethod
|
||||
def migrateVmWithVolumes(self, apiclient, vm, destinationHost, volumes, pool):
|
||||
"""
|
||||
This method is used to migrate a vm and its volumes using migrate virtual machine with volume API
|
||||
INPUTS:
|
||||
1. vm -> virtual machine object
|
||||
2. destinationHost -> the host to which VM will be migrated
|
||||
3. volumes -> list of volumes which are to be migrated
|
||||
4. pools -> list of destination pools
|
||||
"""
|
||||
vol_pool_map = {vol.id: pool.id for vol in volumes}
|
||||
|
||||
cmd = migrateVirtualMachineWithVolume.migrateVirtualMachineWithVolumeCmd()
|
||||
cmd.hostid = destinationHost.id
|
||||
cmd.migrateto = []
|
||||
cmd.virtualmachineid = self.virtual_machine.id
|
||||
for volume, pool1 in vol_pool_map.items():
|
||||
cmd.migrateto.append({
|
||||
'volume': volume,
|
||||
'pool': pool1
|
||||
})
|
||||
apiclient.migrateVirtualMachineWithVolume(cmd)
|
||||
|
||||
vm.getState(
|
||||
apiclient,
|
||||
"Running"
|
||||
)
|
||||
# check for the VM's host and volume's storage post migration
|
||||
migrated_vm_response = list_virtual_machines(apiclient, id=vm.id)
|
||||
assert isinstance(migrated_vm_response, list), "Check list virtual machines response for valid list"
|
||||
|
||||
assert migrated_vm_response[0].hostid == destinationHost.id, "VM did not migrate to a specified host"
|
||||
|
||||
for vol in volumes:
|
||||
migrated_volume_response = list_volumes(
|
||||
apiclient,
|
||||
virtualmachineid=migrated_vm_response[0].id,
|
||||
name=vol.name,
|
||||
listall=True)
|
||||
assert isinstance(migrated_volume_response, list), "Check list virtual machines response for valid list"
|
||||
assert migrated_volume_response[0].storageid == pool.id, "Volume did not migrate to a specified pool"
|
||||
|
||||
assert str(migrated_volume_response[0].state).lower().eq('ready'), "Check migrated volume is in Ready state"
|
||||
|
||||
return migrated_vm_response[0]
|
||||
|
||||
@classmethod
|
||||
def create_sp_template_and_storage_pool(self, apiclient, template_name, primary_storage, zoneid):
|
||||
spapiRemote = spapi.Api.fromConfig()
|
||||
logging.debug("================ %s" % spapiRemote)
|
||||
|
||||
sp_api = spapi.Api.fromConfig(multiCluster= True)
|
||||
logging.debug("================ %s" % sp_api)
|
||||
|
||||
remote_cluster = self.get_remote_storpool_cluster()
|
||||
logging.debug("================ %s" % remote_cluster)
|
||||
|
||||
newTemplate = sptypes.VolumeTemplateCreateDesc(name = template_name, placeAll = "ssd", placeTail = "ssd", placeHead = "ssd", replication=1)
|
||||
template_on_remote = spapiRemote.volumeTemplateCreate(newTemplate, clusterName = remote_cluster)
|
||||
template_on_local = spapiRemote.volumeTemplateCreate(newTemplate)
|
||||
storage_pool = StoragePool.create(apiclient, primary_storage, zoneid = zoneid,)
|
||||
return storage_pool, spapiRemote, sp_api
|
||||
|
||||
@classmethod
|
||||
def destroy_vm(self, apiclient, virtualmachineid):
|
||||
cmd = destroyVirtualMachine.destroyVirtualMachineCmd()
|
||||
cmd.id = virtualmachineid
|
||||
cmd.expunge = True
|
||||
apiclient.destroyVirtualMachine(cmd)
|
||||
|
||||
@classmethod
|
||||
def check_storpool_volume_size(cls, volume, spapi):
|
||||
name = volume.path.split("/")[3]
|
||||
try:
|
||||
spvolume = spapi.volumeList(volumeName = "~" + name)
|
||||
if spvolume[0].size != volume.size:
|
||||
raise Exception("Storpool volume size is not the same as CloudStack db size")
|
||||
except spapi.ApiError as err:
|
||||
raise Exception(err)
|
||||
|
||||
@classmethod
|
||||
def check_storpool_volume_iops(cls, spapi, volume,):
|
||||
name = volume.path.split("/")[3]
|
||||
try:
|
||||
spvolume = spapi.volumeList(volumeName = "~" + name)
|
||||
logging.debug(spvolume[0].iops)
|
||||
logging.debug(volume.maxiops)
|
||||
if spvolume[0].iops != volume.maxiops:
|
||||
raise Exception("Storpool volume size is not the same as CloudStack db size")
|
||||
except spapi.ApiError as err:
|
||||
raise Exception(err)
|
||||
|
||||
@classmethod
|
||||
def create_custom_disk(cls, apiclient, services, size = None, miniops = None, maxiops =None, diskofferingid=None, zoneid=None, account=None, domainid=None, snapshotid=None):
|
||||
"""Create Volume from Custom disk offering"""
|
||||
cmd = createVolume.createVolumeCmd()
|
||||
cmd.name = services["diskname"]
|
||||
|
||||
if diskofferingid:
|
||||
cmd.diskofferingid = diskofferingid
|
||||
|
||||
if size:
|
||||
cmd.size = size
|
||||
|
||||
if miniops:
|
||||
cmd.miniops = miniops
|
||||
|
||||
if maxiops:
|
||||
cmd.maxiops = maxiops
|
||||
|
||||
if account:
|
||||
cmd.account = account
|
||||
|
||||
if domainid:
|
||||
cmd.domainid = domainid
|
||||
|
||||
if snapshotid:
|
||||
cmd.snapshotid = snapshotid
|
||||
|
||||
cmd.zoneid = zoneid
|
||||
|
||||
return Volume(apiclient.createVolume(cmd).__dict__)
|
||||
|
||||
@classmethod
|
||||
def create_vm_custom(cls, apiclient, services, templateid=None, zoneid=None,
|
||||
serviceofferingid=None, method='GET', hypervisor=None,
|
||||
cpuNumber=None, cpuSpeed=None, memory=None, minIops=None,
|
||||
maxIops=None, hostid=None, rootdisksize=None, account=None, domainid=None
|
||||
):
|
||||
"""Create the instance"""
|
||||
|
||||
cmd = deployVirtualMachine.deployVirtualMachineCmd()
|
||||
|
||||
if serviceofferingid:
|
||||
cmd.serviceofferingid = serviceofferingid
|
||||
elif "serviceoffering" in services:
|
||||
cmd.serviceofferingid = services["serviceoffering"]
|
||||
|
||||
if zoneid:
|
||||
cmd.zoneid = zoneid
|
||||
elif "zoneid" in services:
|
||||
cmd.zoneid = services["zoneid"]
|
||||
|
||||
if hypervisor:
|
||||
cmd.hypervisor = hypervisor
|
||||
|
||||
if hostid:
|
||||
cmd.hostid = hostid
|
||||
|
||||
if "displayname" in services:
|
||||
cmd.displayname = services["displayname"]
|
||||
|
||||
if "name" in services:
|
||||
cmd.name = services["name"]
|
||||
|
||||
if templateid:
|
||||
cmd.templateid = templateid
|
||||
elif "template" in services:
|
||||
cmd.templateid = services["template"]
|
||||
|
||||
cmd.details = [{}]
|
||||
|
||||
if cpuNumber:
|
||||
cmd.details[0]["cpuNumber"] = cpuNumber
|
||||
|
||||
if cpuSpeed:
|
||||
cmd.details[0]["cpuSpeed"] = cpuSpeed
|
||||
|
||||
if memory:
|
||||
cmd.details[0]["memory"] = memory
|
||||
|
||||
if minIops:
|
||||
cmd.details[0]["minIops"] = minIops
|
||||
|
||||
if maxIops:
|
||||
cmd.details[0]["maxIops"] = maxIops
|
||||
|
||||
if rootdisksize >= 0:
|
||||
cmd.details[0]["rootdisksize"] = rootdisksize
|
||||
|
||||
if account:
|
||||
cmd.account = account
|
||||
|
||||
if domainid:
|
||||
cmd.domainid = domainid
|
||||
|
||||
virtual_machine = apiclient.deployVirtualMachine(cmd, method=method)
|
||||
|
||||
return VirtualMachine(virtual_machine.__dict__, services)
|
||||
|
||||
|
||||
@classmethod
|
||||
def resize_volume(cls, apiclient, volume, shrinkOk=None, disk_offering =None, size=None, maxiops=None, miniops=None):
|
||||
|
||||
cmd = resizeVolume.resizeVolumeCmd()
|
||||
cmd.id = volume.id
|
||||
|
||||
if disk_offering:
|
||||
cmd.diskofferingid = disk_offering.id
|
||||
if size:
|
||||
cmd.size = size
|
||||
|
||||
if maxiops:
|
||||
cmd.maxiops = maxiops
|
||||
if miniops:
|
||||
cmd.miniops
|
||||
|
||||
cmd.shrinkok = shrinkOk
|
||||
apiclient.resizeVolume(cmd)
|
||||
|
||||
new_size = Volume.list(
|
||||
apiclient,
|
||||
id=volume.id
|
||||
)
|
||||
volume_size = new_size[0].size
|
||||
return new_size[0]
|
||||
|
||||
@classmethod
|
||||
def create_account(cls, apiclient, services, accounttype=None, domainid=None, roleid=None):
|
||||
"""Creates an account"""
|
||||
cmd = createAccount.createAccountCmd()
|
||||
|
||||
# 0 - User, 1 - Root Admin, 2 - Domain Admin
|
||||
if accounttype:
|
||||
cmd.accounttype = accounttype
|
||||
else:
|
||||
cmd.accounttype = 1
|
||||
|
||||
cmd.email = services["email"]
|
||||
cmd.firstname = services["firstname"]
|
||||
cmd.lastname = services["lastname"]
|
||||
|
||||
cmd.password = services["password"]
|
||||
username = services["username"]
|
||||
# Limit account username to 99 chars to avoid failure
|
||||
# 6 chars start string + 85 chars apiclientid + 6 chars random string + 2 chars joining hyphen string = 99
|
||||
username = username[:6]
|
||||
apiclientid = apiclient.id[-85:] if len(apiclient.id) > 85 else apiclient.id
|
||||
cmd.username = "-".join([username,
|
||||
random_gen(id=apiclientid, size=6)])
|
||||
|
||||
if "accountUUID" in services:
|
||||
cmd.accountid = "-".join([services["accountUUID"], random_gen()])
|
||||
|
||||
if "userUUID" in services:
|
||||
cmd.userid = "-".join([services["userUUID"], random_gen()])
|
||||
|
||||
if domainid:
|
||||
cmd.domainid = domainid
|
||||
|
||||
if roleid:
|
||||
cmd.roleid = roleid
|
||||
|
||||
account = apiclient.createAccount(cmd)
|
||||
|
||||
return Account(account.__dict__)
|
||||
|
||||
def start(cls, apiclient, vmid, hostid):
|
||||
"""Start the instance"""
|
||||
cmd = startVirtualMachine.startVirtualMachineCmd()
|
||||
cmd.id = vmid
|
||||
cmd.hostid = hostid
|
||||
return (apiclient.startVirtualMachine(cmd))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user