linstor: fix live migrate on non-hyperconverged setups (#9832)

In non-hyperconverged setups, diskless nodes don't have a connection
to each other, so setting properties there had no effect.
Now it is checked if a connection exists,
between the live migration nodes and if not,
it will set the allow-two-primaries on resource-definition level.
This commit is contained in:
Rene Peinthor 2024-11-07 10:16:32 +01:00 committed by GitHub
parent 1af41585b7
commit 371e244375
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 16 deletions

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -53,6 +54,7 @@ import com.linbit.linstor.api.model.Resource;
import com.linbit.linstor.api.model.ResourceConnectionModify; import com.linbit.linstor.api.model.ResourceConnectionModify;
import com.linbit.linstor.api.model.ResourceDefinition; import com.linbit.linstor.api.model.ResourceDefinition;
import com.linbit.linstor.api.model.ResourceGroup; import com.linbit.linstor.api.model.ResourceGroup;
import com.linbit.linstor.api.model.ResourceDefinitionModify;
import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.ResourceWithVolumes;
@ -257,6 +259,34 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
} }
} }
private void setAllowTwoPrimariesOnRD(DevelopersApi api, String rscName) throws ApiException {
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
Properties props = new Properties();
props.put("DrbdOptions/Net/allow-two-primaries", "yes");
props.put("DrbdOptions/Net/protocol", "C");
rdm.setOverrideProps(props);
ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
if (answers.hasError()) {
s_logger.error(String.format("Unable to set protocol C and 'allow-two-primaries' on %s", rscName));
// do not fail here as adding allow-two-primaries property is only a problem while live migrating
}
}
private void setAllowTwoPrimariesOnRc(DevelopersApi api, String rscName, String inUseNode) throws ApiException {
ResourceConnectionModify rcm = new ResourceConnectionModify();
Properties props = new Properties();
props.put("DrbdOptions/Net/allow-two-primaries", "yes");
props.put("DrbdOptions/Net/protocol", "C");
rcm.setOverrideProps(props);
ApiCallRcList answers = api.resourceConnectionModify(rscName, inUseNode, localNodeName, rcm);
if (answers.hasError()) {
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
}
}
/** /**
* Checks if the given resource is in use by drbd on any host and * Checks if the given resource is in use by drbd on any host and
* if so set the drbd option allow-two-primaries * if so set the drbd option allow-two-primaries
@ -268,17 +298,13 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
String inUseNode = LinstorUtil.isResourceInUse(api, rscName); String inUseNode = LinstorUtil.isResourceInUse(api, rscName);
if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) { if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) {
// allow 2 primaries for live migration, should be removed by disconnect on the other end // allow 2 primaries for live migration, should be removed by disconnect on the other end
ResourceConnectionModify rcm = new ResourceConnectionModify();
Properties props = new Properties(); // if non hyperconverged setup, we have to set allow-two-primaries on the resource-definition
props.put("DrbdOptions/Net/allow-two-primaries", "yes"); // as there is no resource connection between diskless nodes.
props.put("DrbdOptions/Net/protocol", "C"); if (LinstorUtil.areResourcesDiskless(api, rscName, Arrays.asList(inUseNode, localNodeName))) {
rcm.setOverrideProps(props); setAllowTwoPrimariesOnRD(api, rscName);
ApiCallRcList answers = api.resourceConnectionModify(rscName, inUseNode, localNodeName, rcm); } else {
if (answers.hasError()) { setAllowTwoPrimariesOnRc(api, rscName, inUseNode);
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
} }
} }
} }
@ -317,11 +343,22 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
return true; return true;
} }
private void removeTwoPrimariesRcProps(DevelopersApi api, String inUseNode, String rscName) throws ApiException { private void removeTwoPrimariesRDProps(DevelopersApi api, String rscName, List<String> deleteProps)
throws ApiException {
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
rdm.deleteProps(deleteProps);
ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
if (answers.hasError()) {
s_logger.error(
String.format("Failed to remove 'protocol' and 'allow-two-primaries' on %s: %s",
rscName, LinstorUtil.getBestErrorMessage(answers)));
// do not fail here as removing allow-two-primaries property isn't fatal
}
}
private void removeTwoPrimariesRcProps(DevelopersApi api, String rscName, String inUseNode, List<String> deleteProps)
throws ApiException {
ResourceConnectionModify rcm = new ResourceConnectionModify(); ResourceConnectionModify rcm = new ResourceConnectionModify();
List<String> deleteProps = new ArrayList<>();
deleteProps.add("DrbdOptions/Net/allow-two-primaries");
deleteProps.add("DrbdOptions/Net/protocol");
rcm.deleteProps(deleteProps); rcm.deleteProps(deleteProps);
ApiCallRcList answers = api.resourceConnectionModify(rscName, localNodeName, inUseNode, rcm); ApiCallRcList answers = api.resourceConnectionModify(rscName, localNodeName, inUseNode, rcm);
if (answers.hasError()) { if (answers.hasError()) {
@ -334,6 +371,15 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
} }
} }
private void removeTwoPrimariesProps(DevelopersApi api, String inUseNode, String rscName) throws ApiException {
List<String> deleteProps = new ArrayList<>();
deleteProps.add("DrbdOptions/Net/allow-two-primaries");
deleteProps.add("DrbdOptions/Net/protocol");
removeTwoPrimariesRDProps(api, rscName, deleteProps);
removeTwoPrimariesRcProps(api, rscName, inUseNode, deleteProps);
}
private boolean tryDisconnectLinstor(String volumePath, KVMStoragePool pool) private boolean tryDisconnectLinstor(String volumePath, KVMStoragePool pool)
{ {
if (volumePath == null) { if (volumePath == null) {
@ -367,7 +413,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
try { try {
String inUseNode = LinstorUtil.isResourceInUse(api, rsc.getName()); String inUseNode = LinstorUtil.isResourceInUse(api, rsc.getName());
if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) { if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) {
removeTwoPrimariesRcProps(api, inUseNode, rsc.getName()); removeTwoPrimariesProps(api, inUseNode, rsc.getName());
} }
} catch (ApiException apiEx) { } catch (ApiException apiEx) {
s_logger.error(apiEx.getBestMessage()); s_logger.error(apiEx.getBestMessage());

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.storage.datastore.util; package org.apache.cloudstack.storage.datastore.util;
import com.linbit.linstor.api.ApiClient; import com.linbit.linstor.api.ApiClient;
import com.linbit.linstor.api.ApiConsts;
import com.linbit.linstor.api.ApiException; import com.linbit.linstor.api.ApiException;
import com.linbit.linstor.api.DevelopersApi; import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc; import com.linbit.linstor.api.model.ApiCallRc;
@ -27,8 +28,10 @@ import com.linbit.linstor.api.model.ResourceGroup;
import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.StoragePool; import com.linbit.linstor.api.model.StoragePool;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -113,6 +116,28 @@ public class LinstorUtil {
return null; return null;
} }
/**
* Check if the given resources are diskless.
*
* @param api developer api object to use
* @param rscName resource name to check in use state.
* @return NodeName where the resource is inUse, if not in use `null`
* @throws ApiException forwards api errors
*/
public static boolean areResourcesDiskless(DevelopersApi api, String rscName, Collection<String> nodeNames)
throws ApiException {
List<Resource> rscs = api.resourceList(rscName, null, null);
if (rscs != null) {
Collection<String> disklessNodes = rscs.stream()
.filter(rsc -> rsc.getFlags() != null && (rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS) ||
rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS)))
.map(rsc -> rsc.getNodeName().toLowerCase())
.collect(Collectors.toList());
return disklessNodes.containsAll(nodeNames.stream().map(String::toLowerCase).collect(Collectors.toList()));
}
return false;
}
/** /**
* Try to get the device path for the given resource name. * Try to get the device path for the given resource name.
* This could be made a bit more direct after java-linstor api is fixed for layer data subtypes. * This could be made a bit more direct after java-linstor api is fixed for layer data subtypes.