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