diff --git a/client/pom.xml b/client/pom.xml
index 9394180848b..921a4adbf88 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -767,6 +767,12 @@
false
${project.build.directory}/lib
+
+ org.apache.cloudstack
+ cloud-plugin-storage-volume-linstor
+ false
+ ${project.build.directory}/lib
+
org.bouncycastle
bctls-jdk15on
@@ -811,6 +817,7 @@
org.bouncycastle:bctls-jdk15on
mysql:mysql-connector-java
org.apache.cloudstack:cloud-plugin-storage-volume-storpool
+ org.apache.cloudstack:cloud-plugin-storage-volume-linstor
diff --git a/debian/rules b/debian/rules
index 7537e07a6b6..287ec4256c2 100755
--- a/debian/rules
+++ b/debian/rules
@@ -42,6 +42,7 @@ override_dh_auto_install:
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 plugins/storage/volume/linstor/target/cloud-plugin-storage-volume-linstor-$(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
diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec
index a5271ff13bd..e30d6a6ae32 100644
--- a/packaging/centos7/cloud.spec
+++ b/packaging/centos7/cloud.spec
@@ -355,6 +355,7 @@ install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}
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
+cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
# Usage server
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
diff --git a/packaging/centos8/cloud.spec b/packaging/centos8/cloud.spec
index 3eb9db43cd5..19a1bf0c368 100644
--- a/packaging/centos8/cloud.spec
+++ b/packaging/centos8/cloud.spec
@@ -348,6 +348,7 @@ install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}
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
+cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
# Usage server
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
diff --git a/packaging/suse15/cloud.spec b/packaging/suse15/cloud.spec
index c0164e474a4..abd7c252f47 100644
--- a/packaging/suse15/cloud.spec
+++ b/packaging/suse15/cloud.spec
@@ -350,6 +350,7 @@ install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}
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
+cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
# Usage server
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
diff --git a/plugins/storage/volume/linstor/pom.xml b/plugins/storage/volume/linstor/pom.xml
index a2388340ed0..d4ab8e70630 100644
--- a/plugins/storage/volume/linstor/pom.xml
+++ b/plugins/storage/volume/linstor/pom.xml
@@ -38,6 +38,11 @@
java-linstor
${cs.java-linstor.version}
+
+ org.apache.cloudstack
+ cloud-plugin-hypervisor-kvm
+ ${project.version}
+
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
similarity index 98%
rename from plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
rename to plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
index 08deba57034..6acd2991022 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
+++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
@@ -71,7 +71,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
private String getHostname() {
// either there is already some function for that in the agent or a better way.
- ProcessBuilder pb = new ProcessBuilder("hostname");
+ ProcessBuilder pb = new ProcessBuilder("/usr/bin/hostname");
try
{
String result;
@@ -86,7 +86,8 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
p.destroy();
return result.trim();
} catch (IOException | InterruptedException exc) {
- throw new CloudRuntimeException("Unable to run 'hostname' command.");
+ Thread.currentThread().interrupt();
+ throw new CloudRuntimeException("Unable to run '/usr/bin/hostname' command.");
}
}
@@ -310,11 +311,11 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
public boolean disconnectPhysicalDiskByPath(String localPath)
{
// get first storage pool from the map, as we don't know any better:
- if (!MapStorageUuidToStoragePool.isEmpty())
+ Optional optFirstPool = MapStorageUuidToStoragePool.values().stream().findFirst();
+ if (optFirstPool.isPresent())
{
s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
- String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
- final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+ final KVMStoragePool pool = optFirstPool.get();
s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
final DevelopersApi api = getLinstorAPI(pool);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
similarity index 100%
rename from plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
rename to plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
index 2a486a11039..bcbdc2244b1 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
@@ -21,22 +21,28 @@ import com.linbit.linstor.api.CloneWaiter;
import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc;
import com.linbit.linstor.api.model.ApiCallRcList;
+import com.linbit.linstor.api.model.Properties;
import com.linbit.linstor.api.model.ResourceDefinition;
import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.Snapshot;
import com.linbit.linstor.api.model.SnapshotRestore;
+import com.linbit.linstor.api.model.VolumeDefinition;
import com.linbit.linstor.api.model.VolumeDefinitionModify;
import javax.annotation.Nonnull;
import javax.inject.Inject;
+
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
@@ -242,14 +248,17 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
deleteResourceDefinition(storagePool, rscName);
long usedBytes = storagePool.getUsedBytes();
- long capacityIops = storagePool.getCapacityIops();
+ Long capacityIops = storagePool.getCapacityIops();
+
+ if (capacityIops != null)
+ {
+ if (volumeInfo.getMaxIops() != null)
+ capacityIops += volumeInfo.getMaxIops();
+ storagePool.setCapacityIops(Math.max(0, capacityIops));
+ }
usedBytes -= volumeInfo.getSize();
- if (volumeInfo.getMaxIops() != null)
- capacityIops += volumeInfo.getMaxIops();
-
storagePool.setUsedBytes(Math.max(0, usedBytes));
- storagePool.setCapacityIops(Math.max(0, capacityIops));
_storagePoolDao.update(storagePoolId, storagePool);
}
@@ -325,6 +334,72 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
}
}
+ private void applyQoSSettings(StoragePoolVO storagePool, DevelopersApi api, String rscName, Long maxIops)
+ throws ApiException
+ {
+ Long currentQosIops = null;
+ List vlmDfns = api.volumeDefinitionList(rscName, null, null);
+ if (!vlmDfns.isEmpty())
+ {
+ Properties props = vlmDfns.get(0).getProps();
+ long iops = Long.parseLong(props.getOrDefault("sys/fs/blkio_throttle_write_iops", "0"));
+ currentQosIops = iops > 0 ? iops : null;
+ }
+
+ if (!Objects.equals(maxIops, currentQosIops))
+ {
+ VolumeDefinitionModify vdm = new VolumeDefinitionModify();
+ if (maxIops != null)
+ {
+ Properties props = new Properties();
+ props.put("sys/fs/blkio_throttle_read_iops", "" + maxIops);
+ props.put("sys/fs/blkio_throttle_write_iops", "" + maxIops);
+ vdm.overrideProps(props);
+ s_logger.info("Apply qos setting: " + maxIops + " to " + rscName);
+ }
+ else
+ {
+ s_logger.info("Remove QoS setting for " + rscName);
+ vdm.deleteProps(Arrays.asList("sys/fs/blkio_throttle_read_iops", "sys/fs/blkio_throttle_write_iops"));
+ }
+ ApiCallRcList answers = api.volumeDefinitionModify(rscName, 0, vdm);
+ checkLinstorAnswersThrow(answers);
+
+ Long capacityIops = storagePool.getCapacityIops();
+ if (capacityIops != null)
+ {
+ long vcIops = currentQosIops != null ? currentQosIops * -1 : 0;
+ long vMaxIops = maxIops != null ? maxIops : 0;
+ long newIops = vcIops + vMaxIops;
+ capacityIops -= newIops;
+ s_logger.info("Current storagepool " + storagePool.getName() + " iops capacity: " + capacityIops);
+ storagePool.setCapacityIops(Math.max(0, capacityIops));
+ _storagePoolDao.update(storagePool.getId(), storagePool);
+ }
+ }
+ }
+
+ private void applyAuxProps(DevelopersApi api, String rscName, String dispName, String vmName)
+ throws ApiException
+ {
+ ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+ Properties props = new Properties();
+ if (dispName != null)
+ {
+ props.put("Aux/cs-name", dispName);
+ }
+ if (vmName != null)
+ {
+ props.put("Aux/cs-vm-name", vmName);
+ }
+ if (!props.isEmpty())
+ {
+ rdm.setOverrideProps(props);
+ ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+ checkLinstorAnswersThrow(answers);
+ }
+ }
+
private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
{
DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
@@ -338,10 +413,13 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
try
{
- s_logger.debug("Linstor: Spawn resource " + rscName);
+ s_logger.info("Linstor: Spawn resource " + rscName);
ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
checkLinstorAnswersThrow(answers);
+ applyAuxProps(linstorApi, rscName, vol.getName(), vol.getAttachedVmName());
+ applyQoSSettings(storagePoolVO, linstorApi, rscName, vol.getMaxIops());
+
return getDeviceName(linstorApi, rscName);
} catch (ApiException apiEx)
{
@@ -361,7 +439,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
try {
- s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+ s_logger.info("Clone resource definition " + cloneRes + " to " + rscName);
ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
cloneRequest.setName(rscName);
ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
@@ -373,6 +451,10 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
}
+ s_logger.info("Clone resource definition " + cloneRes + " to " + rscName + " finished");
+ applyAuxProps(linstorApi, rscName, volumeInfo.getName(), volumeInfo.getAttachedVmName());
+ applyQoSSettings(storagePoolVO, linstorApi, rscName, volumeInfo.getMaxIops());
+
return getDeviceName(linstorApi, rscName);
} catch (ApiException apiEx) {
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
@@ -413,10 +495,13 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
checkLinstorAnswersThrow(answers);
// restore snapshot to new resource
- s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+ s_logger.info("Restore resource from snapshot: " + cloneRes + ":" + snapName);
answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
checkLinstorAnswersThrow(answers);
+ applyAuxProps(linstorApi, rscName, volumeVO.getName(), null);
+ applyQoSSettings(storagePoolVO, linstorApi, rscName, volumeVO.getMaxIops());
+
return getDeviceName(linstorApi, rscName);
} catch (ApiException apiEx) {
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
@@ -608,12 +693,11 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
}
private CreateCmdResult notifyResize(
- DataObject data,
+ VolumeObject vol,
long oldSize,
ResizeVolumePayload resizeParameter)
{
- VolumeObject vol = (VolumeObject) data;
- StoragePool pool = (StoragePool) data.getDataStore();
+ StoragePool pool = (StoragePool) vol.getDataStore();
ResizeVolumeCommand resizeCmd =
new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), oldSize, resizeParameter.newSize, resizeParameter.shrinkOk,
@@ -642,7 +726,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
public void resize(DataObject data, AsyncCompletionCallback callback)
{
final VolumeObject vol = (VolumeObject) data;
- final StoragePool pool = (StoragePool) data.getDataStore();
+ final StoragePoolVO pool = _storagePoolDao.findById(data.getDataStore().getId());
final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
final ResizeVolumePayload resizeParameter = (ResizeVolumePayload) vol.getpayload();
@@ -654,6 +738,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
dfm.setSizeKib(resizeParameter.newSize / 1024);
try
{
+ applyQoSSettings(pool, api, rscName, resizeParameter.newMaxIops);
+ {
+ final VolumeVO volume = _volumeDao.findById(vol.getId());
+ volume.setMinIops(resizeParameter.newMinIops);
+ volume.setMaxIops(resizeParameter.newMaxIops);
+ _volumeDao.update(volume.getId(), volume);
+ }
+
ApiCallRcList answers = api.volumeDefinitionModify(rscName, 0, dfm);
if (answers.hasError())
{
@@ -680,7 +772,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
} else
{
// notify guests
- result = notifyResize(data, oldSize, resizeParameter);
+ result = notifyResize(vol, oldSize, resizeParameter);
}
callback.complete(result);
diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
index b9cdae2ea60..a7d0c7fc0a3 100644
--- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
+++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
@@ -89,6 +89,7 @@ public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLif
Long clusterId = (Long) dsInfos.get("clusterId");
String storagePoolName = (String) dsInfos.get("name");
String providerName = (String) dsInfos.get("providerName");
+ Long capacityIops = (Long) dsInfos.get("capacityIops");
String tags = (String) dsInfos.get("tags");
@SuppressWarnings("unchecked")
Map details = (Map) dsInfos.get("details");
@@ -145,11 +146,14 @@ public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLif
}
long capacityBytes = LinstorUtil.getCapacityBytes(url, resourceGroup);
-
if (capacityBytes <= 0) {
throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0.");
}
+ if (capacityIops != null) {
+ parameters.setCapacityIops(capacityIops);
+ }
+
parameters.setHost(url);
parameters.setPort(port);
parameters.setPath(resourceGroup);
@@ -161,7 +165,7 @@ public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLif
parameters.setManaged(false);
parameters.setCapacityBytes(capacityBytes);
parameters.setUsedBytes(0);
- parameters.setCapacityIops(0L);
+ parameters.setCapacityIops(capacityIops);
parameters.setHypervisorType(HypervisorType.KVM);
parameters.setTags(tags);
parameters.setDetails(details);
diff --git a/ui/src/views/infra/AddPrimaryStorage.vue b/ui/src/views/infra/AddPrimaryStorage.vue
index 02dd30baedb..9e628dd287c 100644
--- a/ui/src/views/infra/AddPrimaryStorage.vue
+++ b/ui/src/views/infra/AddPrimaryStorage.vue
@@ -308,6 +308,12 @@
+
+
+
+
+
+
@@ -749,6 +755,9 @@ export default {
params.provider = 'Linstor'
values.managed = false
params['details[0].resourceGroup'] = values.resourcegroup
+ if (values.capacityIops && values.capacityIops.length > 0) {
+ params.capacityIops = values.capacityIops.split(',').join('')
+ }
}
params.url = url
if (values.provider !== 'DefaultPrimary' && values.provider !== 'PowerFlex') {