Skip to content

Commit 27124c1

Browse files
Marcus SorensenMarcus Sorensensureshanaparti
authored
Add ability to set cpu.threadspercore similar to existing cpu.corespersocket (#8850)
* Add ability to set cpu.threadspercore similar to existing cpu.corespersocket * add cpu.threadspercore to VM and template detail options * Update plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com> * add vm detail for KVM --------- Co-authored-by: Marcus Sorensen <mls@apple.com> Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
1 parent 0af923e commit 27124c1

File tree

8 files changed

+155
-19
lines changed

8 files changed

+155
-19
lines changed

api/src/main/java/com/cloud/vm/VmDetailConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
public interface VmDetailConstants {
2020
String KEYBOARD = "keyboard";
2121
String CPU_CORE_PER_SOCKET = "cpu.corespersocket";
22+
String CPU_THREAD_PER_CORE = "cpu.threadspercore";
2223
String ROOT_DISK_SIZE = "rootdisksize";
2324
String BOOT_MODE = "boot.mode";
2425
String NAME_ON_HYPERVISOR= "nameonhypervisor";

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5149,31 +5149,48 @@ public boolean isSecureMode(String bootMode) {
51495149
return false;
51505150
}
51515151

5152-
private void setCpuTopology(CpuModeDef cmd, int vCpusInDef, Map<String, String> details) {
5152+
protected void setCpuTopology(CpuModeDef cmd, int vCpusInDef, Map<String, String> details) {
51535153
if (!enableManuallySettingCpuTopologyOnKvmVm) {
51545154
LOGGER.debug(String.format("Skipping manually setting CPU topology on VM's XML due to it is disabled in agent.properties {\"property\": \"%s\", \"value\": %s}.",
51555155
AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM.getName(), enableManuallySettingCpuTopologyOnKvmVm));
51565156
return;
51575157
}
5158-
// multi cores per socket, for larger core configs
5159-
int numCoresPerSocket = -1;
5158+
5159+
int numCoresPerSocket = 1;
5160+
int numThreadsPerCore = 1;
5161+
51605162
if (details != null) {
5161-
final String coresPerSocket = details.get(VmDetailConstants.CPU_CORE_PER_SOCKET);
5162-
final int intCoresPerSocket = NumbersUtil.parseInt(coresPerSocket, numCoresPerSocket);
5163-
if (intCoresPerSocket > 0 && vCpusInDef % intCoresPerSocket == 0) {
5164-
numCoresPerSocket = intCoresPerSocket;
5165-
}
5163+
numCoresPerSocket = NumbersUtil.parseInt(details.get(VmDetailConstants.CPU_CORE_PER_SOCKET), 1);
5164+
numThreadsPerCore = NumbersUtil.parseInt(details.get(VmDetailConstants.CPU_THREAD_PER_CORE), 1);
51665165
}
5167-
if (numCoresPerSocket <= 0) {
5166+
5167+
if ((numCoresPerSocket * numThreadsPerCore) > vCpusInDef) {
5168+
LOGGER.warn(String.format("cores per socket (%d) * threads per core (%d) exceeds total VM cores. Ignoring extra topology", numCoresPerSocket, numThreadsPerCore));
5169+
numCoresPerSocket = 1;
5170+
numThreadsPerCore = 1;
5171+
}
5172+
5173+
if (vCpusInDef % (numCoresPerSocket * numThreadsPerCore) != 0) {
5174+
LOGGER.warn(String.format("cores per socket(%d) * threads per core(%d) doesn't divide evenly into total VM cores(%d). Ignoring extra topology", numCoresPerSocket, numThreadsPerCore, vCpusInDef));
5175+
numCoresPerSocket = 1;
5176+
numThreadsPerCore = 1;
5177+
}
5178+
5179+
// Set default coupling (makes 4 or 6 core sockets for larger core configs)
5180+
int numTotalSockets = 1;
5181+
if (numCoresPerSocket == 1 && numThreadsPerCore == 1) {
51685182
if (vCpusInDef % 6 == 0) {
51695183
numCoresPerSocket = 6;
51705184
} else if (vCpusInDef % 4 == 0) {
51715185
numCoresPerSocket = 4;
51725186
}
5187+
numTotalSockets = vCpusInDef / numCoresPerSocket;
5188+
} else {
5189+
int nTotalCores = vCpusInDef / numThreadsPerCore;
5190+
numTotalSockets = nTotalCores / numCoresPerSocket;
51735191
}
5174-
if (numCoresPerSocket > 0) {
5175-
cmd.setTopology(numCoresPerSocket, vCpusInDef / numCoresPerSocket);
5176-
}
5192+
5193+
cmd.setTopology(numCoresPerSocket, numThreadsPerCore, numTotalSockets);
51775194
}
51785195

51795196
public void setBackingFileFormat(String volPath) {

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,8 +545,9 @@ private void extractCpuModeDef(final Element rootElement){
545545
}
546546
final String sockets = getAttrValue("topology", "sockets", cpuModeDefElement);
547547
final String cores = getAttrValue("topology", "cores", cpuModeDefElement);
548-
if (StringUtils.isNotBlank(sockets) && StringUtils.isNotBlank(cores)) {
549-
cpuModeDef.setTopology(Integer.parseInt(cores), Integer.parseInt(sockets));
548+
final String threads = getAttrValue("topology", "threads", cpuModeDefElement);
549+
if (StringUtils.isNotBlank(sockets) && StringUtils.isNotBlank(cores) && StringUtils.isNotBlank(threads)) {
550+
cpuModeDef.setTopology(Integer.parseInt(cores), Integer.parseInt(threads), Integer.parseInt(sockets));
550551
}
551552
}
552553
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,6 +1754,7 @@ public static class CpuModeDef {
17541754
private String _model;
17551755
private List<String> _features;
17561756
private int _coresPerSocket = -1;
1757+
private int _threadsPerCore = -1;
17571758
private int _sockets = -1;
17581759

17591760
public void setMode(String mode) {
@@ -1770,8 +1771,9 @@ public void setModel(String model) {
17701771
_model = model;
17711772
}
17721773

1773-
public void setTopology(int coresPerSocket, int sockets) {
1774+
public void setTopology(int coresPerSocket, int threadsPerCore, int sockets) {
17741775
_coresPerSocket = coresPerSocket;
1776+
_threadsPerCore = threadsPerCore;
17751777
_sockets = sockets;
17761778
}
17771779

@@ -1800,9 +1802,9 @@ public String toString() {
18001802
}
18011803
}
18021804

1803-
// add topology
1804-
if (_sockets > 0 && _coresPerSocket > 0) {
1805-
modeBuilder.append("<topology sockets='" + _sockets + "' cores='" + _coresPerSocket + "' threads='1' />");
1805+
// add topology. Note we require sockets, cores, and threads defined
1806+
if (_sockets > 0 && _coresPerSocket > 0 && _threadsPerCore > 0) {
1807+
modeBuilder.append("<topology sockets='" + _sockets + "' cores='" + _coresPerSocket + "' threads='" + _threadsPerCore + "' />");
18061808
}
18071809

18081810
// close cpu def
@@ -1813,6 +1815,8 @@ public String toString() {
18131815
public int getCoresPerSocket() {
18141816
return _coresPerSocket;
18151817
}
1818+
public int getThreadsPerCore() { return _threadsPerCore; }
1819+
public int getSockets() { return _sockets; }
18161820
}
18171821

18181822
public static class SerialDef {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
package com.cloud.hypervisor.kvm.resource;
20+
21+
import com.cloud.vm.VmDetailConstants;
22+
import org.junit.Assert;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.junit.runners.Parameterized;
26+
import org.mockito.Mockito;
27+
28+
import java.util.Arrays;
29+
import java.util.Collection;
30+
import java.util.HashMap;
31+
import java.util.Map;
32+
33+
@RunWith(value = Parameterized.class)
34+
public class LibvirtCpuTopologyTest {
35+
private final LibvirtComputingResource libvirtComputingResource = Mockito.spy(new LibvirtComputingResource());
36+
37+
private final String desc;
38+
private final Integer coresPerSocket;
39+
private final Integer threadsPerCore;
40+
private final Integer totalVmCores;
41+
private final String expectedXml;
42+
43+
public LibvirtCpuTopologyTest(String desc, Integer coresPerSocket, Integer threadsPerCore, Integer totalVmCores, String expectedXml) {
44+
this.desc = desc;
45+
this.coresPerSocket = coresPerSocket;
46+
this.threadsPerCore = threadsPerCore;
47+
this.totalVmCores = totalVmCores;
48+
this.expectedXml = expectedXml;
49+
}
50+
@Parameterized.Parameters
51+
public static Collection<Object[]> data() {
52+
return Arrays.asList(new Object[][] {
53+
createTestData("8 cores, 2 per socket",2, null, 8, "<cpu><topology sockets='4' cores='2' threads='1' /></cpu>"),
54+
createTestData("8 cores, 4 per socket",4, null, 8, "<cpu><topology sockets='2' cores='4' threads='1' /></cpu>"),
55+
createTestData("8 cores, nothing specified",null, null, 8, "<cpu><topology sockets='2' cores='4' threads='1' /></cpu>"),
56+
createTestData("12 cores, nothing specified",null, null, 12, "<cpu><topology sockets='2' cores='6' threads='1' /></cpu>"),
57+
createTestData("8 cores, 2C per socket, 2TPC",2, 2, 8, "<cpu><topology sockets='2' cores='2' threads='2' /></cpu>"),
58+
createTestData("8 cores, 1C per socket, 2TPC",1, 2, 8, "<cpu><topology sockets='4' cores='1' threads='2' /></cpu>"),
59+
createTestData("8 cores, default CPS, 2TPC",null, 2, 8, "<cpu><topology sockets='4' cores='1' threads='2' /></cpu>"),
60+
createTestData("6 cores, default CPS, 2TPC",null, 2, 6, "<cpu><topology sockets='3' cores='1' threads='2' /></cpu>"),
61+
createTestData("12 cores, 2CPS, 2TPC",2, 2, 12, "<cpu><topology sockets='3' cores='2' threads='2' /></cpu>"),
62+
createTestData("6 cores, misconfigured cores, CPS, TPC, use default topology",2, 2, 6, "<cpu><topology sockets='1' cores='6' threads='1' /></cpu>"),
63+
createTestData("odd cores, nothing specified use default topology",null, null, 3, "<cpu><topology sockets='3' cores='1' threads='1' /></cpu>"),
64+
createTestData("odd cores, uneven CPS use default topology",2, null, 3, "<cpu><topology sockets='3' cores='1' threads='1' /></cpu>"),
65+
createTestData("8 cores, 2 CPS, odd threads use default topology", 2, 3, 8, "<cpu><topology sockets='2' cores='4' threads='1' /></cpu>"),
66+
createTestData("1 core, 2 CPS, odd threads use default topology", 2, 1, 1, "<cpu><topology sockets='1' cores='1' threads='1' /></cpu>")
67+
});
68+
}
69+
70+
private static Object[] createTestData(String desc, Integer coresPerSocket, Integer threadsPerCore, int totalVmCores, String expected) {
71+
return new Object[] {desc, coresPerSocket, threadsPerCore, totalVmCores, expected};
72+
}
73+
74+
@Test
75+
public void topologyTest() {
76+
LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
77+
Map<String, String> details = new HashMap<>();
78+
79+
if (coresPerSocket != null) {
80+
details.put(VmDetailConstants.CPU_CORE_PER_SOCKET, coresPerSocket.toString());
81+
}
82+
83+
if (threadsPerCore != null) {
84+
details.put(VmDetailConstants.CPU_THREAD_PER_CORE, threadsPerCore.toString());
85+
}
86+
87+
if (coresPerSocket == null && threadsPerCore == null) {
88+
details = null;
89+
}
90+
91+
libvirtComputingResource.setCpuTopology(cpuModeDef, totalVmCores, details);
92+
Assert.assertEquals(desc, expectedXml, cpuModeDef.toString());
93+
}
94+
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import junit.framework.TestCase;
3333
import org.apache.cloudstack.utils.qemu.QemuObject;
34+
import org.junit.Assert;
3435
import org.junit.Test;
3536
import org.junit.runner.RunWith;
3637
import org.mockito.junit.MockitoJUnitRunner;
@@ -267,7 +268,7 @@ public void testDomainXMLParserWithoutModelName() {
267268
" <uuid>aafaaabc-8657-4efc-9c52-3422d4e04088</uuid>\n" +
268269
" <memory unit='KiB'>2097152</memory>\n" +
269270
" <currentMemory unit='KiB'>2097152</currentMemory>\n" +
270-
" <vcpu placement='static'>2</vcpu>\n" +
271+
" <vcpu placement='static'>8</vcpu>\n" +
271272
" <os>\n" +
272273
" <type arch='x86_64' machine='pc-i440fx-rhel7.0.0'>hvm</type>\n" +
273274
" <boot dev='hd'/>\n" +
@@ -278,6 +279,7 @@ public void testDomainXMLParserWithoutModelName() {
278279
" </features>\n" +
279280
" <cpu mode='host-model' check='partial'>\n" +
280281
" <model fallback='allow'/>\n" +
282+
" <topology sockets='1' cores='4' threads='2' />" +
281283
" </cpu>\n" +
282284
" <clock offset='utc'>\n" +
283285
" <timer name='rtc' tickpolicy='catchup'/>\n" +
@@ -380,5 +382,8 @@ public void testDomainXMLParserWithoutModelName() {
380382
System.out.println("Got exception " + e.getMessage());
381383
throw e;
382384
}
385+
Assert.assertEquals("CPU socket count is parsed", 1, libvirtDomainXMLParser.getCpuModeDef().getSockets());
386+
Assert.assertEquals("CPU cores count is parsed", 4, libvirtDomainXMLParser.getCpuModeDef().getCoresPerSocket());
387+
Assert.assertEquals("CPU threads count is parsed", 2, libvirtDomainXMLParser.getCpuModeDef().getThreadsPerCore());
383388
}
384389
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,4 +547,17 @@ public void testSCSIDef() {
547547
"</controller>\n";
548548
assertEquals(expected, str);
549549
}
550+
551+
@Test
552+
public void testTopology() {
553+
LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
554+
cpuModeDef.setTopology(2, 1, 4);
555+
assertEquals("<cpu><topology sockets='4' cores='2' threads='1' /></cpu>", cpuModeDef.toString());
556+
}
557+
558+
public void testTopologyNoInfo() {
559+
LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
560+
cpuModeDef.setTopology(-1, -1, 4);
561+
assertEquals("<cpu></cpu>", cpuModeDef.toString());
562+
}
550563
}

server/src/main/java/com/cloud/api/query/QueryManagerImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4910,6 +4910,7 @@ private void fillVMOrTemplateDetailOptions(final Map<String, List<String>> optio
49104910
options.put(VmDetailConstants.ROOT_DISK_SIZE, Collections.emptyList());
49114911

49124912
if (HypervisorType.KVM.equals(hypervisorType)) {
4913+
options.put(VmDetailConstants.CPU_THREAD_PER_CORE, Collections.emptyList());
49134914
options.put(VmDetailConstants.NIC_ADAPTER, Arrays.asList("e1000", "virtio", "rtl8139", "vmxnet3", "ne2k_pci"));
49144915
options.put(VmDetailConstants.ROOT_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "virtio"));
49154916
options.put(VmDetailConstants.VIDEO_HARDWARE, Arrays.asList("cirrus", "vga", "qxl", "virtio"));

0 commit comments

Comments
 (0)