Skip to content

Commit d04d60b

Browse files
authored
[VMWare] Limit IOPS in Compute/Disk Offerings (#6386)
1 parent 38f3027 commit d04d60b

File tree

9 files changed

+155
-12
lines changed

9 files changed

+155
-12
lines changed

plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.cloud.agent.api.PatchSystemVmCommand;
5252
import com.cloud.resource.ServerResourceBase;
5353
import com.cloud.utils.FileUtil;
54+
import com.cloud.utils.LogUtils;
5455
import com.cloud.utils.validation.ChecksumUtil;
5556
import org.apache.cloudstack.api.ApiConstants;
5657
import org.apache.cloudstack.storage.command.CopyCommand;
@@ -2431,7 +2432,9 @@ protected StartAnswer execute(StartCommand cmd) {
24312432
scsiUnitNumber++;
24322433
}
24332434

2434-
VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, controllerKey, diskChain, volumeDsDetails.first(), deviceNumber, i + 1);
2435+
Long maxIops = volumeTO.getIopsWriteRate() + volumeTO.getIopsReadRate();
2436+
VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, controllerKey, diskChain, volumeDsDetails.first(), deviceNumber, i + 1, maxIops);
2437+
s_logger.debug(LogUtils.logGsonWithoutException("The following definitions will be used to start the VM: virtual device [%s], volume [%s].", device, volumeTO));
24352438

24362439
diskStoragePolicyId = volumeTO.getvSphereStoragePolicyId();
24372440
if (StringUtils.isNotEmpty(diskStoragePolicyId)) {

plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
import org.apache.cloudstack.storage.command.ResignatureCommand;
5151
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
5252
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
53-
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
5453
import org.apache.cloudstack.storage.command.SyncVolumePathAnswer;
54+
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
5555
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
5656
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
5757
import org.apache.cloudstack.storage.to.TemplateObjectTO;
@@ -97,6 +97,7 @@
9797
import com.cloud.storage.Volume;
9898
import com.cloud.storage.template.OVAProcessor;
9999
import com.cloud.template.TemplateManager;
100+
import com.cloud.utils.LogUtils;
100101
import com.cloud.utils.Pair;
101102
import com.cloud.utils.Ternary;
102103
import com.cloud.utils.exception.CloudRuntimeException;
@@ -2128,7 +2129,7 @@ private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean
21282129
diskController = vmMo.getRecommendedDiskController(null);
21292130
}
21302131

2131-
vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController, storagePolicyId);
2132+
vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController, storagePolicyId, volumeTO.getIopsReadRate() + volumeTO.getIopsWriteRate());
21322133
VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder();
21332134
VirtualMachineDiskInfo diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumePath, dsMo.getName());
21342135
chainInfo = _gson.toJson(diskInfo);
@@ -2409,7 +2410,7 @@ public Answer dettachVolume(DettachCommand cmd) {
24092410

24102411
@Override
24112412
public Answer createVolume(CreateObjectCommand cmd) {
2412-
2413+
s_logger.debug(LogUtils.logGsonWithoutException("Executing CreateObjectCommand cmd: [%s].", cmd));
24132414
VolumeObjectTO volume = (VolumeObjectTO)cmd.getData();
24142415
DataStoreTO primaryStore = volume.getDataStore();
24152416
String vSphereStoragePolicyId = volume.getvSphereStoragePolicyId();

utils/src/main/java/com/cloud/utils/LogUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,22 @@
2020
package com.cloud.utils;
2121

2222
import java.io.File;
23+
import java.util.ArrayList;
2324
import java.util.Enumeration;
2425
import java.util.HashSet;
26+
import java.util.List;
2527
import java.util.Set;
2628

2729
import org.apache.log4j.Appender;
2830
import org.apache.log4j.FileAppender;
2931
import org.apache.log4j.Logger;
3032
import org.apache.log4j.xml.DOMConfigurator;
3133

34+
import com.google.gson.Gson;
35+
3236
public class LogUtils {
3337
public static final Logger LOGGER = Logger.getLogger(LogUtils.class);
38+
private static final Gson GSON = new Gson();
3439

3540
private static String configFileLocation = null;
3641

@@ -72,4 +77,23 @@ public static Set<String> getLogFileNames() {
7277
}
7378
return fileNames;
7479
}
80+
81+
public static String logGsonWithoutException(String formatMessage, Object ... objects) {
82+
List<String> gsons = new ArrayList<>();
83+
for (Object object : objects) {
84+
try {
85+
gsons.add(GSON.toJson(object));
86+
} catch (Exception e) {
87+
LOGGER.debug(String.format("Failed to log object [%s] using GSON.", object != null ? object.getClass().getSimpleName() : "null"));
88+
gsons.add("error to decode");
89+
}
90+
}
91+
try {
92+
return String.format(formatMessage, gsons.toArray());
93+
} catch (Exception e) {
94+
String errorMsg = String.format("Failed to log objects using GSON due to: [%s].", e.getMessage());
95+
LOGGER.error(errorMsg, e);
96+
return errorMsg;
97+
}
98+
}
7599
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.utils;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import org.junit.Test;
22+
23+
public class LogUtilsTest {
24+
25+
@Test
26+
public void logGsonWithoutExceptionTestLogCorrectlyPrimitives() {
27+
String expected = "test primitives: int [1], double [1.11], float [1.2222], boolean [true], null [], char [\"c\"].";
28+
String log = LogUtils.logGsonWithoutException("test primitives: int [%s], double [%s], float [%s], boolean [%s], null [%s], char [%s].",
29+
1, 1.11d, 1.2222f, true, null, 'c');
30+
assertEquals(expected, log);
31+
}
32+
33+
@Test
34+
public void logGsonWithoutExceptionTestPassWrongNumberOfArgs() {
35+
String expected = "Failed to log objects using GSON due to: [Format specifier '%s'].";
36+
String result = LogUtils.logGsonWithoutException("teste wrong [%s] %s args.", "blablabla");
37+
assertEquals(expected, result);
38+
}
39+
}

vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import com.vmware.vim25.VirtualNicManagerNetConfig;
7070
import com.cloud.hypervisor.vmware.util.VmwareContext;
7171
import com.cloud.hypervisor.vmware.util.VmwareHelper;
72+
import com.cloud.utils.LogUtils;
7273
import com.cloud.utils.Pair;
7374

7475
public class HostMO extends BaseMO implements VmwareHypervisorHost {
@@ -643,6 +644,7 @@ public VirtualMachineMO findVmOnPeerHyperHost(String name) throws Exception {
643644
@Override
644645
public boolean createVm(VirtualMachineConfigSpec vmSpec) throws Exception {
645646
assert (vmSpec != null);
647+
s_logger.debug(LogUtils.logGsonWithoutException("Creating VM with configuration: [%s].", vmSpec));
646648
DatacenterMO dcMo = new DatacenterMO(_context, getHyperHostDatacenter());
647649
ManagedObjectReference morPool = getHyperHostOwnerResourcePool();
648650

vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.cloud.offering.NetworkOffering;
6161
import com.cloud.storage.Storage.StoragePoolType;
6262
import com.cloud.utils.ActionDelegate;
63+
import com.cloud.utils.LogUtils;
6364
import com.cloud.utils.NumbersUtil;
6465
import com.cloud.utils.Pair;
6566
import com.cloud.utils.cisco.n1kv.vsm.NetconfHelper;
@@ -1622,6 +1623,7 @@ public static boolean createBlankVm(VmwareHypervisorHost host, String vmName, St
16221623
DatacenterMO dataCenterMo = new DatacenterMO(host.getContext(), host.getHyperHostDatacenter());
16231624
setVMHardwareVersion(vmConfig, clusterMo, dataCenterMo);
16241625

1626+
s_logger.debug(LogUtils.logGsonWithoutException("Creating blank VM with configuration [%s].", vmConfig));
16251627
if (host.createVm(vmConfig)) {
16261628
// Here, when attempting to find the VM, we need to use the name
16271629
// with which we created it. This is the only such place where
@@ -2128,7 +2130,7 @@ public static void createOvfFile(VmwareHypervisorHost host, String diskFileName,
21282130
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
21292131

21302132
// Reconfigure worker VM with datadisk
2131-
VirtualDevice device = VmwareHelper.prepareDiskDevice(workerVmMo, null, -1, disks, morDs, -1, 1);
2133+
VirtualDevice device = VmwareHelper.prepareDiskDevice(workerVmMo, null, -1, disks, morDs, -1, 1, null);
21322134
deviceConfigSpec.setDevice(device);
21332135
deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
21342136
vmConfigSpec.getDeviceChange().add(deviceConfigSpec);

vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.cloud.hypervisor.vmware.util.VmwareContext;
5353
import com.cloud.hypervisor.vmware.util.VmwareHelper;
5454
import com.cloud.utils.ActionDelegate;
55+
import com.cloud.utils.LogUtils;
5556
import com.cloud.utils.Pair;
5657
import com.cloud.utils.Ternary;
5758
import com.cloud.utils.concurrency.NamedThreadFactory;
@@ -1395,7 +1396,10 @@ public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference m
13951396
}
13961397

13971398
public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId) throws Exception {
1399+
attachDisk(vmdkDatastorePathChain, morDs, diskController, vSphereStoragePolicyId, null);
1400+
}
13981401

1402+
public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId, Long maxIops) throws Exception {
13991403
if(s_logger.isTraceEnabled())
14001404
s_logger.trace("vCenter API trace - attachDisk(). target MOR: " + _mor.getValue() + ", vmdkDatastorePath: "
14011405
+ GSON.toJson(vmdkDatastorePathChain) + ", datastore: " + morDs.getValue());
@@ -1425,7 +1429,7 @@ public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference m
14251429
}
14261430

14271431
synchronized (_mor.getValue().intern()) {
1428-
VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, controllerKey, vmdkDatastorePathChain, morDs, unitNumber, 1);
1432+
VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, controllerKey, vmdkDatastorePathChain, morDs, unitNumber, 1, maxIops);
14291433
if (StringUtils.isNotBlank(diskController)) {
14301434
String vmdkFileName = vmdkDatastorePathChain[0];
14311435
updateVmdkAdapter(vmdkFileName, diskController);
@@ -2086,7 +2090,7 @@ public VirtualMachineMO cloneFromDiskChain(String clonedVmName, int cpuSpeedMHz,
20862090
VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
20872091
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
20882092

2089-
VirtualDevice device = VmwareHelper.prepareDiskDevice(clonedVmMo, null, -1, disks, morDs, -1, 1);
2093+
VirtualDevice device = VmwareHelper.prepareDiskDevice(clonedVmMo, null, -1, disks, morDs, -1, 1, null);
20902094

20912095
deviceConfigSpec.setDevice(device);
20922096
deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
@@ -2120,6 +2124,7 @@ public GuestOsDescriptor getGuestOsDescriptor(String guestOsId) throws Exception
21202124
}
21212125

21222126
public void plugDevice(VirtualDevice device) throws Exception {
2127+
s_logger.debug(LogUtils.logGsonWithoutException("Pluging device [%s] to VM [%s].", device, getVmName()));
21232128
VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
21242129
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
21252130
deviceConfigSpec.setDevice(device);

vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.cloud.hypervisor.vmware.mo.VirtualEthernetCardType;
4949
import com.cloud.hypervisor.vmware.mo.VirtualMachineMO;
5050
import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost;
51+
import com.cloud.utils.LogUtils;
5152
import com.cloud.utils.Pair;
5253
import com.cloud.utils.Ternary;
5354
import com.cloud.utils.exception.ExceptionUtil;
@@ -61,6 +62,7 @@
6162
import com.vmware.vim25.PerfCounterInfo;
6263
import com.vmware.vim25.PerfMetricId;
6364
import com.vmware.vim25.ResourceAllocationInfo;
65+
import com.vmware.vim25.StorageIOAllocationInfo;
6466
import com.vmware.vim25.VirtualCdrom;
6567
import com.vmware.vim25.VirtualCdromIsoBackingInfo;
6668
import com.vmware.vim25.VirtualCdromRemotePassthroughBackingInfo;
@@ -87,7 +89,6 @@
8789
import com.vmware.vim25.VirtualVmxnet3;
8890

8991
public class VmwareHelper {
90-
@SuppressWarnings("unused")
9192
private static final Logger s_logger = Logger.getLogger(VmwareHelper.class);
9293

9394
public static final int MAX_SCSI_CONTROLLER_COUNT = 4;
@@ -141,7 +142,6 @@ private static VirtualEthernetCard createVirtualEthernetCard(VirtualEthernetCard
141142

142143
public static VirtualDevice prepareNicOpaque(VirtualMachineMO vmMo, VirtualEthernetCardType deviceType, String portGroupName,
143144
String macAddress, int contextNumber, boolean connected, boolean connectOnStart) throws Exception {
144-
145145
assert(vmMo.getRunningHost().hasOpaqueNSXNetwork());
146146

147147
VirtualEthernetCard nic = createVirtualEthernetCard(deviceType);
@@ -215,8 +215,10 @@ public static VirtualDevice prepareDvNicDevice(VirtualMachineMO vmMo, ManagedObj
215215

216216
// vmdkDatastorePath: [datastore name] vmdkFilePath
217217
public static VirtualDevice prepareDiskDevice(VirtualMachineMO vmMo, VirtualDisk device, int controllerKey, String vmdkDatastorePathChain[],
218-
ManagedObjectReference morDs, int deviceNumber, int contextNumber) throws Exception {
219-
218+
ManagedObjectReference morDs, int deviceNumber, int contextNumber, Long maxIops) throws Exception {
219+
s_logger.debug(LogUtils.logGsonWithoutException("Trying to prepare disk device to virtual machine [%s], using the following details: Virtual device [%s], "
220+
+ "ManagedObjectReference [%s], ControllerKey [%s], VMDK path chain [%s], DeviceNumber [%s], ContextNumber [%s] and max IOPS [%s].",
221+
vmMo, device, morDs, controllerKey, vmdkDatastorePathChain, deviceNumber, contextNumber, maxIops));
220222
assert (vmdkDatastorePathChain != null);
221223
assert (vmdkDatastorePathChain.length >= 1);
222224

@@ -243,6 +245,13 @@ public static VirtualDevice prepareDiskDevice(VirtualMachineMO vmMo, VirtualDisk
243245
disk.setKey(-contextNumber);
244246
disk.setUnitNumber(deviceNumber);
245247

248+
if (maxIops != null && maxIops > 0) {
249+
s_logger.debug(LogUtils.logGsonWithoutException("Defining [%s] as the max IOPS of disk [%s].", maxIops, disk));
250+
StorageIOAllocationInfo storageIOAllocationInfo = new StorageIOAllocationInfo();
251+
storageIOAllocationInfo.setLimit(maxIops);
252+
disk.setStorageIOAllocation(storageIOAllocationInfo);
253+
}
254+
246255
VirtualDeviceConnectInfo connectInfo = new VirtualDeviceConnectInfo();
247256
connectInfo.setConnected(true);
248257
connectInfo.setStartConnected(true);
@@ -255,6 +264,9 @@ public static VirtualDevice prepareDiskDevice(VirtualMachineMO vmMo, VirtualDisk
255264
setParentBackingInfo(backingInfo, morDs, parentDisks);
256265
}
257266

267+
s_logger.debug(LogUtils.logGsonWithoutException("Prepared disk device, to attach to virtual machine [%s], has the following details: Virtual device [%s], "
268+
+ "ManagedObjectReference [%s], ControllerKey [%s], VMDK path chain [%s], DeviceNumber [%s], ContextNumber [%s] and max IOPS [%s], is: [%s].",
269+
vmMo, device, morDs, controllerKey, vmdkDatastorePathChain, deviceNumber, contextNumber, maxIops, disk));
258270
return disk;
259271
}
260272

@@ -750,5 +762,4 @@ public static HostMO getHostMOFromHostName(final VmwareContext context, final St
750762
}
751763
return host;
752764
}
753-
754765
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package com.cloud.hypervisor.vmware.util;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.mockito.Mock;
25+
import org.mockito.Mockito;
26+
import org.mockito.junit.MockitoJUnitRunner;
27+
28+
import com.cloud.hypervisor.vmware.mo.VirtualMachineMO;
29+
import com.vmware.vim25.VirtualDisk;
30+
31+
@RunWith(MockitoJUnitRunner.class)
32+
public class VmwareHelperTest {
33+
@Mock
34+
private VirtualMachineMO virtualMachineMO;
35+
36+
@Test
37+
public void prepareDiskDeviceTestNotLimitingIOPS() throws Exception {
38+
Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
39+
VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, null);
40+
assertEquals(null, virtualDisk.getStorageIOAllocation());
41+
}
42+
43+
@Test
44+
public void prepareDiskDeviceTestLimitingIOPS() throws Exception {
45+
Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
46+
VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, Long.valueOf(1000));
47+
assertEquals(Long.valueOf(1000), virtualDisk.getStorageIOAllocation().getLimit());
48+
}
49+
50+
@Test
51+
public void prepareDiskDeviceTestLimitingIOPSToZero() throws Exception {
52+
Mockito.when(virtualMachineMO.getIDEDeviceControllerKey()).thenReturn(1);
53+
VirtualDisk virtualDisk = (VirtualDisk) VmwareHelper.prepareDiskDevice(virtualMachineMO, null, -1, new String[1], null, 0, 0, Long.valueOf(0));
54+
assertEquals(null, virtualDisk.getStorageIOAllocation());
55+
}
56+
}

0 commit comments

Comments
 (0)