diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index bc7b6903aae..7e3bd3847b1 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -67,6 +67,31 @@ java-linstor ${cs.java-linstor.version} + + com.fasterxml.jackson.core + jackson-core + ${cs.jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${cs.jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${cs.jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${cs.jackson.version} + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + ${cs.jackson.version} + net.java.dev.jna jna diff --git a/plugins/storage/object/ceph/src/main/java/org/apache/cloudstack/storage/datastore/driver/CephObjectStoreDriverImpl.java b/plugins/storage/object/ceph/src/main/java/org/apache/cloudstack/storage/datastore/driver/CephObjectStoreDriverImpl.java index 23f155c16c5..2a7b6e1dda6 100644 --- a/plugins/storage/object/ceph/src/main/java/org/apache/cloudstack/storage/datastore/driver/CephObjectStoreDriverImpl.java +++ b/plugins/storage/object/ceph/src/main/java/org/apache/cloudstack/storage/datastore/driver/CephObjectStoreDriverImpl.java @@ -350,7 +350,7 @@ public class CephObjectStoreDriverImpl extends BaseObjectStoreDriverImpl { new AWSStaticCredentialsProvider( new BasicAWSCredentials(accessKey, secretKey))) .withEndpointConfiguration( - new AwsClientBuilder.EndpointConfiguration(url, null)) + new AwsClientBuilder.EndpointConfiguration(url, "us-east-1")) .build(); if (client == null) { 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 d3b797e319f..67fe4dcc059 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 @@ -63,6 +63,8 @@ import com.cloud.api.storage.LinstorBackupSnapshotCommand; import com.cloud.api.storage.LinstorRevertBackupSnapshotCommand; import com.cloud.configuration.Config; import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.resource.ResourceState; import com.cloud.storage.DataStoreRole; @@ -922,9 +924,10 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); - Optional optEP = getDiskfullEP(linstorApi, rscName); + final StoragePool pool = (StoragePool) volumeInfo.getDataStore(); + Optional optEP = getDiskfullEP(linstorApi, pool, rscName); if (optEP.isEmpty()) { - optEP = getLinstorEP(linstorApi, rscName); + optEP = getLinstorEP(linstorApi, pool, rscName); } if (optEP.isPresent()) { @@ -1064,13 +1067,29 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver Answer answer = copyVolume(srcData, dstData); res = new CopyCommandResult(null, answer); } else { - Answer answer = new Answer(null, false, "noimpl"); - res = new CopyCommandResult(null, answer); - res.setResult("Not implemented yet"); + throw new CloudRuntimeException("Not implemented for Linstor primary storage."); } callback.complete(res); } + private Host getEnabledClusterHost(StoragePool storagePool, List linstorNodeNames) { + List csHosts; + if (storagePool.getClusterId() != null) { + csHosts = _hostDao.findByClusterId(storagePool.getClusterId()); + } else { + csHosts = _hostDao.findByDataCenterId(storagePool.getDataCenterId()); + } + Collections.shuffle(csHosts); // so we do not always pick the same host for operations + for (HostVO host : csHosts) { + if (host.getResourceState() == ResourceState.Enabled && + host.getStatus() == Status.Up && + linstorNodeNames.contains(host.getName())) { + return host; + } + } + return null; + } + /** * Tries to get a Linstor cloudstack end point, that is at least diskless. * @@ -1079,47 +1098,37 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver * @return Optional RemoteHostEndPoint if one could get found. * @throws ApiException */ - private Optional getLinstorEP(DevelopersApi api, String rscName) throws ApiException { + private Optional getLinstorEP(DevelopersApi api, StoragePool storagePool, String rscName) + throws ApiException { List linstorNodeNames = LinstorUtil.getLinstorNodeNames(api); - Collections.shuffle(linstorNodeNames); // do not always pick the first linstor node - - Host host = null; - for (String nodeName : linstorNodeNames) { - host = _hostDao.findByName(nodeName); - if (host != null && host.getResourceState() == ResourceState.Enabled) { - logger.info(String.format("Linstor: Make resource %s available on node %s ...", rscName, nodeName)); - ApiCallRcList answers = api.resourceMakeAvailableOnNode(rscName, nodeName, new ResourceMakeAvailable()); - if (!answers.hasError()) { - break; // found working host - } else { - logger.error( - String.format("Linstor: Unable to make resource %s on node %s available: %s", - rscName, - nodeName, - LinstorUtil.getBestErrorMessage(answers))); - } + Host host = getEnabledClusterHost(storagePool, linstorNodeNames); + if (host != null) { + logger.info("Linstor: Make resource {} available on node {} ...", rscName, host.getName()); + ApiCallRcList answers = api.resourceMakeAvailableOnNode( + rscName, host.getName(), new ResourceMakeAvailable()); + if (answers.hasError()) { + logger.error("Linstor: Unable to make resource {} on node {} available: {}", + rscName, host.getName(), LinstorUtil.getBestErrorMessage(answers)); + return Optional.empty(); + } else { + return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); } } - if (host == null) - { - logger.error("Linstor: Couldn't create a resource on any cloudstack host."); - return Optional.empty(); - } - else - { - return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); - } + logger.error("Linstor: Couldn't create a resource on any cloudstack host."); + return Optional.empty(); } - private Optional getDiskfullEP(DevelopersApi api, String rscName) throws ApiException { + private Optional getDiskfullEP(DevelopersApi api, StoragePool storagePool, String rscName) + throws ApiException { List linSPs = LinstorUtil.getDiskfulStoragePools(api, rscName); if (linSPs != null) { - for (com.linbit.linstor.api.model.StoragePool sp : linSPs) { - Host host = _hostDao.findByName(sp.getNodeName()); - if (host != null && host.getResourceState() == ResourceState.Enabled) { - return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); - } + List linstorNodeNames = linSPs.stream() + .map(com.linbit.linstor.api.model.StoragePool::getNodeName) + .collect(Collectors.toList()); + Host host = getEnabledClusterHost(storagePool, linstorNodeNames); + if (host != null) { + return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); } } logger.error("Linstor: No diskfull host found."); @@ -1200,12 +1209,12 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver VirtualMachineManager.ExecuteInSequence.value()); try { - Optional optEP = getLinstorEP(api, rscName); + Optional optEP = getLinstorEP(api, pool, rscName); if (optEP.isPresent()) { answer = optEP.get().sendMessage(cmd); } else { - answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint."); deleteResourceDefinition(pool, rscName); + throw new CloudRuntimeException("Unable to get matching Linstor endpoint."); } } catch (ApiException exc) { logger.error("copy template failed: ", exc); @@ -1242,12 +1251,12 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver Answer answer; try { - Optional optEP = getLinstorEP(api, rscName); + Optional optEP = getLinstorEP(api, pool, rscName); if (optEP.isPresent()) { answer = optEP.get().sendMessage(cmd); } else { - answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint."); + throw new CloudRuntimeException("Unable to get matching Linstor endpoint."); } } catch (ApiException exc) { logger.error("copy volume failed: ", exc); @@ -1280,14 +1289,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver try { String devName = restoreResourceFromSnapshot(api, pool, rscName, snapshotName, restoreName); - Optional optEPAny = getLinstorEP(api, restoreName); + Optional optEPAny = getLinstorEP(api, pool, restoreName); if (optEPAny.isPresent()) { // patch the src device path to the temporary linstor resource snapshotObject.setPath(devName); origCmd.setSrcTO(snapshotObject.getTO()); answer = optEPAny.get().sendMessage(origCmd); - } else{ - answer = new Answer(origCmd, false, "Unable to get matching Linstor endpoint."); + } else { + throw new CloudRuntimeException("Unable to get matching Linstor endpoint."); } } finally { // delete the temporary resource, noop if already gone @@ -1349,7 +1358,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver VirtualMachineManager.ExecuteInSequence.value()); cmd.setOptions(options); - Optional optEP = getDiskfullEP(api, rscName); + Optional optEP = getDiskfullEP(api, pool, rscName); Answer answer; if (optEP.isPresent()) { answer = optEP.get().sendMessage(cmd); diff --git a/pom.xml b/pom.xml index 97d0d2645da..e56c8b96c54 100644 --- a/pom.xml +++ b/pom.xml @@ -188,6 +188,7 @@ 5.3.26 0.5.4 3.1.7 + 3.25.5 @@ -730,6 +731,17 @@ xml-apis 2.0.2 + + + com.google.protobuf + protobuf-java + ${cs.protobuf.version} + + + com.google.protobuf + protobuf-java-util + ${cs.protobuf.version} + com.linbit.linstor.api java-linstor diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 5667fc33484..bda74027288 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -403,6 +403,7 @@ "label.app.name": "CloudStack", "label.application.policy.set": "Application Policy Set", "label.apply": "Apply", +"label.apply.to.all": "Apply to all", "label.apply.tungsten.firewall.policy": "Apply Firewall Policy", "label.apply.tungsten.network.policy": "Apply Network Policy", "label.apply.tungsten.tag": "Apply tag", @@ -4034,6 +4035,7 @@ "message.vnf.no.credentials": "No credentials found for the VNF appliance.", "message.vnf.select.networks": "Please select the relevant network for each VNF NIC.", "message.volume.desc": "Volume to use as a ROOT disk", +"message.volume.pool.apply.to.all": "Selected storage pool will be applied to all existing volumes of the instance.", "message.volume.state.allocated": "The volume is allocated but has not been created yet.", "message.volume.state.attaching": "The volume is attaching to a volume from Ready state.", "message.volume.state.copying": "The volume is being copied from the image store to primary storage, in case it's an uploaded volume.", diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index 3b84def952e..0031d730f56 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -709,7 +709,7 @@
{{ $t('label.storagepool') }}
- {{ resource.storage || resource.storageid }} + {{ resource.storage || resource.storageid }} {{ resource.storage || resource.storageid }} {{ resource.storagetype }} diff --git a/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue b/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue index 5319e39334b..b5663402a93 100644 --- a/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue +++ b/ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue @@ -206,13 +206,19 @@ export default { closeVolumeStoragePoolSelector () { this.selectedVolumeForStoragePoolSelection = {} }, - handleVolumeStoragePoolSelection (volumeId, storagePool) { + handleVolumeStoragePoolSelection (volumeId, storagePool, applyToAll) { for (const volume of this.volumes) { - if (volume.id === volumeId) { + if (applyToAll) { volume.selectedstorageid = storagePool.id volume.selectedstoragename = storagePool.name volume.selectedstorageclusterid = storagePool.clusterid - break + } else { + if (volume.id === volumeId) { + volume.selectedstorageid = storagePool.id + volume.selectedstoragename = storagePool.name + volume.selectedstorageclusterid = storagePool.clusterid + break + } } } this.updateVolumeToStoragePoolSelection() diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 47aa3d2ddef..168e355cbc8 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -161,17 +161,9 @@ >{{ $t(text.toLowerCase()) }} - {{ text }} - {{ text }} - + {{ text }} + {{ text }} +   @@ -607,10 +599,7 @@ {{ text }}