diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md new file mode 100644 index 00000000000..30f0225b45e --- /dev/null +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to Linstor CloudStack plugin will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2024-08-27] + +### Changed + +- Allow two primaries(+protocol c) is now set on resource-connection level instead of rd diff --git a/plugins/storage/volume/linstor/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 index 64626788999..83a84a91622 100644 --- a/plugins/storage/volume/linstor/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 @@ -16,6 +16,7 @@ // under the License. package com.cloud.hypervisor.kvm.storage; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -26,6 +27,7 @@ import javax.annotation.Nonnull; import com.cloud.storage.Storage; import com.cloud.utils.exception.CloudRuntimeException; + import org.apache.cloudstack.storage.datastore.util.LinstorUtil; import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImgException; @@ -43,8 +45,8 @@ import com.linbit.linstor.api.model.ApiCallRcList; import com.linbit.linstor.api.model.Properties; import com.linbit.linstor.api.model.ProviderKind; import com.linbit.linstor.api.model.Resource; +import com.linbit.linstor.api.model.ResourceConnectionModify; import com.linbit.linstor.api.model.ResourceDefinition; -import com.linbit.linstor.api.model.ResourceDefinitionModify; import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.ResourceWithVolumes; @@ -237,15 +239,19 @@ public class LinstorStorageAdaptor implements StorageAdaptor { * @throws ApiException if any problem connecting to the Linstor controller */ private void allow2PrimariesIfInUse(DevelopersApi api, String rscName) throws ApiException { - if (LinstorUtil.isResourceInUse(api, rscName)) { + String inUseNode = LinstorUtil.isResourceInUse(api, rscName); + if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) { // allow 2 primaries for live migration, should be removed by disconnect on the other end - ResourceDefinitionModify rdm = new ResourceDefinitionModify(); + ResourceConnectionModify rcm = new ResourceConnectionModify(); Properties props = new Properties(); props.put("DrbdOptions/Net/allow-two-primaries", "yes"); - rdm.setOverrideProps(props); - ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm); + props.put("DrbdOptions/Net/protocol", "C"); + rcm.setOverrideProps(props); + ApiCallRcList answers = api.resourceConnectionModify(rscName, inUseNode, localNodeName, rcm); if (answers.hasError()) { - s_logger.error("Unable to set 'allow-two-primaries' on " + rscName); + s_logger.error(String.format( + "Unable to set protocol C and 'allow-two-primaries' on %s/%s/%s", + inUseNode, localNodeName, rscName)); // do not fail here as adding allow-two-primaries property is only a problem while live migrating } } @@ -285,6 +291,23 @@ public class LinstorStorageAdaptor implements StorageAdaptor { return true; } + private void removeTwoPrimariesRcProps(DevelopersApi api, String inUseNode, String rscName) throws ApiException { + ResourceConnectionModify rcm = new ResourceConnectionModify(); + List deleteProps = new ArrayList<>(); + deleteProps.add("DrbdOptions/Net/allow-two-primaries"); + deleteProps.add("DrbdOptions/Net/protocol"); + rcm.deleteProps(deleteProps); + ApiCallRcList answers = api.resourceConnectionModify(rscName, localNodeName, inUseNode, rcm); + if (answers.hasError()) { + s_logger.error( + String.format("Failed to remove 'protocol' and 'allow-two-primaries' on %s/%s/%s: %s", + localNodeName, + inUseNode, + rscName, LinstorUtil.getBestErrorMessage(answers))); + // do not fail here as removing allow-two-primaries property isn't fatal + } + } + private boolean tryDisconnectLinstor(String volumePath, KVMStoragePool pool) { if (volumePath == null) { @@ -314,9 +337,18 @@ public class LinstorStorageAdaptor implements StorageAdaptor { if (optRsc.isPresent()) { + Resource rsc = optRsc.get(); try { - Resource rsc = optRsc.get(); + String inUseNode = LinstorUtil.isResourceInUse(api, rsc.getName()); + if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) { + removeTwoPrimariesRcProps(api, inUseNode, rsc.getName()); + } + } catch (ApiException apiEx) { + s_logger.error(apiEx.getBestMessage()); + // do not fail here as removing allow-two-primaries property or deleting diskless isn't fatal + } + try { // if diskless resource remove it, in the worst case it will be transformed to a tiebreaker if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS) && @@ -324,17 +356,6 @@ public class LinstorStorageAdaptor implements StorageAdaptor { ApiCallRcList delAnswers = api.resourceDelete(rsc.getName(), localNodeName); logLinstorAnswers(delAnswers); } - - // remove allow-two-primaries - ResourceDefinitionModify rdm = new ResourceDefinitionModify(); - rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries")); - ApiCallRcList answers = api.resourceDefinitionModify(rsc.getName(), rdm); - if (answers.hasError()) { - s_logger.error( - String.format("Failed to remove 'allow-two-primaries' on %s: %s", - rsc.getName(), LinstorUtil.getBestErrorMessage(answers))); - // do not fail here as removing allow-two-primaries property isn't fatal - } } catch (ApiException apiEx) { s_logger.error(apiEx.getBestMessage()); // do not fail here as removing allow-two-primaries property or deleting diskless isn't fatal diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index e78a1be7125..b857b4ebb83 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -191,17 +191,20 @@ public class LinstorUtil { * * @param api developer api object to use * @param rscName resource name to check in use state. - * @return True if a resource found that is in use(primary) state, else false. + * @return NodeName where the resource is inUse, if not in use `null` * @throws ApiException forwards api errors */ - public static boolean isResourceInUse(DevelopersApi api, String rscName) throws ApiException { + public static String isResourceInUse(DevelopersApi api, String rscName) throws ApiException { List rscs = api.resourceList(rscName, null, null); if (rscs != null) { return rscs.stream() - .anyMatch(rsc -> rsc.getState() != null && Boolean.TRUE.equals(rsc.getState().isInUse())); - } + .filter(rsc -> rsc.getState() != null && Boolean.TRUE.equals(rsc.getState().isInUse())) + .map(Resource::getNodeName) + .findFirst() + .orElse(null); + } s_logger.error("isResourceInUse: null returned from resourceList"); - return false; + return null; } /**