diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java index b4f4619be9ac..6f24b1cd6ca8 100644 --- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java @@ -84,6 +84,8 @@ public class VirtualMachineTO { Map extraConfig = new HashMap<>(); Map networkIdToNetworkNameMap = new HashMap<>(); DeployAsIsInfoTO deployAsIsInfo; + String metadataManufacturer; + String metadataProductName; public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader, String os, boolean enableHA, boolean limitCpuUse, String vncPassword) { @@ -429,6 +431,22 @@ public void setDeployAsIsInfo(DeployAsIsInfoTO deployAsIsInfo) { this.deployAsIsInfo = deployAsIsInfo; } + public String getMetadataManufacturer() { + return metadataManufacturer; + } + + public void setMetadataManufacturer(String metadataManufacturer) { + this.metadataManufacturer = metadataManufacturer; + } + + public String getMetadataProductName() { + return metadataProductName; + } + + public void setMetadataProductName(String metadataProductName) { + this.metadataProductName = metadataProductName; + } + @Override public String toString() { return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 04ba9a483e1d..75211df5291e 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -87,6 +87,20 @@ public interface VirtualMachineManager extends Manager { ConfigKey MetadataCustomCloudName = new ConfigKey<>("Advanced", String.class, "metadata.custom.cloud.name", "", "If provided, a custom cloud-name in cloud-init metadata", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataManufacturer = new ConfigKey<>("Advanced", String.class, + "vm.metadata.manufacturer", "Apache Software Foundation", + "If provided, a custom manufacturer will be used in the instance metadata. When an empty" + + "value is set then default manufacturer will be 'Apache Software Foundation'. " + + "A custom manufacturer may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataProductName = new ConfigKey<>("Advanced", String.class, + "vm.metadata.product", "", + "If provided, a custom product name will be used in the instance metadata. When an empty" + + "value is set then default product name will be 'CloudStack Hypervisor'. " + + "A custom product name may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", + true, ConfigKey.Scope.Zone); + interface Topics { String VM_POWER_STATE = "vm.powerstate"; } 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 d207ebc41ce9..3fa27e52c090 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -49,15 +49,6 @@ import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; -import com.cloud.configuration.Resource; -import com.cloud.domain.Domain; -import com.cloud.domain.dao.DomainDao; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.dao.AccountDao; -import com.cloud.event.ActionEventUtils; -import com.google.gson.Gson; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -160,6 +151,7 @@ import com.cloud.api.query.vo.DomainRouterJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.capacity.CapacityManager; +import com.cloud.configuration.Resource; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; @@ -178,6 +170,9 @@ import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.DeploymentPlanningManagerImpl; import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.event.UsageEventVO; @@ -189,6 +184,7 @@ import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageAccessException; import com.cloud.exception.StorageUnavailableException; @@ -211,6 +207,8 @@ import com.cloud.network.dao.NetworkVO; import com.cloud.network.router.VirtualRouter; import com.cloud.network.security.SecurityGroupManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.NetworkOffering; @@ -246,6 +244,7 @@ import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; @@ -281,6 +280,7 @@ import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.google.gson.Gson; public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { @@ -1101,6 +1101,19 @@ protected void checkAndAttemptMigrateVmAcrossCluster(final VMInstanceVO vm, fina markVolumesInPool(vm, answer); } + protected void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm) { + String metadataManufacturer = VmMetadataManufacturer.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataManufacturer = VmMetadataManufacturer.defaultValue(); + } + vmTO.setMetadataManufacturer(metadataManufacturer); + String metadataProduct = VmMetadataProductName.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataProduct = String.format("CloudStack %s Hypervisor", vm.getHypervisorType().toString()); + } + vmTO.setMetadataProductName(metadataProduct); + } + @Override public void orchestrateStart(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -1259,6 +1272,7 @@ public void orchestrateStart(final String vmUuid, final Map[] getConfigKeys() { VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, - AllowExposeDomainInMetadata, MetadataCustomCloudName + AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName }; } diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index 9b32980087c0..f2bd1095c819 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -37,26 +37,18 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.stream.Collectors; -import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.dao.DataCenterDao; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.AccountVO; -import com.cloud.user.dao.AccountDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ScopedConfigStorage; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.MapUtils; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -76,24 +68,34 @@ import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.service.ServiceOfferingVO; @@ -115,7 +117,9 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.user.AccountVO; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.Journal; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; @@ -220,8 +224,12 @@ public class VirtualMachineManagerImplTest { @Mock protected StateMachine2 _stateMachine; + private ConfigDepotImpl configDepotImpl; + private boolean updatedConfigKeyDepot = false; + @Before public void setup() { + ReflectionTestUtils.getField(VirtualMachineManager.VmMetadataManufacturer, "s_depot"); virtualMachineManagerImpl.setHostAllocators(new ArrayList<>()); when(vmInstanceMock.getId()).thenReturn(vmInstanceVoMockId); @@ -251,6 +259,13 @@ public void setup() { virtualMachineManagerImpl.setStoragePoolAllocators(storagePoolAllocators); } + @After + public void cleanup() { + if (updatedConfigKeyDepot) { + ReflectionTestUtils.setField(VirtualMachineManager.VmMetadataManufacturer, "s_depot", configDepotImpl); + } + } + @Test public void testaddHostIpToCertDetailsIfConfigAllows() { Host vmHost = mock(Host.class); @@ -1236,4 +1251,48 @@ public void testGetDiskOfferingSuitabilityForVm() { assertFalse(result.get(1L)); assertTrue(result.get(2L)); } + + private void overrideVmMetadataConfigValue(final String manufacturer, final String product) { + ConfigKey configKey = VirtualMachineManager.VmMetadataManufacturer; + this.configDepotImpl = (ConfigDepotImpl)ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + ScopedConfigStorage storage = Mockito.mock(ScopedConfigStorage.class); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(configKey))).thenReturn(manufacturer); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(VirtualMachineManager.VmMetadataProductName))) + .thenReturn(product); + Mockito.when(configDepot.findScopedConfigStorage(configKey)).thenReturn(storage); + Mockito.when(configDepot.findScopedConfigStorage(VirtualMachineManager.VmMetadataProductName)).thenReturn(storage); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; + } + + private Pair getDummyVmTOAndVm() { + VirtualMachineTO virtualMachineTO = new VirtualMachineTO(1L, "VM", VirtualMachine.Type.User, 1, + 1000, 256, 512, VirtualMachineTemplate.BootloaderType.HVM, "OS", + false, false, "Pass"); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getDataCenterId()).thenReturn(1L); + return new Pair<>(virtualMachineTO, vm); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductDefaultManufacturer() { + overrideVmMetadataConfigValue("", ""); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(VirtualMachineManager.VmMetadataManufacturer.defaultValue(), to.getMetadataManufacturer()); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductCustomManufacturer() { + String manufacturer = UUID.randomUUID().toString(); + String product = UUID.randomUUID().toString(); + overrideVmMetadataConfigValue(manufacturer, product); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(manufacturer, to.getMetadataManufacturer()); + Assert.assertEquals(product, to.getMetadataProductName()); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java index 80a890aacad6..86e0da53666f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java @@ -29,6 +29,8 @@ import javax.persistence.TemporalType; import com.cloud.storage.snapshot.SnapshotSchedule; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; @Entity @Table(name = "snapshot_schedule") @@ -132,4 +134,11 @@ public String getUuid() { public void setUuid(String uuid) { this.uuid = uuid; } + + @Override + public String toString() { + ReflectionToStringBuilder reflectionToStringBuilder = new ReflectionToStringBuilder(this, ToStringStyle.JSON_STYLE); + reflectionToStringBuilder.setExcludeFieldNames("id"); + return reflectionToStringBuilder.toString(); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java index 7ca0a3915f54..284a42cf9e1d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java @@ -27,13 +27,11 @@ */ public interface SnapshotScheduleDao extends GenericDao { - List getCoincidingSnapshotSchedules(long volumeId, Date date); - List getSchedulesToExecute(Date currentTimestamp); - SnapshotScheduleVO getCurrentSchedule(Long volumeId, Long policyId, boolean executing); + List getSchedulesAssignedWithAsyncJob(); - SnapshotScheduleVO findOneByVolume(long volumeId); + SnapshotScheduleVO getCurrentSchedule(Long volumeId, Long policyId, boolean executing); SnapshotScheduleVO findOneByVolumePolicy(long volumeId, long policyId); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java index 925d02dd90b4..14669ce1d438 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java @@ -32,7 +32,7 @@ public class SnapshotScheduleDaoImpl extends GenericDaoBase implements SnapshotScheduleDao { protected final SearchBuilder executableSchedulesSearch; protected final SearchBuilder coincidingSchedulesSearch; - private final SearchBuilder VolumeIdSearch; + protected final SearchBuilder schedulesAssignedWithAsyncJob; private final SearchBuilder VolumeIdPolicyIdSearch; protected SnapshotScheduleDaoImpl() { @@ -48,36 +48,14 @@ protected SnapshotScheduleDaoImpl() { coincidingSchedulesSearch.and("asyncJobId", coincidingSchedulesSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL); coincidingSchedulesSearch.done(); - VolumeIdSearch = createSearchBuilder(); - VolumeIdSearch.and("volumeId", VolumeIdSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - VolumeIdSearch.done(); - VolumeIdPolicyIdSearch = createSearchBuilder(); VolumeIdPolicyIdSearch.and("volumeId", VolumeIdPolicyIdSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); VolumeIdPolicyIdSearch.and("policyId", VolumeIdPolicyIdSearch.entity().getPolicyId(), SearchCriteria.Op.EQ); VolumeIdPolicyIdSearch.done(); - } - - /** - * {@inheritDoc} - */ - @Override - public List getCoincidingSnapshotSchedules(long volumeId, Date date) { - SearchCriteria sc = coincidingSchedulesSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("scheduledTimestamp", date); - // Don't return manual snapshots. They will be executed through another - // code path. - sc.addAnd("policyId", SearchCriteria.Op.NEQ, 1L); - return listBy(sc); - } - - @Override - public SnapshotScheduleVO findOneByVolume(long volumeId) { - SearchCriteria sc = VolumeIdSearch.create(); - sc.setParameters("volumeId", volumeId); - return findOneBy(sc); + schedulesAssignedWithAsyncJob = createSearchBuilder(); + schedulesAssignedWithAsyncJob.and("asyncJobId", schedulesAssignedWithAsyncJob.entity().getAsyncJobId(), SearchCriteria.Op.NNULL); + schedulesAssignedWithAsyncJob.done(); } @Override @@ -98,6 +76,11 @@ public List getSchedulesToExecute(Date currentTimestamp) { return listBy(sc); } + @Override + public List getSchedulesAssignedWithAsyncJob() { + return listBy(schedulesAssignedWithAsyncJob.create()); + } + /** * {@inheritDoc} */ diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql index ab4b8aac6427..4b1acdd9bb4f 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql @@ -90,6 +90,9 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`( CONSTRAINT `FK_quota_email_configuration_account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`quota_account`(`account_id`), CONSTRAINT `FK_quota_email_configuration_email_template_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`)); +-- Remove on delete cascade from snapshot schedule +ALTER TABLE `cloud`.`snapshot_schedule` DROP CONSTRAINT `fk__snapshot_schedule_async_job_id`; + -- Add `is_implicit` column to `host_tags` table CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_tags', 'is_implicit', 'int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT "If host tag is implicit or explicit" '); 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 dec7f70e62fd..5d9645092158 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 @@ -2838,6 +2838,8 @@ protected GuestDef createGuestFromSpec(VirtualMachineTO vmTO, LibvirtVMDef vm, S GuestDef guest = new GuestDef(); configureGuestAndVMHypervisorType(vmTO, vm, guest); + guest.setManufacturer(vmTO.getMetadataManufacturer()); + guest.setProduct(vmTO.getMetadataProductName()); guest.setGuestArch(guestCpuArch != null ? guestCpuArch : vmTO.getArch()); guest.setMachineType(isGuestAarch64() ? VIRT : PC); guest.setBootType(GuestDef.BootType.BIOS); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index a03944306436..c1ea3e99717d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -95,6 +95,8 @@ public String toString() { } private GuestType _type; + private String manufacturer; + private String product; private BootType _boottype; private BootMode _bootmode; private String _arch; @@ -124,6 +126,22 @@ public GuestType getGuestType() { return _type; } + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + public void setNvram(String nvram) { _nvram = nvram; } public void setNvramTemplate(String nvramTemplate) { _nvramTemplate = nvramTemplate; } @@ -182,8 +200,8 @@ public String toString() { guestDef.append("\n"); guestDef.append("\n"); - guestDef.append("Apache Software Foundation\n"); - guestDef.append("CloudStack " + _type.toString() + " Hypervisor\n"); + guestDef.append("" + getManufacturer() +"\n"); + guestDef.append("" + getProduct() + "\n"); guestDef.append("" + _uuid + "\n"); guestDef.append("\n"); guestDef.append("\n"); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java index 29955066062c..2a53021636c5 100644 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.springframework.stereotype.Component; @@ -47,7 +48,6 @@ import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotScheduleVO; -import com.cloud.storage.SnapshotVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.SnapshotPolicyDao; @@ -64,7 +64,6 @@ import com.cloud.utils.concurrency.TestClock; import com.cloud.utils.db.DB; import com.cloud.utils.db.GlobalLock; -import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; @@ -144,7 +143,7 @@ public void poll(final Date currentTimestamp) { try { if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { try { - checkStatusOfCurrentlyExecutingSnapshots(); + scheduleNextSnapshotJobsIfNecessary(); } finally { scanLock.unlock(); } @@ -174,68 +173,37 @@ public void poll(final Date currentTimestamp) { } } - private void checkStatusOfCurrentlyExecutingSnapshots() { - final SearchCriteria sc = _snapshotScheduleDao.createSearchCriteria(); - sc.addAnd("asyncJobId", SearchCriteria.Op.NNULL); - final List snapshotSchedules = _snapshotScheduleDao.search(sc, null); - for (final SnapshotScheduleVO snapshotSchedule : snapshotSchedules) { - final Long asyncJobId = snapshotSchedule.getAsyncJobId(); - final AsyncJobVO asyncJob = _asyncJobDao.findByIdIncludingRemoved(asyncJobId); - switch (asyncJob.getStatus()) { - case SUCCEEDED: - // The snapshot has been successfully backed up. - // The snapshot state has also been cleaned up. - // We can schedule the next job for this snapshot. - // Remove the existing entry in the snapshot_schedule table. - scheduleNextSnapshotJob(snapshotSchedule); - break; - case FAILED: - // Check the snapshot status. - final Long snapshotId = snapshotSchedule.getSnapshotId(); - if (snapshotId == null) { - // createSnapshotAsync exited, successfully or unsuccessfully, - // even before creating a snapshot record - // No cleanup needs to be done. - // Schedule the next snapshot. - scheduleNextSnapshotJob(snapshotSchedule); - } else { - final SnapshotVO snapshot = _snapshotDao.findById(snapshotId); - if (snapshot == null || snapshot.getRemoved() != null) { - // This snapshot has been deleted successfully from the primary storage - // Again no cleanup needs to be done. - // Schedule the next snapshot. - // There's very little probability that the code reaches this point. - // The snapshotId is a foreign key for the snapshot_schedule table - // set to ON DELETE CASCADE. So if the snapshot entry is deleted, the snapshot_schedule entry will be too. - // But what if it has only been marked as removed? - scheduleNextSnapshotJob(snapshotSchedule); - } else { - // The management server executing this snapshot job appears to have crashed - // while creating the snapshot on primary storage/or backing it up. - // We have no idea whether the snapshot was successfully taken on the primary or not. - // Schedule the next snapshot job. - // The ValidatePreviousSnapshotCommand will take appropriate action on this snapshot - // If the snapshot was taken successfully on primary, it will retry backing it up. - // and cleanup the previous snapshot - // Set the userId to that of system. - //_snapshotManager.validateSnapshot(1L, snapshot); - // In all cases, schedule the next snapshot job - scheduleNextSnapshotJob(snapshotSchedule); - } - } + private void scheduleNextSnapshotJobsIfNecessary() { + List snapshotSchedules = _snapshotScheduleDao.getSchedulesAssignedWithAsyncJob(); + logger.info("Verifying the current state of [{}] snapshot schedules and scheduling next jobs, if necessary.", snapshotSchedules.size()); + for (SnapshotScheduleVO snapshotSchedule : snapshotSchedules) { + scheduleNextSnapshotJobIfNecessary(snapshotSchedule); + } + } - break; - case IN_PROGRESS: - // There is no way of knowing from here whether - // 1) Another management server is processing this snapshot job - // 2) The management server has crashed and this snapshot is lying - // around in an inconsistent state. - // Hopefully, this can be resolved at the backend when the current snapshot gets executed. - // But if it remains in this state, the current snapshot will not get executed. - // And it will remain in stasis. - break; - } + protected void scheduleNextSnapshotJobIfNecessary(SnapshotScheduleVO snapshotSchedule) { + Long asyncJobId = snapshotSchedule.getAsyncJobId(); + AsyncJobVO asyncJob = _asyncJobDao.findByIdIncludingRemoved(asyncJobId); + + if (asyncJob == null) { + logger.debug("The async job [{}] of snapshot schedule [{}] does not exist anymore. Considering it as finished and scheduling the next snapshot job.", + asyncJobId, snapshotSchedule); + scheduleNextSnapshotJob(snapshotSchedule); + return; } + + JobInfo.Status status = asyncJob.getStatus(); + + if (JobInfo.Status.SUCCEEDED.equals(status)) { + logger.debug("Last job of schedule [{}] succeeded; scheduling the next snapshot job.", snapshotSchedule); + } else if (JobInfo.Status.FAILED.equals(status)) { + logger.debug("Last job of schedule [{}] failed with [{}]; scheduling a new snapshot job.", snapshotSchedule, asyncJob.getResult()); + } else { + logger.debug("Schedule [{}] is still in progress, skipping next job scheduling.", snapshotSchedule); + return; + } + + scheduleNextSnapshotJob(snapshotSchedule); } @DB diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java index 971af289ef70..3827531891fd 100644 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java @@ -26,6 +26,9 @@ import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; +import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.jobs.JobInfo; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,6 +68,16 @@ public class SnapshotSchedulerImplTest { @Mock AccountVO accountVoMock; + @Mock + private SnapshotScheduleVO snapshotScheduleVoMock; + + @Mock + private AsyncJobDao asyncJobDaoMock; + + @Mock + private AsyncJobVO asyncJobVoMock; + + @Test public void scheduleNextSnapshotJobTestParameterIsNullReturnNull() { SnapshotScheduleVO snapshotScheduleVO = null; @@ -215,4 +228,50 @@ public void canSnapshotBeScheduledTestSnapshotPolicyIsNotRemovedDoNotCallRemove( Mockito.verify(snapshotScheduleDaoMock, Mockito.never()).remove(Mockito.anyLong()); } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobIsNullThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(null).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobSucceededThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.SUCCEEDED).when(asyncJobVoMock).getStatus(); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobFailedThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.FAILED).when(asyncJobVoMock).getStatus(); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobInProgressThenDoNothing() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.IN_PROGRESS).when(asyncJobVoMock).getStatus(); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy, Mockito.never()).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } }