server: Fix issue with volume resize on VMWare (deploy as-is templates) (#4829)

This PR fixes the issue pertaining to volume resize on VMWare for deploy as-is templates. VMware deploy as-is templates are those that are deployed as per the specification in the imported OVF. Hence override root disk size will not be adhered to for such templates. Moreover, when we deploy VMs in stopped state and resize the volume, the root disk doesn't get resized but the volume size is merely updated in the DB.
This PR also includes the following (for deploy as-is templates):
- Disables overriding root disk size during VM deployment on the UI
- Disables selection of compute offerings with root disk size specified, at the time of deployment
- Provided users with the option to deploy VM is stopped state via UI (so as to give an option to users to resize the volumes before starting the VM)

Co-authored-by: Pearl Dsilva <pearl.dsilva@shapeblue.com>
This commit is contained in:
Pearl Dsilva 2021-03-29 12:54:47 +05:30 committed by GitHub
parent 918c3bd3d5
commit 97176690b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 6 deletions

View File

@ -2071,7 +2071,13 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
// Setup ROOT/DATA disk devices
//
for (DiskTO vol : sortedDisks) {
if (vol.getType() == Volume.Type.ISO || deployAsIs && vol.getType() == Volume.Type.ROOT) {
if (vol.getType() == Volume.Type.ISO) {
continue;
}
if (deployAsIs && vol.getType() == Volume.Type.ROOT) {
rootDiskTO = vol;
resizeRootDiskOnVMStart(vmMo, rootDiskTO, hyperHost, context);
continue;
}

View File

@ -31,6 +31,7 @@ import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Level;
@ -70,6 +71,10 @@ import com.cloud.network.rules.RulesService;
import com.cloud.network.rules.dao.PortForwardingRulesDao;
import com.cloud.offering.ServiceOffering;
import com.cloud.resource.ResourceManager;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.SSHKeyPairVO;
import com.cloud.uservm.UserVm;
@ -118,6 +123,10 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
protected VMInstanceDao vmInstanceDao;
@Inject
protected UserVmManager userVmManager;
@Inject
protected VolumeApiService volumeService;
@Inject
protected VolumeDao volumeDao;
protected String kubernetesClusterNodeNamePrefix;
@ -268,6 +277,29 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
return plan(kubernetesCluster.getTotalNodeCount(), zone, offering);
}
protected void resizeNodeVolume(final UserVm vm) throws ManagementServerException {
try {
if (vm.getHypervisorType() == Hypervisor.HypervisorType.VMware && templateDao.findById(vm.getTemplateId()).isDeployAsIs()) {
List<VolumeVO> vmVols = volumeDao.findByInstance(vm.getId());
for (VolumeVO volumeVO : vmVols) {
if (volumeVO.getVolumeType() == Volume.Type.ROOT) {
ResizeVolumeCmd resizeVolumeCmd = new ResizeVolumeCmd();
resizeVolumeCmd = ComponentContext.inject(resizeVolumeCmd);
Field f = resizeVolumeCmd.getClass().getDeclaredField("size");
Field f1 = resizeVolumeCmd.getClass().getDeclaredField("id");
f.setAccessible(true);
f1.setAccessible(true);
f1.set(resizeVolumeCmd, volumeVO.getId());
f.set(resizeVolumeCmd, kubernetesCluster.getNodeRootDiskSize());
volumeService.resizeVolume(resizeVolumeCmd);
}
}
}
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new ManagementServerException(String.format("Failed to resize volume of VM in the Kubernetes cluster : %s", kubernetesCluster.getName()), e);
}
}
protected void startKubernetesVM(final UserVm vm) throws ManagementServerException {
try {
StartVMCmd startVm = new StartVMCmd();
@ -275,6 +307,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
Field f = startVm.getClass().getDeclaredField("id");
f.setAccessible(true);
f.set(startVm, vm.getId());
resizeNodeVolume(vm);
userVmService.startVirtualMachine(startVm);
if (LOGGER.isInfoEnabled()) {
LOGGER.info(String.format("Started VM : %s in the Kubernetes cluster : %s", vm.getDisplayName(), kubernetesCluster.getName()));
@ -296,6 +329,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
for (int i = offset + 1; i <= nodeCount; i++) {
UserVm vm = createKubernetesNode(publicIpAddress, i);
addKubernetesClusterVm(kubernetesCluster.getId(), vm.getId());
resizeNodeVolume(vm);
startKubernetesVM(vm);
vm = userVmDao.findById(vm.getId());
if (vm == null) {

View File

@ -277,6 +277,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
UserVm k8sMasterVM = null;
k8sMasterVM = createKubernetesMaster(network, publicIpAddress);
addKubernetesClusterVm(kubernetesCluster.getId(), k8sMasterVM.getId());
resizeNodeVolume(k8sMasterVM);
startKubernetesVM(k8sMasterVM);
k8sMasterVM = userVmDao.findById(k8sMasterVM.getId());
if (k8sMasterVM == null) {
@ -296,6 +297,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
UserVm vm = null;
vm = createKubernetesAdditionalMaster(publicIpAddress, i);
addKubernetesClusterVm(kubernetesCluster.getId(), vm.getId());
resizeNodeVolume(vm);
startKubernetesVM(vm);
vm = userVmDao.findById(vm.getId());
if (vm == null) {

View File

@ -916,8 +916,15 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
// if we are to use the existing disk offering
ImageFormat format = null;
if (newDiskOffering == null) {
if (volume.getVolumeType().equals(Volume.Type.ROOT) && diskOffering.getDiskSize() > 0) {
Long templateId = volume.getTemplateId();
if (templateId != null) {
VMTemplateVO template = _templateDao.findById(templateId);
format = template.getFormat();
}
if (volume.getVolumeType().equals(Volume.Type.ROOT) && diskOffering.getDiskSize() > 0 && format != null && format != ImageFormat.ISO) {
throw new InvalidParameterValueException(
"Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; "
+ "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. "

View File

@ -49,6 +49,8 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.cloud.agent.api.to.deployasis.OVFPropertyTO;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import com.cloud.deployasis.UserVmDeployAsIsDetailVO;
import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao;
import com.cloud.exception.UnsupportedServiceException;
@ -514,6 +516,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private UserVmDeployAsIsDetailsDao userVmDeployAsIsDetailsDao;
@Inject
private StorageManager storageMgr;
@Inject
private ServiceOfferingJoinDao serviceOfferingJoinDao;
private ScheduledExecutorService _executor = null;
private ScheduledExecutorService _vmIpFetchExecutor = null;
@ -5266,9 +5270,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new InvalidParameterValueException("Unable to use template " + templateId);
}
// Bootmode and boottype are not supported on VMWare dpeloy-as-is templates (since 4.15)
if (template.isDeployAsIs() && (cmd.getBootMode() != null || cmd.getBootType() != null)) {
throw new InvalidParameterValueException("Boot type and boot mode are not supported on VMware, as we honour what is defined in the template.");
ServiceOfferingJoinVO svcOffering = serviceOfferingJoinDao.findById(serviceOfferingId);
if (template.isDeployAsIs()) {
if (svcOffering != null && svcOffering.getRootDiskSize() != null && svcOffering.getRootDiskSize() > 0) {
throw new InvalidParameterValueException("Failed to deploy Virtual Machine as a service offering with root disk size specified cannot be used with a deploy as-is template");
}
if (cmd.getDetails().get("rootdisksize") != null) {
throw new InvalidParameterValueException("Overriding root disk size isn't supported for VMs deployed from defploy as-is templates");
}
// Bootmode and boottype are not supported on VMWare dpeloy-as-is templates (since 4.15)
if ((cmd.getBootMode() != null || cmd.getBootType() != null)) {
throw new InvalidParameterValueException("Boot type and boot mode are not supported on VMware, as we honour what is defined in the template.");
}
}
Long diskOfferingId = cmd.getDiskOfferingId();

View File

@ -2682,6 +2682,7 @@
"message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN Customer Gateway",
"message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway",
"message.deleting.vm": "Deleting VM",
"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.",
"message.desc.add.new.lb.sticky.rule": "Add new LB sticky rule",
"message.desc.advanced.zone": "For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
"message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",

View File

@ -105,7 +105,8 @@
@update-template-iso="updateFieldValue" />
<span>
{{ $t('label.override.rootdisk.size') }}
<a-switch @change="val => { this.showRootDiskSizeChanger = val }" style="margin-left: 10px;"/>
<a-switch :disabled="template.deployasis" @change="val => { this.showRootDiskSizeChanger = val }" style="margin-left: 10px;"/>
<div v-if="template.deployasis"> {{ this.$t('message.deployasis') }} </div>
</span>
<disk-size-selection
v-show="showRootDiskSizeChanger"
@ -183,6 +184,7 @@
</a-form-item>
<compute-offering-selection
:compute-items="options.serviceOfferings"
:selected-template="template"
:row-count="rowCount.serviceOfferings"
:zoneId="zoneId"
:value="serviceOffering ? serviceOffering.id : ''"
@ -543,6 +545,9 @@
:options="keyboardSelectOptions"
></a-select>
</a-form-item>
<a-form-item :label="$t('label.action.start.instance')">
<a-switch v-decorator="['startvm', { initialValue: true }]" :checked="this.startvm" @change="checked => { this.startvm = checked }" />
</a-form-item>
</div>
</template>
</a-step>
@ -663,6 +668,7 @@ export default {
podId: null,
clusterId: null,
zoneSelected: false,
startvm: true,
vm: {
name: null,
zoneid: null,
@ -1419,6 +1425,9 @@ export default {
if (values.hypervisor && values.hypervisor.length > 0) {
deployVmData.hypervisor = values.hypervisor
}
deployVmData.startvm = values.startvm
// step 3: select service offering
deployVmData.serviceofferingid = values.computeofferingid
if (this.serviceOffering && this.serviceOffering.iscustomized) {

View File

@ -63,6 +63,10 @@ export default {
type: Array,
default: () => []
},
selectedTemplate: {
type: Object,
default: () => {}
},
rowCount: {
type: Number,
default: () => 0
@ -161,6 +165,9 @@ export default {
(item.iscustomized === true && maxMemory < this.minimumMemory))) {
disabled = true
}
if (this.selectedTemplate && this.selectedTemplate.hypervisor === 'VMware' && this.selectedTemplate.deployasis && item.rootdisksize) {
disabled = true
}
return {
key: item.id,
name: item.name,
@ -238,6 +245,9 @@ export default {
return {
on: {
click: () => {
if (record.disabled) {
return
}
this.selectedRowKeys = [record.key]
this.$emit('select-compute-item', record.key)
}