diff --git a/api/src/main/java/com/cloud/storage/MigrationOptions.java b/api/src/main/java/com/cloud/storage/MigrationOptions.java index a39a2a7c827..8b642d09e29 100644 --- a/api/src/main/java/com/cloud/storage/MigrationOptions.java +++ b/api/src/main/java/com/cloud/storage/MigrationOptions.java @@ -24,6 +24,7 @@ public class MigrationOptions implements Serializable { private String srcPoolUuid; private Storage.StoragePoolType srcPoolType; + private Long srcPoolClusterId; private Type type; private ScopeType scopeType; private String srcBackingFilePath; @@ -38,21 +39,23 @@ public class MigrationOptions implements Serializable { public MigrationOptions() { } - public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcBackingFilePath, boolean copySrcTemplate, ScopeType scopeType) { + public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcBackingFilePath, boolean copySrcTemplate, ScopeType scopeType, Long srcPoolClusterId) { this.srcPoolUuid = srcPoolUuid; this.srcPoolType = srcPoolType; this.type = Type.LinkedClone; this.scopeType = scopeType; this.srcBackingFilePath = srcBackingFilePath; this.copySrcTemplate = copySrcTemplate; + this.srcPoolClusterId = srcPoolClusterId; } - public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcVolumeUuid, ScopeType scopeType) { + public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcVolumeUuid, ScopeType scopeType, Long srcPoolClusterId) { this.srcPoolUuid = srcPoolUuid; this.srcPoolType = srcPoolType; this.type = Type.FullClone; this.scopeType = scopeType; this.srcVolumeUuid = srcVolumeUuid; + this.srcPoolClusterId = srcPoolClusterId; } public String getSrcPoolUuid() { @@ -63,6 +66,10 @@ public class MigrationOptions implements Serializable { return srcPoolType; } + public Long getSrcPoolClusterId() { + return srcPoolClusterId; + } + public ScopeType getScopeType() { return scopeType; } public String getSrcBackingFilePath() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 0245f228b89..548f4d67b23 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; @@ -54,10 +55,16 @@ public class DeleteBackupScheduleCmd extends BaseCmd { @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, - required = true, description = "ID of the VM") private Long vmId; + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupScheduleResponse.class, + description = "ID of the schedule", + since = "4.20.1") + private Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -66,6 +73,9 @@ public class DeleteBackupScheduleCmd extends BaseCmd { return vmId; } + public Long getId() { return id; } + + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -73,7 +83,7 @@ public class DeleteBackupScheduleCmd extends BaseCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.deleteBackupSchedule(getVmId()); + boolean result = backupManager.deleteBackupSchedule(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java index 3847176608c..0db51f04034 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java @@ -57,10 +57,6 @@ public class BackupRepositoryResponse extends BaseResponse { @Param(description = "backup type") private String type; - @SerializedName(ApiConstants.MOUNT_OPTIONS) - @Param(description = "mount options for the backup repository") - private String mountOptions; - @SerializedName(ApiConstants.CAPACITY_BYTES) @Param(description = "capacity of the backup repository") private Long capacityBytes; @@ -112,14 +108,6 @@ public class BackupRepositoryResponse extends BaseResponse { this.address = address; } - public String getMountOptions() { - return mountOptions; - } - - public void setMountOptions(String mountOptions) { - this.mountOptions = mountOptions; - } - public String getProviderName() { return providerName; } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index cbd4b7e0596..eebad3af067 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -23,6 +23,7 @@ import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -192,10 +193,10 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer /** * Deletes VM backup schedule for a VM - * @param vmId + * @param cmd * @return */ - boolean deleteBackupSchedule(Long vmId); + boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd); /** * Creates backup of a VM diff --git a/client/pom.xml b/client/pom.xml index e12e0395482..2b673d7750e 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -642,6 +642,11 @@ cloud-plugin-storage-object-ceph ${project.version} + + org.apache.cloudstack + cloud-plugin-storage-object-cloudian + ${project.version} + org.apache.cloudstack cloud-plugin-storage-object-simulator diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index 765602e42d0..ca56446631c 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -1473,7 +1473,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl } if (!BooleanUtils.toBoolean(EnableKVMAutoEnableDisable.valueIn(host.getClusterId()))) { logger.debug("{} is disabled for the cluster {}, cannot process the health check result " + - "received for the host {}", EnableKVMAutoEnableDisable.key(), host.getClusterId(), host); + "received for {}", EnableKVMAutoEnableDisable.key(), host.getClusterId(), host); return; } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 7b231d02cb0..472fe148a5d 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -6071,6 +6071,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds) { VMInstanceVO vm = _vmDao.findById(vmId); + if (userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) { + return new HashMap<>(); + } VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); Pair clusterAndHost = findClusterAndHostIdForVm(vm, false); Long clusterId = clusterAndHost.first(); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java index 4b344ac4299..7a1a39ec0f0 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java @@ -77,19 +77,19 @@ public class VirtualMachinePowerStateSyncImpl implements VirtualMachinePowerStat processReport(hostId, translatedInfo, force); } - private void updateAndPublishVmPowerStates(long hostId, Map instancePowerStates, - Date updateTime) { + protected void updateAndPublishVmPowerStates(long hostId, Map instancePowerStates, + Date updateTime) { if (instancePowerStates.isEmpty()) { return; } Set vmIds = instancePowerStates.keySet(); - Map notUpdated = _instanceDao.updatePowerState(instancePowerStates, hostId, - updateTime); + Map notUpdated = + _instanceDao.updatePowerState(instancePowerStates, hostId, updateTime); if (notUpdated.size() > vmIds.size()) { return; } for (Long vmId : vmIds) { - if (!notUpdated.isEmpty() && !notUpdated.containsKey(vmId)) { + if (!notUpdated.containsKey(vmId)) { logger.debug("VM state report is updated. {}, {}, power state: {}", () -> hostCache.get(hostId), () -> vmCache.get(vmId), () -> instancePowerStates.get(vmId)); _messageBus.publish(null, VirtualMachineManager.Topics.VM_POWER_STATE, diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachinePowerStateSyncImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachinePowerStateSyncImplTest.java new file mode 100644 index 00000000000..4df14fe22f3 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachinePowerStateSyncImplTest.java @@ -0,0 +1,107 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VirtualMachinePowerStateSyncImplTest { + @Mock + MessageBus messageBus; + @Mock + VMInstanceDao instanceDao; + @Mock + HostDao hostDao; + + @InjectMocks + VirtualMachinePowerStateSyncImpl virtualMachinePowerStateSync = new VirtualMachinePowerStateSyncImpl(); + + @Before + public void setup() { + Mockito.lenient().when(instanceDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(VMInstanceVO.class)); + Mockito.lenient().when(hostDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(HostVO.class)); + } + + @Test + public void test_updateAndPublishVmPowerStates_emptyStates() { + virtualMachinePowerStateSync.updateAndPublishVmPowerStates(1L, new HashMap<>(), new Date()); + Mockito.verify(instanceDao, Mockito.never()).updatePowerState(Mockito.anyMap(), Mockito.anyLong(), + Mockito.any(Date.class)); + } + + @Test + public void test_updateAndPublishVmPowerStates_moreNotUpdated() { + Map powerStates = new HashMap<>(); + powerStates.put(1L, VirtualMachine.PowerState.PowerOff); + Map notUpdated = new HashMap<>(powerStates); + notUpdated.put(2L, VirtualMachine.PowerState.PowerOn); + Mockito.when(instanceDao.updatePowerState(Mockito.anyMap(), Mockito.anyLong(), + Mockito.any(Date.class))).thenReturn(notUpdated); + virtualMachinePowerStateSync.updateAndPublishVmPowerStates(1L, powerStates, new Date()); + Mockito.verify(messageBus, Mockito.never()).publish(Mockito.nullable(String.class), Mockito.anyString(), + Mockito.any(PublishScope.class), Mockito.anyLong()); + } + + @Test + public void test_updateAndPublishVmPowerStates_allUpdated() { + Map powerStates = new HashMap<>(); + powerStates.put(1L, VirtualMachine.PowerState.PowerOff); + Mockito.when(instanceDao.updatePowerState(Mockito.anyMap(), Mockito.anyLong(), + Mockito.any(Date.class))).thenReturn(new HashMap<>()); + virtualMachinePowerStateSync.updateAndPublishVmPowerStates(1L, powerStates, new Date()); + Mockito.verify(messageBus, Mockito.times(1)).publish(null, + VirtualMachineManager.Topics.VM_POWER_STATE, + PublishScope.GLOBAL, + 1L); + } + + @Test + public void test_updateAndPublishVmPowerStates_partialUpdated() { + Map powerStates = new HashMap<>(); + powerStates.put(1L, VirtualMachine.PowerState.PowerOn); + powerStates.put(2L, VirtualMachine.PowerState.PowerOff); + Map notUpdated = new HashMap<>(); + notUpdated.put(2L, VirtualMachine.PowerState.PowerOff); + Mockito.when(instanceDao.updatePowerState(Mockito.anyMap(), Mockito.anyLong(), + Mockito.any(Date.class))).thenReturn(notUpdated); + virtualMachinePowerStateSync.updateAndPublishVmPowerStates(1L, powerStates, new Date()); + Mockito.verify(messageBus, Mockito.times(1)).publish(null, + VirtualMachineManager.Topics.VM_POWER_STATE, + PublishScope.GLOBAL, + 1L); + Mockito.verify(messageBus, Mockito.never()).publish(null, + VirtualMachineManager.Topics.VM_POWER_STATE, + PublishScope.GLOBAL, + 2L); + } +} diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java index 1212bc66fd7..947b4af8f69 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java @@ -215,7 +215,7 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot } VMTemplateStoragePoolVO sourceVolumeTemplateStoragePoolVO = vmTemplatePoolDao.findByPoolTemplate(destStoragePool.getId(), srcVolumeInfo.getTemplateId(), null); - if (sourceVolumeTemplateStoragePoolVO == null && (isStoragePoolTypeInList(destStoragePool.getPoolType(), StoragePoolType.Filesystem, StoragePoolType.SharedMountPoint))) { + if (sourceVolumeTemplateStoragePoolVO == null && (isStoragePoolTypeInList(destStoragePool.getPoolType(), StoragePoolType.NetworkFilesystem, StoragePoolType.Filesystem, StoragePoolType.SharedMountPoint))) { DataStore sourceTemplateDataStore = dataStoreManagerImpl.getRandomImageStore(srcVolumeInfo.getDataCenterId()); if (sourceTemplateDataStore != null) { TemplateInfo sourceTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), sourceTemplateDataStore); diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 2f1227a91a5..cdf823eaf5b 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -1949,18 +1949,26 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { /** * Return expected MigrationOptions for a linked clone volume live storage migration */ - protected MigrationOptions createLinkedCloneMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, String srcVolumeBackingFile, String srcPoolUuid, Storage.StoragePoolType srcPoolType) { + protected MigrationOptions createLinkedCloneMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, String srcVolumeBackingFile, StoragePoolVO srcPool) { + String srcPoolUuid = srcPool.getUuid(); + Storage.StoragePoolType srcPoolType = srcPool.getPoolType(); + Long srcPoolClusterId = srcPool.getClusterId(); VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(destVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId(), null); boolean updateBackingFileReference = ref == null; String backingFile = !updateBackingFileReference ? ref.getInstallPath() : srcVolumeBackingFile; - return new MigrationOptions(srcPoolUuid, srcPoolType, backingFile, updateBackingFileReference, srcVolumeInfo.getDataStore().getScope().getScopeType()); + ScopeType scopeType = srcVolumeInfo.getDataStore().getScope().getScopeType(); + return new MigrationOptions(srcPoolUuid, srcPoolType, backingFile, updateBackingFileReference, scopeType, srcPoolClusterId); } /** * Return expected MigrationOptions for a full clone volume live storage migration */ - protected MigrationOptions createFullCloneMigrationOptions(VolumeInfo srcVolumeInfo, VirtualMachineTO vmTO, Host srcHost, String srcPoolUuid, Storage.StoragePoolType srcPoolType) { - return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeInfo.getPath(), srcVolumeInfo.getDataStore().getScope().getScopeType()); + protected MigrationOptions createFullCloneMigrationOptions(VolumeInfo srcVolumeInfo, VirtualMachineTO vmTO, Host srcHost, StoragePoolVO srcPool) { + String srcPoolUuid = srcPool.getUuid(); + Storage.StoragePoolType srcPoolType = srcPool.getPoolType(); + Long srcPoolClusterId = srcPool.getClusterId(); + ScopeType scopeType = srcVolumeInfo.getDataStore().getScope().getScopeType(); + return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeInfo.getPath(), scopeType, srcPoolClusterId); } /** @@ -1983,9 +1991,9 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { MigrationOptions migrationOptions; if (MigrationOptions.Type.LinkedClone.equals(migrationType)) { - migrationOptions = createLinkedCloneMigrationOptions(srcVolumeInfo, destVolumeInfo, srcVolumeBackingFile, srcPoolUuid, srcPoolType); + migrationOptions = createLinkedCloneMigrationOptions(srcVolumeInfo, destVolumeInfo, srcVolumeBackingFile, srcPool); } else { - migrationOptions = createFullCloneMigrationOptions(srcVolumeInfo, vmTO, srcHost, srcPoolUuid, srcPoolType); + migrationOptions = createFullCloneMigrationOptions(srcVolumeInfo, vmTO, srcHost, srcPool); } migrationOptions.setTimeout(StorageManager.KvmStorageOnlineMigrationWait.value()); destVolumeInfo.setMigrationOptions(migrationOptions); diff --git a/packaging/el8/cloud.spec b/packaging/el8/cloud.spec index 244f4431a3b..c9bea72f5e8 100644 --- a/packaging/el8/cloud.spec +++ b/packaging/el8/cloud.spec @@ -114,6 +114,7 @@ Requires: iproute Requires: ipset Requires: perl Requires: rsync +Requires: cifs-utils Requires: (python3-libvirt or python3-libvirt-python) Requires: (qemu-img or qemu-tools) Requires: qemu-kvm diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index ba37c899169..1e3b5aaa8e5 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -219,6 +219,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co restoreCommand.setBackupPath(backup.getExternalId()); restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); + restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmName(vm.getName()); restoreCommand.setVolumePaths(getVolumePaths(volumes)); restoreCommand.setVmExists(vm.getRemoved() == null); @@ -287,6 +288,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co restoreCommand.setVmName(vmNameAndState.first()); restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT)); + restoreCommand.setMountOptions(backupRepository.getMountOptions()); restoreCommand.setVmExists(null); restoreCommand.setVmState(vmNameAndState.second()); restoreCommand.setRestoreVolumeUUID(volumeUuid); @@ -372,8 +374,12 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co Long vmBackupSize = 0L; Long vmBackupProtectedSize = 0L; for (final Backup backup: backupDao.listByVmId(null, vm.getId())) { - vmBackupSize += backup.getSize(); - vmBackupProtectedSize += backup.getProtectedSize(); + if (Objects.nonNull(backup.getSize())) { + vmBackupSize += backup.getSize(); + } + if (Objects.nonNull(backup.getProtectedSize())) { + vmBackupProtectedSize += backup.getProtectedSize(); + } } Backup.Metric vmBackupMetric = new Backup.Metric(vmBackupSize,vmBackupProtectedSize); LOG.debug("Metrics for VM {} is [backup size: {}, data size: {}].", vm, vmBackupMetric.getBackupSize(), vmBackupMetric.getDataSize()); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java index eb64f4bc439..ce9fbe6c232 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java @@ -38,6 +38,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import javax.inject.Inject; +import java.util.Arrays; import java.util.List; public class KVMInvestigator extends AdapterBase implements Investigator { @@ -81,7 +82,7 @@ public class KVMInvestigator extends AdapterBase implements Investigator { return haManager.getHostStatus(agent); } - List clusterPools = _storagePoolDao.listPoolsByCluster(agent.getClusterId()); + List clusterPools = _storagePoolDao.findPoolsInClusters(Arrays.asList(agent.getClusterId()), null); boolean storageSupportHA = storageSupportHa(clusterPools); if (!storageSupportHA) { List zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(agent.getDataCenterId(), agent.getHypervisorType()); @@ -89,7 +90,7 @@ public class KVMInvestigator extends AdapterBase implements Investigator { } if (!storageSupportHA) { logger.warn("Agent investigation was requested on host {}, but host does not support investigation because it has no NFS storage. Skipping investigation.", agent); - return Status.Disconnected; + return null; } Status hostStatus = null; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 29374f3e594..7997983a367 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -331,6 +331,16 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv public static final String UBUNTU_WINDOWS_GUEST_CONVERSION_SUPPORTED_CHECK_CMD = "dpkg -l virtio-win"; public static final String UBUNTU_NBDKIT_PKG_CHECK_CMD = "dpkg -l nbdkit"; + public static final int LIBVIRT_CGROUP_CPU_SHARES_MIN = 2; + public static final int LIBVIRT_CGROUP_CPU_SHARES_MAX = 262144; + /** + * The minimal value for the LIBVIRT_CGROUPV2_WEIGHT_MIN is actually 1. + * However, due to an old libvirt bug, it is raised to 2. + * See: https://github.com/libvirt/libvirt/commit/38af6497610075e5fe386734b87186731d4c17ac + */ + public static final int LIBVIRT_CGROUPV2_WEIGHT_MIN = 2; + public static final int LIBVIRT_CGROUPV2_WEIGHT_MAX = 10000; + private String modifyVlanPath; private String versionStringPath; private String patchScriptPath; @@ -512,8 +522,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private static int hostCpuMaxCapacity = 0; - private static final int CGROUP_V2_UPPER_LIMIT = 10000; - private static final String COMMAND_GET_CGROUP_HOST_VERSION = "stat -fc %T /sys/fs/cgroup/"; public static final String CGROUP_V2 = "cgroup2fs"; @@ -641,6 +649,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return libvirtUtilitiesHelper; } + public String getClusterId() { + return clusterId; + } + public CPUStat getCPUStat() { return cpuStat; } @@ -2821,14 +2833,24 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv int requestedCpuShares = vCpus * cpuSpeed; int hostCpuMaxCapacity = getHostCpuMaxCapacity(); + // cgroup v2 is in use if (hostCpuMaxCapacity > 0) { - int updatedCpuShares = (int) Math.ceil((requestedCpuShares * CGROUP_V2_UPPER_LIMIT) / (double) hostCpuMaxCapacity); - LOGGER.debug(String.format("This host utilizes cgroupv2 (as the max shares value is [%s]), thus, the VM requested shares of [%s] will be converted to " + - "consider the host limits; the new CPU shares value is [%s].", hostCpuMaxCapacity, requestedCpuShares, updatedCpuShares)); + + int updatedCpuShares = (int) Math.ceil((requestedCpuShares * LIBVIRT_CGROUPV2_WEIGHT_MAX) / (double) hostCpuMaxCapacity); + LOGGER.debug("This host utilizes cgroupv2 (as the max shares value is [{}]), thus, the VM requested shares of [{}] will be converted to " + + "consider the host limits; the new CPU shares value is [{}].", hostCpuMaxCapacity, requestedCpuShares, updatedCpuShares); + + if (updatedCpuShares < LIBVIRT_CGROUPV2_WEIGHT_MIN) updatedCpuShares = LIBVIRT_CGROUPV2_WEIGHT_MIN; + if (updatedCpuShares > LIBVIRT_CGROUPV2_WEIGHT_MAX) updatedCpuShares = LIBVIRT_CGROUPV2_WEIGHT_MAX; return updatedCpuShares; } - LOGGER.debug(String.format("This host does not have a maximum CPU shares set; therefore, this host utilizes cgroupv1 and the VM requested CPU shares [%s] will not be " + - "converted.", requestedCpuShares)); + + // cgroup v1 is in use + LOGGER.debug("This host does not have a maximum CPU shares set; therefore, this host utilizes cgroupv1 and the VM requested CPU shares [{}] will not be " + + "converted.", requestedCpuShares); + + if (requestedCpuShares < LIBVIRT_CGROUP_CPU_SHARES_MIN) requestedCpuShares = LIBVIRT_CGROUP_CPU_SHARES_MIN; + if (requestedCpuShares > LIBVIRT_CGROUP_CPU_SHARES_MAX) requestedCpuShares = LIBVIRT_CGROUP_CPU_SHARES_MAX; return requestedCpuShares; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java index 0f00a6e29bd..e050cb4e85d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java @@ -27,6 +27,7 @@ import com.cloud.agent.api.GetVmIpAddressCommand; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Pair; import com.cloud.utils.net.NetUtils; import com.cloud.utils.script.Script; @@ -34,6 +35,26 @@ import com.cloud.utils.script.Script; public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper { + static String virsh_path = null; + static String virt_win_reg_path = null; + static String grep_path = null; + static String awk_path = null; + static String sed_path = null; + static String virt_ls_path = null; + static String virt_cat_path = null; + static String tail_path = null; + + static void init() { + virt_ls_path = Script.getExecutableAbsolutePath("virt-ls"); + virt_cat_path = Script.getExecutableAbsolutePath("virt-cat"); + virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg"); + tail_path = Script.getExecutableAbsolutePath("tail"); + grep_path = Script.getExecutableAbsolutePath("grep"); + awk_path = Script.getExecutableAbsolutePath("awk"); + sed_path = Script.getExecutableAbsolutePath("sed"); + virsh_path = Script.getExecutableAbsolutePath("virsh"); + } + @Override public Answer execute(final GetVmIpAddressCommand command, final LibvirtComputingResource libvirtComputingResource) { String ip = null; @@ -42,65 +63,113 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper commands = new ArrayList<>(); - final String virt_ls_path = Script.getExecutableAbsolutePath("virt-ls"); - final String virt_cat_path = Script.getExecutableAbsolutePath("virt-cat"); - final String virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg"); - final String tail_path = Script.getExecutableAbsolutePath("tail"); - final String grep_path = Script.getExecutableAbsolutePath("grep"); - final String awk_path = Script.getExecutableAbsolutePath("awk"); - final String sed_path = Script.getExecutableAbsolutePath("sed"); - if(!command.isWindows()) { - //List all dhcp lease files inside guestVm - commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"}); - commands.add(new String[]{grep_path, ".*\\*.leases"}); - String leasesList = Script.executePipedCommands(commands, 0).second(); - if(leasesList != null) { - String[] leasesFiles = leasesList.split("\n"); - for(String leaseFile : leasesFiles){ - //Read from each dhclient lease file inside guest Vm using virt-cat libguestfs utility - commands = new ArrayList<>(); - commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile}); - commands.add(new String[]{tail_path, "-16"}); - commands.add(new String[]{grep_path, "fixed-address"}); - commands.add(new String[]{awk_path, "{print $2}"}); - commands.add(new String[]{sed_path, "-e", "s/;//"}); - String ipAddr = Script.executePipedCommands(commands, 0).second(); - // Check if the IP belongs to the network - if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) { - ip = ipAddr; - break; - } - logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); - } - } - } else { - // For windows, read from guest Vm registry using virt-win-reg libguestfs ulitiy. Registry Path: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces\\DhcpIPAddress - commands = new ArrayList<>(); - commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"}); - commands.add(new String[]{grep_path, "DhcpIPAddress"}); - commands.add(new String[]{awk_path, "-F", ":", "{print $2}"}); - commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"}); - String ipList = Script.executePipedCommands(commands, 0).second(); - if(ipList != null) { - logger.debug("GetVmIp: "+ vmName + "Ips: "+ipList); - String[] ips = ipList.split("\n"); - for (String ipAddr : ips){ - // Check if the IP belongs to the network - if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){ - ip = ipAddr; - break; - } - logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); - } + + ip = ipFromDomIf(sanitizedVmName, networkCidr); + + if (ip == null) { + if(!command.isWindows()) { + ip = ipFromDhcpLeaseFile(sanitizedVmName, networkCidr); + } else { + ip = ipFromWindowsRegistry(sanitizedVmName, networkCidr); } } + if(ip != null){ result = true; logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip); + } else { + logger.warn("GetVmIp: "+ vmName + " IP not found."); } + return new Answer(command, result, ip); } + + private String ipFromDomIf(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"}); + Pair response = executePipedCommands(commands, 0); + if (response != null) { + String output = response.second(); + String[] lines = output.split("\n"); + for (String line : lines) { + if (line.contains("ipv4")) { + String[] parts = line.split(" "); + String[] ipParts = parts[parts.length-1].split("/"); + if (ipParts.length > 1) { + if (NetUtils.isIpWithInCidrRange(ipParts[0], networkCidr)) { + ip = ipParts[0]; + break; + } + } + } + } + } else { + logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + private String ipFromDhcpLeaseFile(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"}); + commands.add(new String[]{grep_path, ".*\\*.leases"}); + Pair response = executePipedCommands(commands, 0); + + if(response != null && response.second() != null) { + String leasesList = response.second(); + String[] leasesFiles = leasesList.split("\n"); + for(String leaseFile : leasesFiles){ + commands = new ArrayList<>(); + commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile}); + commands.add(new String[]{tail_path, "-16"}); + commands.add(new String[]{grep_path, "fixed-address"}); + commands.add(new String[]{awk_path, "{print $2}"}); + commands.add(new String[]{sed_path, "-e", "s/;//"}); + String ipAddr = executePipedCommands(commands, 0).second(); + if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) { + ip = ipAddr; + break; + } + logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); + } + } else { + logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + private String ipFromWindowsRegistry(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"}); + commands.add(new String[]{grep_path, "DhcpIPAddress"}); + commands.add(new String[]{awk_path, "-F", ":", "{print $2}"}); + commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"}); + Pair pair = executePipedCommands(commands, 0); + if(pair != null && pair.second() != null) { + String ipList = pair.second(); + ipList = ipList.replaceAll("\"", ""); + logger.debug("GetVmIp: "+ sanitizedVmName + "Ips: "+ipList); + String[] ips = ipList.split("\n"); + for (String ipAddr : ips){ + if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){ + ip = ipAddr; + break; + } + logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); + } + } else { + logger.error("ipFromWindowsRegistry: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + static Pair executePipedCommands(List commands, long timeout) { + return Script.executePipedCommands(commands, timeout); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 23ead355096..49b67194356 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -67,7 +67,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper(vmName, command.getVmState())); + new Pair<>(vmName, command.getVmState()), mountOptions); } else if (Boolean.TRUE.equals(vmExists)) { restoreVolumesOfExistingVM(volumePaths, backupPath, backupRepoType, backupRepoAddress, mountOptions); } else { @@ -80,7 +80,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper volumePaths, String backupPath, String backupRepoType, String backupRepoAddress, String mountOptions) { String diskType = "root"; - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType); + String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); try { for (int idx = 0; idx < volumePaths.size(); idx++) { String volumePath = volumePaths.get(idx); @@ -101,7 +101,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper volumePaths, String vmName, String backupPath, String backupRepoType, String backupRepoAddress, String mountOptions) { - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType); + String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); String diskType = "root"; try { for (int i = 0; i < volumePaths.size(); i++) { @@ -121,8 +121,8 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper vmNameAndState) { - String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType); + String diskType, String volumeUUID, Pair vmNameAndState, String mountOptions) { + String mountDirectory = mountBackupDirectory(backupRepoAddress, backupRepoType, mountOptions); Pair bkpPathAndVolUuid; try { bkpPathAndVolUuid = getBackupPath(mountDirectory, volumePath, backupPath, diskType, volumeUUID); @@ -145,12 +145,22 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper scriptMock = mockStatic(Script.class); + + when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName"); + when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.isWindows()).thenReturn(false); + when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT)); + + Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource); + + try { + assertTrue(answer.getResult()); + assertEquals("192.168.0.10", answer.getDetails()); + } finally { + scriptMock.close(); + } + } + + @Test + public void testExecuteWithInvalidVmName() { + LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class); + GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class); + LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper(); + MockedStatic + + diff --git a/ui/src/views/infra/ConfigureHostOOBM.vue b/ui/src/views/infra/ConfigureHostOOBM.vue new file mode 100644 index 00000000000..d80ac68fc06 --- /dev/null +++ b/ui/src/views/infra/ConfigureHostOOBM.vue @@ -0,0 +1,172 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + + + diff --git a/ui/src/views/infra/HostEnableDisable.vue b/ui/src/views/infra/HostEnableDisable.vue index bc71aa27080..84310a0051f 100644 --- a/ui/src/views/infra/HostEnableDisable.vue +++ b/ui/src/views/infra/HostEnableDisable.vue @@ -28,15 +28,15 @@ > -
+
{ - if (json.listconfigurationsresponse.configuration[0]) { - this.enableKVMAutoEnableDisableSetting = json.listconfigurationsresponse.configuration[0].value + if (json.listconfigurationsresponse.configuration?.[0]) { + this.kvmAutoEnableDisableSetting = json?.listconfigurationsresponse?.configuration?.[0]?.value || false } }) }, diff --git a/ui/src/views/infra/HostInfo.vue b/ui/src/views/infra/HostInfo.vue index 1a4e9ee9f44..259445154a0 100644 --- a/ui/src/views/infra/HostInfo.vue +++ b/ui/src/views/infra/HostInfo.vue @@ -86,14 +86,48 @@
- -
- {{ $t('label.powerstate') }} + +
- {{ host.outofbandmanagement.powerstate }} + {{ $t('label.oobm.username') }} +
+ {{ host.outofbandmanagement.username }} +
-
-
+ + +
+ {{ $t('label.oobm.powerstate') }} +
+ {{ host.outofbandmanagement.powerstate }} +
+
+
+ +
+ {{ $t('label.oobm.driver') }} +
+ {{ host.outofbandmanagement.driver }} +
+
+
+ +
+ {{ $t('label.oobm.address') }} +
+ {{ host.outofbandmanagement.address }} +
+
+
+ +
+ {{ $t('label.oobm.port') }} +
+ {{ host.outofbandmanagement.port }} +
+
+
+
{{ $t('label.haenable') }} diff --git a/ui/src/views/offering/AddDiskOffering.vue b/ui/src/views/offering/AddDiskOffering.vue index 5cb1ff8bde9..f4e4d49c3dc 100644 --- a/ui/src/views/offering/AddDiskOffering.vue +++ b/ui/src/views/offering/AddDiskOffering.vue @@ -491,7 +491,6 @@ export default { const formRaw = toRaw(this.form) const values = this.handleRemoveFields(formRaw) var params = { - isMirrored: false, name: values.name, displaytext: values.displaytext, storageType: values.storagetype, diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue index 2ee2f61cf0d..55c3c39ebfc 100644 --- a/ui/src/views/offering/AddNetworkOffering.vue +++ b/ui/src/views/offering/AddNetworkOffering.vue @@ -312,11 +312,10 @@ - +