Refactor affinity group mapping

This commit is contained in:
Daman Arora 2026-01-07 08:41:17 -05:00
parent 6e3ede9d78
commit f625d6ed93
4 changed files with 90 additions and 68 deletions

View File

@ -18,6 +18,7 @@ package com.cloud.kubernetes.cluster;
import org.apache.cloudstack.acl.ControlledEntity;
import java.util.List;
import java.util.Map;
import com.cloud.user.Account;
@ -36,6 +37,6 @@ public interface KubernetesServiceHelper extends Adapter {
boolean isValidNodeType(String nodeType);
Map<String, Long> getServiceOfferingNodeTypeMap(Map<String, Map<String, String>> serviceOfferingNodeTypeMap);
Map<String, Long> getTemplateNodeTypeMap(Map<String, Map<String, String>> templateNodeTypeMap);
Map<String, String> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap);
Map<String, List<Long>> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap);
void cleanupForAccount(Account account);
}

View File

@ -18,7 +18,9 @@ package com.cloud.kubernetes.cluster;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -248,8 +250,8 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
return mapping;
}
protected void checkNodeTypeAffinityGroupEntryCompleteness(String nodeTypeStr, String affinityGroupUuids) {
if (StringUtils.isAnyBlank(nodeTypeStr, affinityGroupUuids)) {
protected void checkNodeTypeAffinityGroupEntryCompleteness(String nodeType, String affinityGroupUuids) {
if (StringUtils.isAnyBlank(nodeType, affinityGroupUuids)) {
String error = String.format("Any Node Type to Affinity Group entry should have a valid '%s' and '%s' values",
VmDetailConstants.CKS_NODE_TYPE, VmDetailConstants.AFFINITY_GROUP);
logger.error(error);
@ -257,15 +259,15 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
}
}
protected void checkNodeTypeAffinityGroupEntryNodeType(String nodeTypeStr) {
if (!isValidNodeType(nodeTypeStr)) {
String error = String.format("The provided value '%s' for Node Type is invalid", nodeTypeStr);
protected void checkNodeTypeAffinityGroupEntryNodeType(String nodeType) {
if (!isValidNodeType(nodeType)) {
String error = String.format("The provided value '%s' for Node Type is invalid", nodeType);
logger.error(error);
throw new InvalidParameterValueException(error);
}
}
protected void validateAffinityGroupUuid(String affinityGroupUuid) {
protected Long validateAffinityGroupUuidAndGetId(String affinityGroupUuid) {
if (StringUtils.isBlank(affinityGroupUuid)) {
String error = "Empty affinity group UUID provided";
logger.error(error);
@ -277,55 +279,52 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
logger.error(error);
throw new InvalidParameterValueException(error);
}
return affinityGroup.getId();
}
protected String validateAndNormalizeAffinityGroupUuids(String affinityGroupUuids) {
protected List<Long> validateAndGetAffinityGroupIds(String affinityGroupUuids) {
String[] uuids = affinityGroupUuids.split(",");
StringBuilder normalizedUuids = new StringBuilder();
for (int i = 0; i < uuids.length; i++) {
String uuid = uuids[i].trim();
validateAffinityGroupUuid(uuid);
if (i > 0) {
normalizedUuids.append(",");
}
normalizedUuids.append(uuid);
List<Long> affinityGroupIds = new ArrayList<>();
for (String uuid : uuids) {
String trimmedUuid = uuid.trim();
Long affinityGroupId = validateAffinityGroupUuidAndGetId(trimmedUuid);
affinityGroupIds.add(affinityGroupId);
}
return normalizedUuids.toString();
return affinityGroupIds;
}
protected void addNodeTypeAffinityGroupEntry(String nodeTypeStr, String validatedAffinityGroupUuids, Map<String, String> mapping) {
protected void addNodeTypeAffinityGroupEntry(String nodeType, List<Long> affinityGroupIds, Map<String, List<Long>> nodeTypeToAffinityGroupIds) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Node Type: '%s' should use affinity group IDs: '%s'", nodeTypeStr, validatedAffinityGroupUuids));
logger.debug(String.format("Node Type: '%s' should use affinity group IDs: '%s'", nodeType, affinityGroupIds));
}
KubernetesClusterNodeType nodeType = KubernetesClusterNodeType.valueOf(nodeTypeStr.toUpperCase());
mapping.put(nodeType.name(), validatedAffinityGroupUuids);
KubernetesClusterNodeType clusterNodeType = KubernetesClusterNodeType.valueOf(nodeType.toUpperCase());
nodeTypeToAffinityGroupIds.put(clusterNodeType.name(), affinityGroupIds);
}
protected void processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(Map<String, String> entry, Map<String, String> mapping) {
if (MapUtils.isEmpty(entry)) {
protected void processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(Map<String, String> nodeTypeAffinityConfig, Map<String, List<Long>> nodeTypeToAffinityGroupIds) {
if (MapUtils.isEmpty(nodeTypeAffinityConfig)) {
return;
}
String nodeTypeStr = entry.get(VmDetailConstants.CKS_NODE_TYPE);
String affinityGroupUuids = entry.get(VmDetailConstants.AFFINITY_GROUP);
checkNodeTypeAffinityGroupEntryCompleteness(nodeTypeStr, affinityGroupUuids);
checkNodeTypeAffinityGroupEntryNodeType(nodeTypeStr);
String nodeType = nodeTypeAffinityConfig.get(VmDetailConstants.CKS_NODE_TYPE);
String affinityGroupUuids = nodeTypeAffinityConfig.get(VmDetailConstants.AFFINITY_GROUP);
checkNodeTypeAffinityGroupEntryCompleteness(nodeType, affinityGroupUuids);
checkNodeTypeAffinityGroupEntryNodeType(nodeType);
String validatedUuids = validateAndNormalizeAffinityGroupUuids(affinityGroupUuids);
addNodeTypeAffinityGroupEntry(nodeTypeStr, validatedUuids, mapping);
List<Long> affinityGroupIds = validateAndGetAffinityGroupIds(affinityGroupUuids);
addNodeTypeAffinityGroupEntry(nodeType, affinityGroupIds, nodeTypeToAffinityGroupIds);
}
@Override
public Map<String, String> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap) {
Map<String, String> mapping = new HashMap<>();
public Map<String, List<Long>> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap) {
Map<String, List<Long>> nodeTypeToAffinityGroupIds = new HashMap<>();
if (MapUtils.isNotEmpty(affinityGroupNodeTypeMap)) {
for (Map<String, String> entry : affinityGroupNodeTypeMap.values()) {
processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(entry, mapping);
for (Map<String, String> nodeTypeAffinityConfig : affinityGroupNodeTypeMap.values()) {
processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(nodeTypeAffinityConfig, nodeTypeToAffinityGroupIds);
}
}
return mapping;
return nodeTypeToAffinityGroupIds;
}
public void cleanupForAccount(Account account) {
kubernetesClusterService.cleanupForAccount(account);
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.kubernetes.cluster;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -327,7 +328,7 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
return kubernetesServiceHelper.getTemplateNodeTypeMap(templateNodeTypeMap);
}
public Map<String, String> getAffinityGroupNodeTypeMap() {
public Map<String, List<Long>> getAffinityGroupNodeTypeMap() {
return kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
}

View File

@ -17,7 +17,9 @@
package com.cloud.kubernetes.cluster;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -218,76 +220,87 @@ public class KubernetesServiceHelperImplTest {
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAffinityGroupUuidBlank() {
kubernetesServiceHelper.validateAffinityGroupUuid("");
public void testValidateAffinityGroupUuidAndGetIdBlank() {
kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("");
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAffinityGroupUuidNotFound() {
public void testValidateAffinityGroupUuidAndGetIdNotFound() {
Mockito.when(affinityGroupDao.findByUuid("non-existent-uuid")).thenReturn(null);
kubernetesServiceHelper.validateAffinityGroupUuid("non-existent-uuid");
kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("non-existent-uuid");
}
@Test
public void testValidateAffinityGroupUuidValid() {
public void testValidateAffinityGroupUuidAndGetIdValid() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("valid-uuid")).thenReturn(affinityGroup);
kubernetesServiceHelper.validateAffinityGroupUuid("valid-uuid");
Long result = kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("valid-uuid");
Assert.assertEquals(Long.valueOf(100L), result);
}
@Test
public void testValidateAndNormalizeAffinityGroupUuidsSingleUuid() {
public void testValidateAndGetAffinityGroupIdsSingleUuid() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(1L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup);
String result = kubernetesServiceHelper.validateAndNormalizeAffinityGroupUuids("uuid1");
Assert.assertEquals("uuid1", result);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1");
Assert.assertEquals(1, result.size());
Assert.assertEquals(Long.valueOf(1L), result.get(0));
}
@Test
public void testValidateAndNormalizeAffinityGroupUuidsMultipleUuids() {
public void testValidateAndGetAffinityGroupIdsMultipleUuids() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup3 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroup3.getId()).thenReturn(3L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
Mockito.when(affinityGroupDao.findByUuid("uuid3")).thenReturn(affinityGroup3);
String result = kubernetesServiceHelper.validateAndNormalizeAffinityGroupUuids("uuid1,uuid2,uuid3");
Assert.assertEquals("uuid1,uuid2,uuid3", result);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1,uuid2,uuid3");
Assert.assertEquals(3, result.size());
Assert.assertEquals(Arrays.asList(1L, 2L, 3L), result);
}
@Test
public void testValidateAndNormalizeAffinityGroupUuidsWithSpaces() {
public void testValidateAndGetAffinityGroupIdsWithSpaces() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
String result = kubernetesServiceHelper.validateAndNormalizeAffinityGroupUuids(" uuid1 , uuid2 ");
Assert.assertEquals("uuid1,uuid2", result);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds(" uuid1 , uuid2 ");
Assert.assertEquals(2, result.size());
Assert.assertEquals(Arrays.asList(1L, 2L), result);
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAndNormalizeAffinityGroupUuidsOneInvalid() {
public void testValidateAndGetAffinityGroupIdsOneInvalid() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("invalid-uuid")).thenReturn(null);
kubernetesServiceHelper.validateAndNormalizeAffinityGroupUuids("uuid1,invalid-uuid");
kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1,invalid-uuid");
}
@Test
public void testAddNodeTypeAffinityGroupEntry() {
Map<String, String> mapping = new HashMap<>();
kubernetesServiceHelper.addNodeTypeAffinityGroupEntry("control", "uuid1,uuid2", mapping);
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.addNodeTypeAffinityGroupEntry("control", Arrays.asList(1L, 2L), mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals("uuid1,uuid2", mapping.get("CONTROL"));
Assert.assertEquals(Arrays.asList(1L, 2L), mapping.get("CONTROL"));
}
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidEmptyEntry() {
Map<String, String> mapping = new HashMap<>();
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(new HashMap<>(), mapping);
Assert.assertTrue(mapping.isEmpty());
}
@ -295,22 +308,25 @@ public class KubernetesServiceHelperImplTest {
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidValidEntry() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("affinity-group-uuid")).thenReturn(affinityGroup);
Map<String, String> entry = new HashMap<>();
entry.put(VmDetailConstants.CKS_NODE_TYPE, "control");
entry.put(VmDetailConstants.AFFINITY_GROUP, "affinity-group-uuid");
Map<String, String> mapping = new HashMap<>();
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(entry, mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals("affinity-group-uuid", mapping.get("CONTROL"));
Assert.assertEquals(Arrays.asList(100L), mapping.get("CONTROL"));
}
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidMultipleUuids() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
@ -318,15 +334,15 @@ public class KubernetesServiceHelperImplTest {
entry.put(VmDetailConstants.CKS_NODE_TYPE, "worker");
entry.put(VmDetailConstants.AFFINITY_GROUP, "uuid1,uuid2");
Map<String, String> mapping = new HashMap<>();
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(entry, mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals("uuid1,uuid2", mapping.get("WORKER"));
Assert.assertEquals(Arrays.asList(1L, 2L), mapping.get("WORKER"));
}
@Test
public void testGetAffinityGroupNodeTypeMapEmptyMap() {
Map<String, String> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(null);
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(null);
Assert.assertTrue(result.isEmpty());
result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(new HashMap<>());
@ -336,9 +352,11 @@ public class KubernetesServiceHelperImplTest {
@Test
public void testGetAffinityGroupNodeTypeMapValidEntries() {
AffinityGroupVO controlAffinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(controlAffinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("control-affinity-uuid")).thenReturn(controlAffinityGroup);
AffinityGroupVO workerAffinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(workerAffinityGroup.getId()).thenReturn(200L);
Mockito.when(affinityGroupDao.findByUuid("worker-affinity-uuid")).thenReturn(workerAffinityGroup);
Map<String, Map<String, String>> affinityGroupNodeTypeMap = new HashMap<>();
@ -353,17 +371,20 @@ public class KubernetesServiceHelperImplTest {
workerEntry.put(VmDetailConstants.AFFINITY_GROUP, "worker-affinity-uuid");
affinityGroupNodeTypeMap.put("1", workerEntry);
Map<String, String> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Assert.assertEquals(2, result.size());
Assert.assertEquals("control-affinity-uuid", result.get("CONTROL"));
Assert.assertEquals("worker-affinity-uuid", result.get("WORKER"));
Assert.assertEquals(Arrays.asList(100L), result.get("CONTROL"));
Assert.assertEquals(Arrays.asList(200L), result.get("WORKER"));
}
@Test
public void testGetAffinityGroupNodeTypeMapMultipleUuidsPerNodeType() {
public void testGetAffinityGroupNodeTypeMapMultipleIdsPerNodeType() {
AffinityGroupVO ag1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO ag2 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO ag3 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag1.getId()).thenReturn(1L);
Mockito.when(ag2.getId()).thenReturn(2L);
Mockito.when(ag3.getId()).thenReturn(3L);
Mockito.when(affinityGroupDao.findByUuid("ag1")).thenReturn(ag1);
Mockito.when(affinityGroupDao.findByUuid("ag2")).thenReturn(ag2);
Mockito.when(affinityGroupDao.findByUuid("ag3")).thenReturn(ag3);
@ -375,8 +396,8 @@ public class KubernetesServiceHelperImplTest {
controlEntry.put(VmDetailConstants.AFFINITY_GROUP, "ag1,ag2,ag3");
affinityGroupNodeTypeMap.put("0", controlEntry);
Map<String, String> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Assert.assertEquals(1, result.size());
Assert.assertEquals("ag1,ag2,ag3", result.get("CONTROL"));
Assert.assertEquals(Arrays.asList(1L, 2L, 3L), result.get("CONTROL"));
}
}