Skip to content

Commit c5e1ea2

Browse files
authored
Merge branch 'main' into integrate-gpu
2 parents 7251193 + 4aed972 commit c5e1ea2

File tree

20 files changed

+300
-29
lines changed

20 files changed

+300
-29
lines changed

api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.admin.cluster;
1818

19+
import java.util.Map;
20+
1921
import com.cloud.cpu.CPU;
2022
import org.apache.cloudstack.api.ApiCommandResourceType;
2123

@@ -60,6 +62,12 @@ public class UpdateClusterCmd extends BaseCmd {
6062
since = "4.20")
6163
private String arch;
6264

65+
@Parameter(name = ApiConstants.EXTERNAL_DETAILS,
66+
type = CommandType.MAP,
67+
description = "Details in key/value pairs to be added to the extension-resource mapping. Use the format externaldetails[i].<key>=<value>. Example: externaldetails[0].endpoint.url=https://example.com",
68+
since = "4.21.0")
69+
protected Map externalDetails;
70+
6371
public String getClusterName() {
6472
return clusterName;
6573
}
@@ -122,6 +130,10 @@ public CPU.CPUArch getArch() {
122130
return CPU.CPUArch.fromType(arch);
123131
}
124132

133+
public Map<String, String> getExternalDetails() {
134+
return convertDetailsToMap(externalDetails);
135+
}
136+
125137
@Override
126138
public void execute() {
127139
Cluster cluster = _resourceService.getCluster(getId());

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
import com.cloud.host.Host;
4848
import com.cloud.org.Cluster;
49+
import com.cloud.utils.Pair;
4950
import com.cloud.utils.component.Manager;
5051

5152
public interface ExtensionsManager extends Manager {
@@ -87,4 +88,9 @@ public interface ExtensionsManager extends Manager {
8788
Map<String, Map<String, String>> getExternalAccessDetails(Host host, Map<String, String> vmDetails);
8889

8990
String handleExtensionServerCommands(ExtensionServerActionBaseCommand cmd);
91+
92+
Pair<Boolean, ExtensionResourceMap> extensionResourceMapDetailsNeedUpdate(final long resourceId,
93+
final ExtensionResourceMap.ResourceType resourceType, final Map<String, String> details);
94+
95+
void updateExtensionResourceMapDetails(final long extensionResourceMapId, final Map<String, String> details);
9096
}

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,45 @@ public String handleExtensionServerCommands(ExtensionServerActionBaseCommand com
14781478
return GsonHelper.getGson().toJson(answers);
14791479
}
14801480

1481+
@Override
1482+
public Pair<Boolean, ExtensionResourceMap> extensionResourceMapDetailsNeedUpdate(long resourceId,
1483+
ExtensionResourceMap.ResourceType resourceType, Map<String, String> externalDetails) {
1484+
if (MapUtils.isEmpty(externalDetails)) {
1485+
return new Pair<>(false, null);
1486+
}
1487+
ExtensionResourceMapVO extensionResourceMapVO =
1488+
extensionResourceMapDao.findByResourceIdAndType(resourceId, resourceType);
1489+
if (extensionResourceMapVO == null) {
1490+
return new Pair<>(true, null);
1491+
}
1492+
Map<String, String> mapDetails =
1493+
extensionResourceMapDetailsDao.listDetailsKeyPairs(extensionResourceMapVO.getId());
1494+
if (MapUtils.isEmpty(mapDetails) || mapDetails.size() != externalDetails.size()) {
1495+
return new Pair<>(true, extensionResourceMapVO);
1496+
}
1497+
for (Map.Entry<String, String> entry : externalDetails.entrySet()) {
1498+
String key = entry.getKey();
1499+
String value = entry.getValue();
1500+
if (!value.equals(mapDetails.get(key))) {
1501+
return new Pair<>(true, extensionResourceMapVO);
1502+
}
1503+
}
1504+
return new Pair<>(false, extensionResourceMapVO);
1505+
}
1506+
1507+
@Override
1508+
public void updateExtensionResourceMapDetails(long extensionResourceMapId, Map<String, String> details) {
1509+
if (MapUtils.isEmpty(details)) {
1510+
return;
1511+
}
1512+
List<ExtensionResourceMapDetailsVO> detailsList = new ArrayList<>();
1513+
for (Map.Entry<String, String> entry : details.entrySet()) {
1514+
detailsList.add(new ExtensionResourceMapDetailsVO(extensionResourceMapId, entry.getKey(),
1515+
entry.getValue()));
1516+
}
1517+
extensionResourceMapDetailsDao.saveDetails(detailsList);
1518+
}
1519+
14811520
@Override
14821521
public Long getExtensionIdForCluster(long clusterId) {
14831522
ExtensionResourceMapVO map = extensionResourceMapDao.findByResourceIdAndType(clusterId,

framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,6 +1742,82 @@ public void handleExtensionServerCommands_UnsupportedCommand_ReturnsUnsupportedA
17421742
assertTrue(json.contains("\"result\":false"));
17431743
}
17441744

1745+
@Test
1746+
public void extensionResourceMapDetailsNeedUpdateReturnsTrueWhenNoResourceMapExists() {
1747+
when(extensionResourceMapDao.findByResourceIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(null);
1748+
Map<String, String> externalDetails = Map.of("key", "value");
1749+
Pair<Boolean, ExtensionResourceMap> result = extensionsManager.extensionResourceMapDetailsNeedUpdate(1L,
1750+
ExtensionResourceMap.ResourceType.Cluster, externalDetails);
1751+
assertTrue(result.first());
1752+
assertNull(result.second());
1753+
}
1754+
1755+
@Test
1756+
public void extensionResourceMapDetailsNeedUpdateReturnsFalseWhenDetailsMatch() {
1757+
ExtensionResourceMapVO resourceMap = mock(ExtensionResourceMapVO.class);
1758+
when(extensionResourceMapDao.findByResourceIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(resourceMap);
1759+
when(extensionResourceMapDetailsDao.listDetailsKeyPairs(resourceMap.getId())).thenReturn(Map.of("key", "value"));
1760+
Map<String, String> externalDetails = Map.of("key", "value");
1761+
Pair<Boolean, ExtensionResourceMap> result = extensionsManager.extensionResourceMapDetailsNeedUpdate(1L,
1762+
ExtensionResourceMap.ResourceType.Cluster, externalDetails);
1763+
assertFalse(result.first());
1764+
assertEquals(resourceMap, result.second());
1765+
}
1766+
1767+
@Test
1768+
public void extensionResourceMapDetailsNeedUpdateReturnsTrueWhenDetailsDiffer() {
1769+
ExtensionResourceMapVO resourceMap = mock(ExtensionResourceMapVO.class);
1770+
when(extensionResourceMapDao.findByResourceIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(resourceMap);
1771+
when(extensionResourceMapDetailsDao.listDetailsKeyPairs(resourceMap.getId())).thenReturn(Map.of("key", "oldValue"));
1772+
Map<String, String> externalDetails = Map.of("key", "newValue");
1773+
Pair<Boolean, ExtensionResourceMap> result = extensionsManager.extensionResourceMapDetailsNeedUpdate(1L,
1774+
ExtensionResourceMap.ResourceType.Cluster, externalDetails);
1775+
assertTrue(result.first());
1776+
assertEquals(resourceMap, result.second());
1777+
}
1778+
1779+
@Test
1780+
public void extensionResourceMapDetailsNeedUpdateReturnsTrueWhenExternalDetailsHaveExtraKeys() {
1781+
ExtensionResourceMapVO resourceMap = mock(ExtensionResourceMapVO.class);
1782+
when(extensionResourceMapDao.findByResourceIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(resourceMap);
1783+
when(extensionResourceMapDetailsDao.listDetailsKeyPairs(resourceMap.getId())).thenReturn(Map.of("key", "value"));
1784+
Map<String, String> externalDetails = Map.of("key", "value", "extra", "something");
1785+
Pair<Boolean, ExtensionResourceMap> result = extensionsManager.extensionResourceMapDetailsNeedUpdate(1L,
1786+
ExtensionResourceMap.ResourceType.Cluster, externalDetails);
1787+
assertTrue(result.first());
1788+
assertEquals(resourceMap, result.second());
1789+
}
1790+
1791+
@Test
1792+
public void updateExtensionResourceMapDetails_SavesDetails_WhenDetailsProvided() {
1793+
long resourceMapId = 100L;
1794+
Map<String, String> details = Map.of("foo", "bar", "baz", "qux");
1795+
extensionsManager.updateExtensionResourceMapDetails(resourceMapId, details);
1796+
verify(extensionResourceMapDetailsDao).saveDetails(any());
1797+
}
1798+
1799+
@Test
1800+
public void updateExtensionResourceMapDetails_RemovesDetails_WhenDetailsIsNull() {
1801+
long resourceMapId = 101L;
1802+
extensionsManager.updateExtensionResourceMapDetails(resourceMapId, null);
1803+
verify(extensionResourceMapDetailsDao, never()).saveDetails(any());
1804+
}
1805+
1806+
@Test
1807+
public void updateExtensionResourceMapDetails_RemovesDetails_WhenDetailsIsEmpty() {
1808+
long resourceMapId = 102L;
1809+
extensionsManager.updateExtensionResourceMapDetails(resourceMapId, Collections.emptyMap());
1810+
verify(extensionResourceMapDetailsDao, never()).saveDetails(any());
1811+
}
1812+
1813+
@Test(expected = CloudRuntimeException.class)
1814+
public void updateExtensionResourceMapDetails_ThrowsException_WhenSaveFails() {
1815+
long resourceMapId = 103L;
1816+
Map<String, String> details = Map.of("foo", "bar");
1817+
doThrow(CloudRuntimeException.class).when(extensionResourceMapDetailsDao).saveDetails(any());
1818+
extensionsManager.updateExtensionResourceMapDetails(resourceMapId, details);
1819+
}
1820+
17451821
@Test
17461822
public void getExtensionIdForCluster_WhenMappingExists_ReturnsExtensionId() {
17471823
long clusterId = 1L;

framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,31 @@
1818
*/
1919
package org.apache.cloudstack.spring.module.factory;
2020

21-
import static org.junit.Assert.assertEquals;
22-
import static org.junit.Assert.assertNotNull;
23-
import static org.junit.Assert.assertNull;
24-
import static org.junit.Assert.assertTrue;
25-
import static org.junit.Assert.fail;
26-
27-
import java.io.IOException;
28-
import java.util.Collection;
29-
21+
import org.apache.cloudstack.spring.module.locator.impl.ClasspathModuleDefinitionLocator;
22+
import org.apache.cloudstack.spring.module.model.ModuleDefinition;
23+
import org.apache.cloudstack.spring.module.model.ModuleDefinitionSet;
3024
import org.junit.Before;
3125
import org.junit.Test;
3226
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3327
import org.springframework.context.ApplicationContext;
28+
import org.springframework.core.io.Resource;
3429

35-
import org.apache.cloudstack.spring.module.locator.impl.ClasspathModuleDefinitionLocator;
36-
import org.apache.cloudstack.spring.module.model.ModuleDefinition;
37-
import org.apache.cloudstack.spring.module.model.ModuleDefinitionSet;
30+
import java.io.IOException;
31+
import java.util.Arrays;
32+
import java.util.Collection;
33+
import java.util.List;
34+
import java.util.stream.Collectors;
35+
36+
import static org.junit.Assert.assertEquals;
37+
import static org.junit.Assert.assertNotNull;
38+
import static org.junit.Assert.assertNull;
39+
import static org.junit.Assert.assertTrue;
40+
import static org.junit.Assert.fail;
3841

3942
public class ModuleBasedContextFactoryTest {
4043

4144
Collection<ModuleDefinition> defs;
45+
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
4246

4347
@Before
4448
public void setUp() throws IOException {
@@ -51,8 +55,6 @@ public void setUp() throws IOException {
5155
@Test
5256
public void testLoad() throws IOException {
5357

54-
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
55-
5658
ModuleDefinitionSet set = factory.loadModules(defs, "base");
5759

5860
assertNotNull(set.getApplicationContext("base"));
@@ -63,8 +65,6 @@ public void testOverride() throws IOException {
6365

6466
InitTest.initted = false;
6567

66-
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
67-
6868
ModuleDefinitionSet set = factory.loadModules(defs, "base");
6969

7070
assertTrue(!InitTest.initted);
@@ -73,7 +73,6 @@ public void testOverride() throws IOException {
7373

7474
@Test
7575
public void testExcluded() throws IOException {
76-
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
7776
ModuleDefinitionSet set = factory.loadModules(defs, "base");
7877

7978
assertNull(set.getApplicationContext("excluded"));
@@ -83,7 +82,6 @@ public void testExcluded() throws IOException {
8382

8483
@Test
8584
public void testBeans() throws IOException {
86-
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
8785
ModuleDefinitionSet set = factory.loadModules(defs, "base");
8886

8987
testBeansInContext(set, "base", 1, new String[] {"base"}, new String[] {"child1", "child2", "child1-1"});
@@ -92,6 +90,51 @@ public void testBeans() throws IOException {
9290
testBeansInContext(set, "child1-1", 3, new String[] {"base", "child1", "child1-1"}, new String[] {"child2"});
9391
}
9492

93+
@Test
94+
public void testEmptyNameConfigResources() throws IOException {
95+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
96+
testConfigResourcesArray(new String[] {}, set.getConfigResources(""));
97+
}
98+
99+
@Test
100+
public void testBaseConfigResources() throws IOException {
101+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
102+
testConfigResourcesArray(new String[] {"base-context.xml", "base-context-inheritable.xml"}, set.getConfigResources("base"));
103+
}
104+
105+
@Test
106+
public void testChild1ConfigResources() throws IOException {
107+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
108+
testConfigResourcesArray(new String[] {
109+
"child1-context.xml", "child1-context-inheritable.xml",
110+
"base-context-inheritable.xml", "child1-context-override.xml"
111+
}, set.getConfigResources("child1"));
112+
}
113+
114+
@Test
115+
public void testChild2ConfigResources() throws IOException {
116+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
117+
testConfigResourcesArray(new String[] {
118+
"child2-context.xml", "base-context-inheritable.xml"
119+
}, set.getConfigResources("child2"));
120+
}
121+
122+
@Test
123+
public void testChild1_1ConfigResources() throws IOException {
124+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
125+
testConfigResourcesArray(new String[] {
126+
"child1-1-context.xml", "child1-context-inheritable.xml", "base-context-inheritable.xml"
127+
}, set.getConfigResources("child1-1"));
128+
}
129+
130+
private void testConfigResourcesArray(String[] expected, Resource[] actual) {
131+
assertEquals(expected.length, actual.length);
132+
List<String> actualFileNameList = Arrays.stream(actual).map(Resource::getFilename).collect(Collectors.toList());
133+
for (int i = 0; i < expected.length; i++) {
134+
assertEquals(expected[i], actualFileNameList.get(i));
135+
}
136+
}
137+
95138
protected void testBeansInContext(ModuleDefinitionSet set, String name, int order, String[] parents, String[] notTheres) {
96139
ApplicationContext context = set.getApplicationContext(name);
97140

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
<beans xmlns="http://www.springframework.org/schema/beans"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://www.springframework.org/schema/beans
22+
http://www.springframework.org/schema/beans/spring-beans.xsd">
23+
24+
</beans>

0 commit comments

Comments
 (0)