diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51c0b006c4a7..4fe612cad5d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,8 @@ jobs: smoke/test_multipleips_per_nic smoke/test_nested_virtualization smoke/test_set_sourcenat - smoke/test_webhook_lifecycle", + smoke/test_webhook_lifecycle + smoke/test_purge_expunged_vms", "smoke/test_network smoke/test_network_acl smoke/test_network_ipv6 diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 689676290b36..ea70b619d099 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -721,6 +721,8 @@ public class EventTypes { // SystemVM public static final String EVENT_LIVE_PATCH_SYSTEMVM = "LIVE.PATCH.SYSTEM.VM"; + //Purge resources + public static final String EVENT_PURGE_EXPUNGED_RESOURCES = "PURGE.EXPUNGED.RESOURCES"; // OBJECT STORE public static final String EVENT_OBJECT_STORE_CREATE = "OBJECT.STORE.CREATE"; diff --git a/api/src/main/java/com/cloud/network/element/LoadBalancingServiceProvider.java b/api/src/main/java/com/cloud/network/element/LoadBalancingServiceProvider.java index 1bb37be970d3..dc0f60f45196 100644 --- a/api/src/main/java/com/cloud/network/element/LoadBalancingServiceProvider.java +++ b/api/src/main/java/com/cloud/network/element/LoadBalancingServiceProvider.java @@ -48,4 +48,7 @@ public interface LoadBalancingServiceProvider extends NetworkElement, IpDeployin List updateHealthChecks(Network network, List lbrules); boolean handlesOnlyRulesInTransitionState(); + + default void expungeLbVmRefs(List vmIds, Long batchSize) { + } } diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java index 58c7b0dbaf96..acb7a9f1cf91 100644 --- a/api/src/main/java/com/cloud/offering/ServiceOffering.java +++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java @@ -33,6 +33,9 @@ public interface ServiceOffering extends InfrastructureEntity, InternalIdentity, static final String internalLbVmDefaultOffUniqueName = "Cloud.Com-InternalLBVm"; // leaving cloud.com references as these are identifyers and no real world addresses (check against DB) + + static final String PURGE_DB_ENTITIES_KEY = "purge.db.entities"; + enum State { Inactive, Active, } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 54c0227de9fa..bad462e5a008 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -388,6 +388,7 @@ public class ApiConstants { public static final String PUBLIC_START_PORT = "publicport"; public static final String PUBLIC_END_PORT = "publicendport"; public static final String PUBLIC_ZONE = "publiczone"; + public static final String PURGE_RESOURCES = "purgeresources"; public static final String RECEIVED_BYTES = "receivedbytes"; public static final String RECONNECT = "reconnect"; public static final String RECOVER = "recover"; @@ -909,6 +910,7 @@ public class ApiConstants { public static final String AUTOSCALE_VMGROUP_NAME = "autoscalevmgroupname"; public static final String BAREMETAL_DISCOVER_NAME = "baremetaldiscovername"; public static final String BAREMETAL_RCT_URL = "baremetalrcturl"; + public static final String BATCH_SIZE = "batchsize"; public static final String UCS_DN = "ucsdn"; public static final String GSLB_PROVIDER = "gslbprovider"; public static final String EXCLUSIVE_GSLB_PROVIDER = "isexclusivegslbprovider"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 4dfa76bb9dbe..8f6d5413d72d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -246,6 +246,12 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.ENCRYPT_ROOT, type = CommandType.BOOLEAN, description = "VMs using this offering require root volume encryption", since="4.18") private Boolean encryptRoot; + @Parameter(name = ApiConstants.PURGE_RESOURCES, type = CommandType.BOOLEAN, + description = "Whether to cleanup instance and its associated resource from database upon expunge of the instance", + since="4.20") + private Boolean purgeResources; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -273,7 +279,7 @@ public Integer getMemory() { public String getServiceOfferingName() { if (StringUtils.isEmpty(serviceOfferingName)) { - throw new InvalidParameterValueException("Failed to create service offering because offering name has not been spified."); + throw new InvalidParameterValueException("Failed to create service offering because offering name has not been specified."); } return serviceOfferingName; } @@ -481,6 +487,10 @@ public boolean getEncryptRoot() { return false; } + public boolean isPurgeResources() { + return Boolean.TRUE.equals(purgeResources); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java index 7d6bae860834..e580f0d9f41a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java @@ -89,6 +89,11 @@ public class UpdateServiceOfferingCmd extends BaseCmd { description = "state of the service offering") private String serviceOfferingState; + @Parameter(name = ApiConstants.PURGE_RESOURCES, type = CommandType.BOOLEAN, + description = "Whether to cleanup VM and its associated resource upon expunge", + since="4.20") + private Boolean purgeResources; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -185,6 +190,10 @@ public State getState() { return state; } + public boolean isPurgeResources() { + return Boolean.TRUE.equals(purgeResources); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java new file mode 100644 index 000000000000..b6833f097336 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java @@ -0,0 +1,131 @@ +// 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 org.apache.cloudstack.api.command.admin.resource; + + +import java.util.Date; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.PurgeExpungedResourcesResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.resource.ResourceCleanupService; + +import com.cloud.event.EventTypes; + +@APICommand(name = "purgeExpungedResources", + description = "Purge expunged resources", + responseObject = SuccessResponse.class, + responseView = ResponseObject.ResponseView.Full, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = {RoleType.Admin}, + since = "4.20") +public class PurgeExpungedResourcesCmd extends BaseAsyncCmd { + + @Inject + ResourceCleanupService resourceCleanupService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, + description = "The type of the resource which need to be purged. Supported types: " + + "VirtualMachine") + private String resourceType; + + @Parameter(name = ApiConstants.BATCH_SIZE, type = CommandType.LONG, + description = "The size of batch used during purging") + private Long batchSize; + + @Parameter(name = ApiConstants.START_DATE, + type = CommandType.DATE, + description = "The start date range of the expunged resources used for purging " + + "(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\")") + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, + type = CommandType.DATE, + description = "The end date range of the expunged resources used for purging " + + "(use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\")") + private Date endDate; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public String getResourceType() { + return resourceType; + } + + public Long getBatchSize() { + return batchSize; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_PURGE_EXPUNGED_RESOURCES; + } + + @Override + public String getEventDescription() { + return "Purging expunged resources"; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + long result = resourceCleanupService.purgeExpungedResources(this); + PurgeExpungedResourcesResponse response = new PurgeExpungedResourcesResponse(); + response.setResourceCount(result); + response.setObjectName(getCommandName().toLowerCase()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getLocalizedMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/PurgeExpungedResourcesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/PurgeExpungedResourcesResponse.java new file mode 100644 index 000000000000..3807d0d5b168 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/PurgeExpungedResourcesResponse.java @@ -0,0 +1,39 @@ +// 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 org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class PurgeExpungedResourcesResponse extends BaseResponse { + + @SerializedName(ApiConstants.RESOURCE_COUNT) + @Param(description = "The count of the purged expunged resources") + private Long resourceCount; + + public Long getResourceCount() { + return resourceCount; + } + + public void setResourceCount(Long resourceCount) { + this.resourceCount = resourceCount; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index c7740c19214b..0622b936f6e0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -234,6 +234,10 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "true if virtual machine root disk will be encrypted on storage", since = "4.18") private Boolean encryptRoot; + @SerializedName(ApiConstants.PURGE_RESOURCES) + @Param(description = "Whether to cleanup VM and its associated resource upon expunge", since = "4.20") + private Boolean purgeResources; + public ServiceOfferingResponse() { } @@ -555,4 +559,8 @@ public String getDiskOfferingDisplayText() { } public void setEncryptRoot(Boolean encrypt) { this.encryptRoot = encrypt; } + + public void setPurgeResources(Boolean purgeResources) { + this.purgeResources = purgeResources; + } } diff --git a/api/src/main/java/org/apache/cloudstack/resource/ResourceCleanupService.java b/api/src/main/java/org/apache/cloudstack/resource/ResourceCleanupService.java new file mode 100644 index 000000000000..0d72edb07489 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/resource/ResourceCleanupService.java @@ -0,0 +1,74 @@ +// 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 org.apache.cloudstack.resource; + +import org.apache.cloudstack.api.command.admin.resource.PurgeExpungedResourcesCmd; +import org.apache.cloudstack.framework.config.ConfigKey; + +import com.cloud.vm.VirtualMachine; + +public interface ResourceCleanupService { + int MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS = 3 * 60; + ConfigKey ExpungedResourcePurgeEnabled = new ConfigKey<>("Advanced", Boolean.class, + "expunged.resources.purge.enabled", "false", + "Whether to run a background task to purge the DB records of the expunged resources", + false, ConfigKey.Scope.Global); + ConfigKey ExpungedResourcePurgeResources = new ConfigKey<>("Advanced", String.class, + "expunged.resources.purge.resources", "", + "A comma-separated list of resource types that will be considered by the background task " + + "to purge the DB records of the expunged resources. Currently only VirtualMachine is supported. " + + "An empty value will result in considering all resource types for purging", + false, ConfigKey.Scope.Global); + ConfigKey ExpungedResourcesPurgeInterval = new ConfigKey<>("Advanced", Integer.class, + "expunged.resources.purge.interval", "86400", + "Interval (in seconds) for the background task to purge the DB records of the expunged resources", + false); + ConfigKey ExpungedResourcesPurgeDelay = new ConfigKey<>("Advanced", Integer.class, + "expunged.resources.purge.delay", "300", + "Initial delay (in seconds) to start the background task to purge the DB records of the " + + "expunged resources task", false); + ConfigKey ExpungedResourcesPurgeBatchSize = new ConfigKey<>("Advanced", Integer.class, + "expunged.resources.purge.batch.size", "50", + "Batch size to be used during purging of the DB records of the expunged resources", + true); + ConfigKey ExpungedResourcesPurgeStartTime = new ConfigKey<>("Advanced", String.class, + "expunged.resources.purge.start.time", "", + "Start time to be used by the background task to purge the DB records of the expunged " + + "resources. Use format \"yyyy-MM-dd\" or \"yyyy-MM-dd HH:mm:ss\"", true); + ConfigKey ExpungedResourcesPurgeKeepPastDays = new ConfigKey<>("Advanced", Integer.class, + "expunged.resources.purge.keep.past.days", "30", + "The number of days in the past from the execution time of the background task to purge " + + "the DB records of the expunged resources for which the expunged resources must not be purged. " + + "To enable purging DB records of the expunged resource till the execution of the background " + + "task, set the value to zero.", true); + ConfigKey ExpungedResourcePurgeJobDelay = new ConfigKey<>("Advanced", Integer.class, + "expunged.resource.purge.job.delay", + String.valueOf(MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS), + String.format("Delay (in seconds) to execute the purging of the DB records of an expunged resource " + + "initiated by the configuration in the offering. Minimum value should be %d seconds " + + "and if a lower value is set then the minimum value will be used", + MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS), + true); + + enum ResourceType { + VirtualMachine + } + + long purgeExpungedResources(PurgeExpungedResourcesCmd cmd); + void purgeExpungedVmResourcesLaterIfNeeded(VirtualMachine vm); +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java index f69e8cea4f3d..6daa5de07cbf 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java @@ -37,4 +37,22 @@ public void testGetDisplayTextWhenEmpty() { Assert.assertEquals(createServiceOfferingCmd.getDisplayText(), netName); } + @Test + public void testIsPurgeResourcesNoOrNullValue() { + Assert.assertFalse(createServiceOfferingCmd.isPurgeResources()); + ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", false); + Assert.assertFalse(createServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesFalse() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", false); + Assert.assertFalse(createServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesTrue() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", true); + Assert.assertTrue(createServiceOfferingCmd.isPurgeResources()); + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmdTest.java new file mode 100644 index 000000000000..1bb2be041e18 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmdTest.java @@ -0,0 +1,51 @@ +// 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 org.apache.cloudstack.api.command.admin.offering; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateServiceOfferingCmdTest { + + @InjectMocks + private UpdateServiceOfferingCmd updateServiceOfferingCmd; + + @Test + public void testIsPurgeResourcesNoOrNullValue() { + Assert.assertFalse(updateServiceOfferingCmd.isPurgeResources()); + ReflectionTestUtils.setField(updateServiceOfferingCmd, "purgeResources", false); + Assert.assertFalse(updateServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesFalse() { + ReflectionTestUtils.setField(updateServiceOfferingCmd, "purgeResources", false); + Assert.assertFalse(updateServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesTrue() { + ReflectionTestUtils.setField(updateServiceOfferingCmd, "purgeResources", true); + Assert.assertTrue(updateServiceOfferingCmd.isPurgeResources()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java new file mode 100644 index 000000000000..a628f13275c5 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java @@ -0,0 +1,104 @@ +// 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 org.apache.cloudstack.api.command.admin.resource; + +import static org.junit.Assert.assertNull; + +import java.util.Date; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.PurgeExpungedResourcesResponse; +import org.apache.cloudstack.resource.ResourceCleanupService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class PurgeExpungedResourcesCmdTest { + @Mock + ResourceCleanupService resourceCleanupService; + + @Spy + @InjectMocks + PurgeExpungedResourcesCmd spyCmd = Mockito.spy(new PurgeExpungedResourcesCmd()); + + @Test + public void testGetResourceType() { + PurgeExpungedResourcesCmd cmd = new PurgeExpungedResourcesCmd(); + assertNull(cmd.getResourceType()); + ReflectionTestUtils.setField(cmd, "resourceType", ResourceCleanupService.ResourceType.VirtualMachine.toString()); + Assert.assertEquals(ResourceCleanupService.ResourceType.VirtualMachine.toString(), cmd.getResourceType()); + } + + @Test + public void testGetBatchSize() { + PurgeExpungedResourcesCmd cmd = new PurgeExpungedResourcesCmd(); + assertNull(cmd.getBatchSize()); + Long batchSize = 100L; + ReflectionTestUtils.setField(cmd, "batchSize", batchSize); + Assert.assertEquals(batchSize, cmd.getBatchSize()); + } + + @Test + public void testGetStartDate() { + PurgeExpungedResourcesCmd cmd = new PurgeExpungedResourcesCmd(); + assertNull(cmd.getStartDate()); + Date date = new Date(); + ReflectionTestUtils.setField(cmd, "startDate", date); + Assert.assertEquals(date, cmd.getStartDate()); + } + + @Test + public void testGetEndDate() { + PurgeExpungedResourcesCmd cmd = new PurgeExpungedResourcesCmd(); + assertNull(cmd.getEndDate()); + Date date = new Date(); + ReflectionTestUtils.setField(cmd, "endDate", date); + Assert.assertEquals(date, cmd.getEndDate()); + } + + @Test + public void testExecute() { + final PurgeExpungedResourcesResponse[] executeResponse = new PurgeExpungedResourcesResponse[1]; + Long result = 100L; + Mockito.when(resourceCleanupService.purgeExpungedResources(Mockito.any())).thenReturn(result); + Mockito.doAnswer((Answer) invocation -> { + executeResponse[0] = (PurgeExpungedResourcesResponse)invocation.getArguments()[0]; + return null; + }).when(spyCmd).setResponseObject(Mockito.any()); + spyCmd.execute(); + PurgeExpungedResourcesResponse response = executeResponse[0]; + Assert.assertNotNull(response); + Assert.assertEquals(result, response.getResourceCount()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteException() { + Mockito.doThrow(CloudRuntimeException.class).when(resourceCleanupService).purgeExpungedResources(Mockito.any()); + spyCmd.execute(); + } +} diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 110592161f96..41bd74f11924 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -348,4 +348,6 @@ void implementNetworkElementsAndResources(DeployDestination dest, ReservationCon Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter datacenter, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException; void unmanageNics(VirtualMachineProfile vm); + + void expungeLbVmRefs(List vmIds, Long batchSize); } diff --git a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java index 72737d0b04d2..ae47b1d76ed8 100644 --- a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java +++ b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java @@ -156,4 +156,5 @@ enum Step { String getHaTag(); DeploymentPlanner getHAPlanner(); + int expungeWorkItemsByVmList(List vmIds, Long batchSize); } 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 4772a5ad92de..e2e11965d3e4 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -90,6 +90,7 @@ import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resource.ResourceCleanupService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; @@ -402,6 +403,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private VpcDao vpcDao; @Inject private DomainDao domainDao; + @Inject + ResourceCleanupService resourceCleanupService; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -691,6 +694,7 @@ protected void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableExcepti if (logger.isDebugEnabled()) { logger.debug("Expunged " + vm); } + resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm); } private void handleUnsuccessfulExpungeOperation(List finalizeExpungeCommands, List nicExpungeCommands, diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index fded0a38dde7..50d083fba9cb 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -4738,6 +4738,19 @@ public void unmanageNics(VirtualMachineProfile vm) { } } + @Override + public void expungeLbVmRefs(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(networkElements) || CollectionUtils.isEmpty(vmIds)) { + return; + } + for (NetworkElement element : networkElements) { + if (element instanceof LoadBalancingServiceProvider) { + LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element; + lbProvider.expungeLbVmRefs(vmIds, batchSize); + } + } + } + @Override public String getConfigComponentName() { return NetworkOrchestrationService.class.getSimpleName(); diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDao.java b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDao.java index 4b25c63403e2..718511746c2f 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDao.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDao.java @@ -35,4 +35,6 @@ public interface AutoScaleVmGroupVmMapDao extends GenericDao vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java index 8fca4c26f9a7..1ae55d97da2c 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java @@ -18,7 +18,10 @@ import java.util.List; +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.network.as.AutoScaleVmGroupVmMapVO; @@ -31,9 +34,6 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.VMInstanceDao; -import javax.annotation.PostConstruct; -import javax.inject.Inject; - @Component public class AutoScaleVmGroupVmMapDaoImpl extends GenericDaoBase implements AutoScaleVmGroupVmMapDao { @@ -115,4 +115,16 @@ public boolean removeByGroup(long vmGroupId) { sc.setParameters("vmGroupId", vmGroupId); return remove(sc) >= 0; } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java index b1b1e1cf7571..3f8c36ac94ed 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java @@ -105,4 +105,6 @@ public interface IPAddressDao extends GenericDao { void buildQuarantineSearchCriteria(SearchCriteria sc); IPAddressVO findBySourceNetworkIdAndDatacenterIdAndState(long sourceNetworkId, long dataCenterId, State state); + + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java index ca779f7e9cee..aa143838c343 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.resourcedetail.dao.UserIpAddressDetailsDao; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.dc.Vlan.VlanType; @@ -561,4 +562,16 @@ public IPAddressVO findBySourceNetworkIdAndDatacenterIdAndState(long sourceNetwo sc.setParameters("state", State.Free); return findOneBy(sc); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getAssociatedWithVmId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDao.java b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDao.java index ac3845beffe4..b1831b407a41 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDao.java @@ -16,10 +16,14 @@ // under the License. package com.cloud.network.dao; +import java.util.List; + import com.cloud.utils.db.GenericDao; public interface InlineLoadBalancerNicMapDao extends GenericDao { InlineLoadBalancerNicMapVO findByPublicIpAddress(String publicIpAddress); InlineLoadBalancerNicMapVO findByNicId(long nicId); + int expungeByNicList(List nicIds, Long batchSize); + } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImpl.java index 1c3f231f9c1d..d64ba8b4155f 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImpl.java @@ -17,9 +17,13 @@ package com.cloud.network.dao; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @Component @@ -41,4 +45,15 @@ public InlineLoadBalancerNicMapVO findByNicId(long nicId) { return findOneBy(sc); } + @Override + public int expungeByNicList(List nicIds, Long batchSize) { + if (CollectionUtils.isEmpty(nicIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("nicIds", sb.entity().getNicId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("nicIds", nicIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDao.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDao.java index a25534b7010f..be2941d5cb2f 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDao.java @@ -42,4 +42,5 @@ public interface LoadBalancerVMMapDao extends GenericDao vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java index b32320a84cb0..dc37cdeefe3d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java @@ -18,11 +18,12 @@ import java.util.List; - +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; @@ -135,4 +136,16 @@ public List listByLoadBalancerIdAndVmId(long loadBalancerId sc.addAnd("instanceId", SearchCriteria.Op.EQ, instanceId); return listBy(sc); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDao.java b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDao.java index ebc0f1af2271..0516e26e13a7 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDao.java @@ -18,8 +18,12 @@ package com.cloud.network.dao; +import java.util.List; + import com.cloud.utils.db.GenericDao; public interface OpRouterMonitorServiceDao extends GenericDao { + int expungeByVmList(List vmIds, Long batchSize); + } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImpl.java index 451320ac9b6c..a8e818cfb189 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImpl.java @@ -17,10 +17,27 @@ package com.cloud.network.dao; -import com.cloud.utils.db.GenericDaoBase; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + @Component public class OpRouterMonitorServiceDaoImpl extends GenericDaoBase implements OpRouterMonitorServiceDao { + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java index b89d04ad15a0..8cd114b7fc4f 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java @@ -47,4 +47,5 @@ public interface PortForwardingRulesDao extends GenericDao listByNetworkAndDestIpAddr(String ip4Address, long networkId); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java index 29cba516d720..3a404b3f2df3 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java @@ -20,6 +20,7 @@ import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.network.dao.FirewallRulesCidrsDao; @@ -170,4 +171,16 @@ public PortForwardingRuleVO findByIdAndIp(long id, String secondaryIp) { sc.setParameters("dstIp", secondaryIp); return findOneBy(sc); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVirtualMachineId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDao.java b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDao.java index 98fc8c8687b8..5023aaa3794c 100644 --- a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDao.java +++ b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDao.java @@ -17,10 +17,12 @@ package com.cloud.secstorage; import java.util.Date; +import java.util.List; import com.cloud.utils.db.GenericDao; public interface CommandExecLogDao extends GenericDao { public void expungeExpiredRecords(Date cutTime); public Integer getCopyCmdCountForSSVM(Long id); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDaoImpl.java b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDaoImpl.java index f89a1bbf4ccb..a37acdf60298 100644 --- a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDaoImpl.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.GenericDaoBase; @@ -57,4 +58,16 @@ public Integer getCopyCmdCountForSSVM(Long id) { List copyCmds = customSearch(sc, null); return copyCmds.size(); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java index 998d0bbd724c..171634fb1044 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java @@ -57,4 +57,5 @@ public interface SnapshotDao extends GenericDao, StateDao listByIds(Object... ids); + List searchByVolumes(List volumeIds); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java index 030d10d66827..f5fc9c47d036 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java @@ -18,11 +18,13 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.server.ResourceTag.ResourceObjectType; @@ -285,4 +287,16 @@ public List listByStatusNotIn(long volumeId, Snapshot.State... statu sc.setParameters("status", (Object[]) status); return listBy(sc, null); } + + @Override + public List searchByVolumes(List volumeIds) { + if (CollectionUtils.isEmpty(volumeIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("volumeIds", sb.entity().getVolumeId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("volumeIds", volumeIds.toArray()); + return search(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index 7ef7fd44b244..db1563f3ff16 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -159,4 +159,5 @@ public interface VolumeDao extends GenericDao, StateDao listAllocatedVolumesForAccountDiskOfferingIdsAndNotForVms(long accountId, List diskOfferingIds, List vmIds); + List searchRemovedByVms(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index 2e68dcae3c57..551ff7d2d781 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -27,14 +27,12 @@ import javax.inject.Inject; -import com.cloud.configuration.Resource; -import com.cloud.utils.db.Transaction; -import com.cloud.utils.db.TransactionCallback; import org.apache.cloudstack.reservation.ReservationVO; import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import com.cloud.configuration.Resource; import com.cloud.exception.InvalidParameterValueException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.server.ResourceTag.ResourceObjectType; @@ -48,12 +46,15 @@ import com.cloud.tags.dao.ResourceTagDao; import com.cloud.utils.Pair; import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.UpdateBuilder; import com.cloud.utils.exception.CloudRuntimeException; @@ -895,4 +896,18 @@ public VolumeVO persist(VolumeVO entity) { return volume; }); } + + @Override + public List searchRemovedByVms(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.NNULL); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + Filter filter = new Filter(VolumeVO.class, "id", true, 0L, batchSize); + return searchIncludingRemoved(sc, filter, null, false); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/ItWorkDao.java b/engine/schema/src/main/java/com/cloud/vm/ItWorkDao.java index 2d4a5e138fea..ab07d6989fae 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ItWorkDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/ItWorkDao.java @@ -41,5 +41,6 @@ public interface ItWorkDao extends GenericDao { boolean updateStep(ItWorkVO work, Step step); List listWorkInProgressFor(long nodeId); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/ItWorkDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/ItWorkDaoImpl.java index ff727904dcb7..0cc0a0844431 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ItWorkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/ItWorkDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; - +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.GenericDaoBase; @@ -103,4 +103,16 @@ public List listWorkInProgressFor(long nodeId) { return search(sc, null); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java index 71b1aed1938d..79158dd13b26 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java @@ -23,6 +23,7 @@ import com.cloud.utils.db.GenericDao; import java.util.Date; +import java.util.List; public interface ConsoleSessionDao extends GenericDao { @@ -33,4 +34,6 @@ public interface ConsoleSessionDao extends GenericDao { int expungeSessionsOlderThanDate(Date date); void acquireSession(String sessionUuid); + + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index 8e7e229622e8..487096744512 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -20,6 +20,9 @@ package com.cloud.vm.dao; import java.util.Date; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; @@ -65,5 +68,15 @@ public void acquireSession(String sessionUuid) { update(consoleSessionVO.getId(), consoleSessionVO); } - + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java index 23c26ea07180..d34b03c4cb08 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java @@ -100,4 +100,5 @@ public interface NicDao extends GenericDao { NicVO findByIpAddressAndVmType(String ip, VirtualMachine.Type vmType); List listByNetworkIdAndType(long networkId, VirtualMachine.Type vmType); + List searchRemovedByVms(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index 3eee1d4e749d..7d1af1982ae1 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -17,11 +17,13 @@ package com.cloud.vm.dao; import java.net.URI; +import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.Filter; @@ -428,4 +430,18 @@ public List listByNetworkIdAndType(long networkId, VirtualMachine.Type vm sc.setParameters("vmType", vmType); return listBy(sc); } + + @Override + public List searchRemovedByVms(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.NNULL); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + Filter filter = new Filter(NicVO.class, "id", true, 0L, batchSize); + return searchIncludingRemoved(sc, filter, null, false); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDao.java index 69d9c00e1e0b..7bae64a6acbd 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDao.java @@ -29,4 +29,5 @@ public interface NicExtraDhcpOptionDao extends GenericDao extraDhcpOptions); + int expungeByNicList(List nicIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java index 3056c73938e7..0f3679d66a37 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.vm.dao; -import org.springframework.stereotype.Component; - import java.util.List; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + import com.cloud.utils.db.DB; import com.cloud.utils.db.GenericDaoBase; - import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.NicExtraDhcpOption; @@ -74,4 +74,15 @@ public void removeByNicId(long nicId) { expunge(sc); } + @Override + public int expungeByNicList(List nicIds, Long batchSize) { + if (CollectionUtils.isEmpty(nicIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("nicIds", sb.entity().getNicId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("nicIds", nicIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java index cbb52e57282b..ff7089ca4276 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java @@ -55,4 +55,5 @@ public interface NicSecondaryIpDao extends GenericDao { List listSecondaryIpUsingKeyword(long nicId, String keyword); int moveSecondaryIps(long fromNicId, long toNicId); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java index a56d35d5a63d..563b3279520c 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -192,4 +193,16 @@ public int moveSecondaryIps(long fromNicId, long toNicId) { return update(update, sc); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVmId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 42c00231aac1..ac25eac34800 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -165,4 +165,7 @@ public interface VMInstanceDao extends GenericDao, StateDao< void updateSystemVmTemplateId(long templateId, Hypervisor.HypervisorType hypervisorType); List listByHostOrLastHostOrHostPod(List hostIds, long podId); + + List searchRemovedByRemoveDate(final Date startDate, final Date endDate, final Long batchSize, + List skippedVmIds); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index b7b787b00451..78eb08afadca 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -28,6 +28,7 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.host.HostVO; @@ -39,6 +40,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.Attribute; import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; @@ -1016,4 +1018,26 @@ public List listByHostOrLastHostOrHostPod(List hostIds, long sc.setParameters("podId", String.valueOf(podId)); return listBy(sc); } + + @Override + public List searchRemovedByRemoveDate(Date startDate, Date endDate, Long batchSize, + List skippedVmIds) { + SearchBuilder sb = createSearchBuilder(); + sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.NNULL); + sb.and("startDate", sb.entity().getRemoved(), SearchCriteria.Op.GTEQ); + sb.and("endDate", sb.entity().getRemoved(), SearchCriteria.Op.LTEQ); + sb.and("skippedVmIds", sb.entity().getId(), Op.NOTIN); + SearchCriteria sc = sb.create(); + if (startDate != null) { + sc.setParameters("startDate", startDate); + } + if (endDate != null) { + sc.setParameters("endDate", endDate); + } + if (CollectionUtils.isNotEmpty(skippedVmIds)) { + sc.setParameters("skippedVmIds", skippedVmIds.toArray()); + } + Filter filter = new Filter(VMInstanceVO.class, "id", true, 0L, batchSize); + return searchIncludingRemoved(sc, filter, null, false); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java index 31999ef15d66..0143aaa1e735 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java @@ -38,4 +38,6 @@ public interface VMSnapshotDao extends GenericDao, StateDao< VMSnapshotVO findByName(Long vmId, String name); List listByAccountId(Long accountId); + List searchByVms(List vmIds); + List searchRemovedByVms(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java index 062960130aca..ab8f5f2cd849 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java @@ -17,12 +17,14 @@ package com.cloud.vm.snapshot.dao; +import java.util.ArrayList; import java.util.Date; import java.util.List; - +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -180,4 +182,29 @@ public boolean updateState(State currentState, Event event, State nextState, VMS return rows > 0; } + @Override + public List searchByVms(List vmIds) { + if (CollectionUtils.isEmpty(vmIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVmId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return search(sc, null); + } + + @Override + public List searchRemovedByVms(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVmId(), SearchCriteria.Op.IN); + sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.NNULL); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + Filter filter = new Filter(VMSnapshotVO.class, "id", true, 0L, batchSize); + return searchIncludingRemoved(sc, filter, null, false); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java index 5a173191be1f..8f3d264da981 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java @@ -97,4 +97,6 @@ public interface ResourceDetailsDao extends GenericDao public void addDetail(long resourceId, String key, String value, boolean display); public List findResourceIdsByNameAndValueIn(String name, Object[] values); + + public long batchExpungeForResources(List ids, Long batchSize); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java index 37ebfebf5ddd..4205a7823e44 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java @@ -21,13 +21,14 @@ import java.util.Map; import org.apache.cloudstack.api.ResourceDetail; +import org.apache.commons.collections.CollectionUtils; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.db.TransactionLegacy; public abstract class ResourceDetailsDaoBase extends GenericDaoBase implements ResourceDetailsDao { private SearchBuilder AllFieldsSearch; @@ -201,4 +202,17 @@ public List findResourceIdsByNameAndValueIn(String name, Object[] values) return customSearch(sc, null); } + + @Override + public long batchExpungeForResources(final List ids, final Long batchSize) { + if (CollectionUtils.isEmpty(ids)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("ids", sb.entity().getResourceId(), Op.IN); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("ids", ids.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java index 344ff8b2a699..4cd29b465eeb 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java @@ -106,4 +106,6 @@ public interface SnapshotDataStoreDao extends GenericDao snapshotIds, Long batchSize); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java index c095f4222e76..5bf67eb38819 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java @@ -559,4 +559,16 @@ public void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, Dat ref.setDisplay(display); update(ref.getId(), ref); } + + @Override + public int expungeBySnapshotList(final List snapshotIds, final Long batchSize) { + if (CollectionUtils.isEmpty(snapshotIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("snapshotIds", sb.entity().getSnapshotId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("snapshotIds", snapshotIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java index c3a4b58fbd50..3381391e70e4 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java @@ -59,4 +59,6 @@ public interface VolumeDataStoreDao extends GenericDao, List listByVolume(long volumeId, long storeId); List listByStoreIdAndInstallPaths(Long storeId, List paths); + + int expungeByVolumeList(List volumeIds, Long batchSize); } diff --git a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java index e13ad42ec808..6de8960ae747 100644 --- a/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImplTest.java @@ -19,11 +19,9 @@ package com.cloud.network.as.dao; -import com.cloud.network.as.AutoScaleVmGroupVmMapVO; -import com.cloud.utils.db.GenericSearchBuilder; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; -import com.cloud.vm.VirtualMachine; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.junit.Assert; import org.junit.Before; @@ -33,9 +31,13 @@ import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; -import java.util.Arrays; -import java.util.List; +import com.cloud.network.as.AutoScaleVmGroupVmMapVO; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VirtualMachine; @RunWith(MockitoJUnitRunner.class) public class AutoScaleVmGroupVmMapDaoImplTest { @@ -198,4 +200,33 @@ public void testRemoveByGroupFailed() { Mockito.verify(searchCriteriaAutoScaleVmGroupVmMapVOMock).setParameters("vmGroupId", groupId); Mockito.verify(AutoScaleVmGroupVmMapDaoImplSpy).remove(searchCriteriaAutoScaleVmGroupVmMapVOMock); } + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, AutoScaleVmGroupVmMapDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, AutoScaleVmGroupVmMapDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(AutoScaleVmGroupVmMapDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(AutoScaleVmGroupVmMapDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final AutoScaleVmGroupVmMapVO mockedVO = Mockito.mock(AutoScaleVmGroupVmMapVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), AutoScaleVmGroupVmMapDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(AutoScaleVmGroupVmMapDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } } diff --git a/engine/schema/src/test/java/com/cloud/network/dao/IPAddressDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/dao/IPAddressDaoImplTest.java new file mode 100644 index 000000000000..d8f6a08d8d33 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/dao/IPAddressDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.network.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class IPAddressDaoImplTest { + + @Spy + IPAddressDaoImpl ipAddressDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, ipAddressDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, ipAddressDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(ipAddressDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(ipAddressDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final IPAddressVO mockedVO = Mockito.mock(IPAddressVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), ipAddressDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(ipAddressDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImplTest.java new file mode 100644 index 000000000000..8e06c7618f6a --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.network.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class InlineLoadBalancerNicMapDaoImplTest { + + @Spy + InlineLoadBalancerNicMapDaoImpl inlineLoadBalancerNicMapDaoImplSpy; + + @Test + public void testExpungeByNicListNoVms() { + Assert.assertEquals(0, inlineLoadBalancerNicMapDaoImplSpy.expungeByNicList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, inlineLoadBalancerNicMapDaoImplSpy.expungeByNicList( + null, 100L)); + } + + @Test + public void testExpungeByNicList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(inlineLoadBalancerNicMapDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(inlineLoadBalancerNicMapDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final InlineLoadBalancerNicMapVO mockedVO = Mockito.mock(InlineLoadBalancerNicMapVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), inlineLoadBalancerNicMapDaoImplSpy.expungeByNicList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("nicIds", array); + Mockito.verify(inlineLoadBalancerNicMapDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVMMapDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVMMapDaoImplTest.java new file mode 100644 index 000000000000..fa9571949031 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVMMapDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.network.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class LoadBalancerVMMapDaoImplTest { + + @Spy + LoadBalancerVMMapDaoImpl loadBalancerVMMapDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, loadBalancerVMMapDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, loadBalancerVMMapDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(loadBalancerVMMapDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(loadBalancerVMMapDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final LoadBalancerVMMapVO mockedVO = Mockito.mock(LoadBalancerVMMapVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), loadBalancerVMMapDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(loadBalancerVMMapDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImplTest.java new file mode 100644 index 000000000000..7d0b1b069baa --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.network.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class OpRouterMonitorServiceDaoImplTest { + + @Spy + OpRouterMonitorServiceDaoImpl opRouterMonitorServiceDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, opRouterMonitorServiceDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, opRouterMonitorServiceDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(opRouterMonitorServiceDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(opRouterMonitorServiceDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final OpRouterMonitorServiceVO mockedVO = Mockito.mock(OpRouterMonitorServiceVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), opRouterMonitorServiceDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(opRouterMonitorServiceDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImplTest.java new file mode 100644 index 000000000000..c60e9b1f1bfa --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImplTest.java @@ -0,0 +1,68 @@ +// 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.network.rules.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.network.rules.PortForwardingRuleVO; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class PortForwardingRulesDaoImplTest { + + @Spy + PortForwardingRulesDaoImpl portForwardingRulesDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, portForwardingRulesDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, portForwardingRulesDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(portForwardingRulesDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(portForwardingRulesDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final PortForwardingRuleVO mockedVO = Mockito.mock(PortForwardingRuleVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), portForwardingRulesDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(portForwardingRulesDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/secstorage/CommandExecLogDaoImplTest.java b/engine/schema/src/test/java/com/cloud/secstorage/CommandExecLogDaoImplTest.java new file mode 100644 index 000000000000..f86df6bdd36f --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/secstorage/CommandExecLogDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.secstorage; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class CommandExecLogDaoImplTest { + + @Spy + CommandExecLogDaoImpl commandExecLogDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, commandExecLogDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, commandExecLogDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(commandExecLogDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(commandExecLogDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final CommandExecLogVO mockedVO = Mockito.mock(CommandExecLogVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), commandExecLogDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(commandExecLogDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java index 7968ee4a375e..9445efeb089c 100644 --- a/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java @@ -26,16 +26,25 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.storage.VolumeVO; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; @RunWith(MockitoJUnitRunner.class) @@ -48,6 +57,7 @@ public class VolumeDaoImplTest { private static MockedStatic mockedTransactionLegacy; + @Spy private final VolumeDaoImpl volumeDao = new VolumeDaoImpl(); @BeforeClass @@ -102,4 +112,34 @@ public void testListPoolIdsByVolumeCount_without_cluster_details() throws SQLExc verify(preparedStatementMock, times(2)).setLong(anyInt(), anyLong()); verify(preparedStatementMock, times(1)).executeQuery(); } + + @Test + public void testSearchRemovedByVmsNoVms() { + Assert.assertTrue(CollectionUtils.isEmpty(volumeDao.searchRemovedByVms( + new ArrayList<>(), 100L))); + Assert.assertTrue(CollectionUtils.isEmpty(volumeDao.searchRemovedByVms( + null, 100L))); + } + + @Test + public void testSearchRemovedByVms() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(volumeDao).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb); + final VolumeVO mockedVO = Mockito.mock(VolumeVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + volumeDao.searchRemovedByVms(List.of(1L, 2L), batchSize); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(volumeDao, Mockito.times(1)).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + } + } diff --git a/engine/schema/src/test/java/com/cloud/vm/ItWorkDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/ItWorkDaoImplTest.java new file mode 100644 index 000000000000..04bc125e05f8 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/ItWorkDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class ItWorkDaoImplTest { + + @Spy + ItWorkDaoImpl itWorkDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, itWorkDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, itWorkDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(itWorkDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(itWorkDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final ItWorkVO mockedVO = Mockito.mock(ItWorkVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), itWorkDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(itWorkDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/ConsoleSessionDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/ConsoleSessionDaoImplTest.java new file mode 100644 index 000000000000..c9919e26af6c --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/dao/ConsoleSessionDaoImplTest.java @@ -0,0 +1,68 @@ +// 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.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.ConsoleSessionVO; + +@RunWith(MockitoJUnitRunner.class) +public class ConsoleSessionDaoImplTest { + + @Spy + ConsoleSessionDaoImpl consoleSessionDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, consoleSessionDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, consoleSessionDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(consoleSessionDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(consoleSessionDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final ConsoleSessionVO mockedVO = Mockito.mock(ConsoleSessionVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), consoleSessionDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(consoleSessionDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/NicDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/NicDaoImplTest.java new file mode 100644 index 000000000000..506fdb7fc92b --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/dao/NicDaoImplTest.java @@ -0,0 +1,69 @@ +// 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.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.NicVO; + +@RunWith(MockitoJUnitRunner.class) +public class NicDaoImplTest { + + @Spy + NicDaoImpl nicDaoImplSpy; + + @Test + public void testSearchRemovedByVmsNoVms() { + Assert.assertTrue(CollectionUtils.isEmpty(nicDaoImplSpy.searchRemovedByVms( + new ArrayList<>(), 100L))); + Assert.assertTrue(CollectionUtils.isEmpty(nicDaoImplSpy.searchRemovedByVms( + null, 100L))); + } + + @Test + public void testSearchRemovedByVms() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(nicDaoImplSpy).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + Mockito.when(nicDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final NicVO mockedVO = Mockito.mock(NicVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + nicDaoImplSpy.searchRemovedByVms(List.of(1L, 2L), batchSize); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(nicDaoImplSpy, Mockito.times(1)).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImplTest.java new file mode 100644 index 000000000000..7a1e32e95caf --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/dao/NicExtraDhcpOptionDaoImplTest.java @@ -0,0 +1,68 @@ +// 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.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.NicExtraDhcpOptionVO; + +@RunWith(MockitoJUnitRunner.class) +public class NicExtraDhcpOptionDaoImplTest { + + @Spy + NicExtraDhcpOptionDaoImpl nicExtraDhcpOptionDaoImplSpy; + + @Test + public void testExpungeByNicListNoVms() { + Assert.assertEquals(0, nicExtraDhcpOptionDaoImplSpy.expungeByNicList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, nicExtraDhcpOptionDaoImplSpy.expungeByNicList( + null, 100L)); + } + + @Test + public void testExpungeByNicList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(nicExtraDhcpOptionDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(nicExtraDhcpOptionDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final NicExtraDhcpOptionVO mockedVO = Mockito.mock(NicExtraDhcpOptionVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), nicExtraDhcpOptionDaoImplSpy.expungeByNicList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("nicIds", array); + Mockito.verify(nicExtraDhcpOptionDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/NicSecondaryIpDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/NicSecondaryIpDaoImplTest.java new file mode 100644 index 000000000000..a9f798dbc017 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/dao/NicSecondaryIpDaoImplTest.java @@ -0,0 +1,67 @@ +// 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.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class NicSecondaryIpDaoImplTest { + + @Spy + NicSecondaryIpDaoImpl nicSecondaryIpDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, nicSecondaryIpDaoImplSpy.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, nicSecondaryIpDaoImplSpy.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(nicSecondaryIpDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(nicSecondaryIpDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final NicSecondaryIpVO mockedVO = Mockito.mock(NicSecondaryIpVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), nicSecondaryIpDaoImplSpy.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(nicSecondaryIpDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java index 4a32dc083590..43679081550b 100644 --- a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java @@ -30,6 +30,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import org.joda.time.DateTime; @@ -37,10 +39,14 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -199,4 +205,29 @@ public void testUpdatePowerStateNoChangeMaxUpdatesInvalidStateVmRunning() { assertTrue(result); } + + @Test + public void testSearchRemovedByRemoveDate() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.when(vmInstanceDao.createSearchBuilder()).thenReturn(sb); + final VMInstanceVO mockedVO = Mockito.mock(VMInstanceVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + Mockito.doReturn(new ArrayList<>()).when(vmInstanceDao).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + Calendar cal = Calendar.getInstance(); + Date endDate = new Date(); + cal.setTime(endDate); + cal.add(Calendar.DATE, -1 * 10); + Date startDate = cal.getTime(); + vmInstanceDao.searchRemovedByRemoveDate(startDate, endDate, 50L, new ArrayList<>()); + Mockito.verify(sc).setParameters("startDate", startDate); + Mockito.verify(sc).setParameters("endDate", endDate); + Mockito.verify(sc, Mockito.never()).setParameters(Mockito.eq("skippedVmIds"), Mockito.any()); + Mockito.verify(vmInstanceDao, Mockito.times(1)).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + } } diff --git a/engine/schema/src/test/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImplTest.java b/engine/schema/src/test/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImplTest.java new file mode 100644 index 000000000000..e71518080d24 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImplTest.java @@ -0,0 +1,69 @@ +// 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.snapshot.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.snapshot.VMSnapshotVO; + +@RunWith(MockitoJUnitRunner.class) +public class VMSnapshotDaoImplTest { + + @Spy + VMSnapshotDaoImpl vmSnapshotDaoImplSpy; + + @Test + public void testSearchRemovedByVmsNoVms() { + Assert.assertTrue(CollectionUtils.isEmpty(vmSnapshotDaoImplSpy.searchRemovedByVms( + new ArrayList<>(), 100L))); + Assert.assertTrue(CollectionUtils.isEmpty(vmSnapshotDaoImplSpy.searchRemovedByVms( + null, 100L))); + } + + @Test + public void testSearchRemovedByVms() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(vmSnapshotDaoImplSpy).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + Mockito.when(vmSnapshotDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final VMSnapshotVO mockedVO = Mockito.mock(VMSnapshotVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + vmSnapshotDaoImplSpy.searchRemovedByVms(List.of(1L, 2L), batchSize); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(vmSnapshotDaoImplSpy, Mockito.times(1)).searchIncludingRemoved( + Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), + Mockito.eq(false)); + } +} diff --git a/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java b/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java new file mode 100644 index 000000000000..85240ab4a058 --- /dev/null +++ b/engine/schema/src/test/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImplTest.java @@ -0,0 +1,67 @@ +// 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 org.apache.cloudstack.storage.datastore.db; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class SnapshotDataStoreDaoImplTest { + + @Spy + SnapshotDataStoreDaoImpl snapshotDataStoreDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, snapshotDataStoreDaoImplSpy.expungeBySnapshotList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, snapshotDataStoreDaoImplSpy.expungeBySnapshotList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(snapshotDataStoreDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(snapshotDataStoreDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final SnapshotDataStoreVO mockedVO = Mockito.mock(SnapshotDataStoreVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), snapshotDataStoreDaoImplSpy.expungeBySnapshotList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("snapshotIds", array); + Mockito.verify(snapshotDataStoreDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java index 2c3d5ccfdde8..9eae1fc0711c 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java @@ -407,4 +407,16 @@ public boolean updateVolumeId(long srcVolId, long destVolId) { } return true; } + + @Override + public int expungeByVolumeList(List volumeIds, Long batchSize) { + if (CollectionUtils.isEmpty(volumeIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("volumeIds", sb.entity().getVolumeId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("volumeIds", volumeIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImplTest.java b/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImplTest.java new file mode 100644 index 000000000000..0cd886999564 --- /dev/null +++ b/engine/storage/src/test/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImplTest.java @@ -0,0 +1,68 @@ +// 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 org.apache.cloudstack.storage.image.db; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class VolumeDataStoreDaoImplTest { + + @Spy + VolumeDataStoreDaoImpl volumeDataStoreDaoImplSpy; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, volumeDataStoreDaoImplSpy.expungeByVolumeList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, volumeDataStoreDaoImplSpy.expungeByVolumeList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(volumeDataStoreDaoImplSpy).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(volumeDataStoreDaoImplSpy.createSearchBuilder()).thenReturn(sb); + final VolumeDataStoreVO mockedVO = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), volumeDataStoreDaoImplSpy.expungeByVolumeList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("volumeIds", array); + Mockito.verify(volumeDataStoreDaoImplSpy, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java index 2fc02301cb7a..de8838b09992 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java @@ -229,6 +229,24 @@ public interface GenericDao { */ int expunge(final SearchCriteria sc); + /** + * remove the entity bean specified by the search criteria and filter + * @param sc + * @param filter + * @return number of rows deleted + */ + int expunge(final SearchCriteria sc, final Filter filter); + + /** + * remove the entity bean specified by the search criteria and batchSize + * @param sc + * @param batchSize + * @return number of rows deleted + */ + int batchExpunge(final SearchCriteria sc, final Long batchSize); + + int expungeList(List ids); + /** * expunge the removed rows. */ diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index a09f323905e4..4202f6996c1b 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -20,6 +20,8 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigInteger; @@ -59,9 +61,12 @@ import javax.persistence.Table; import javax.persistence.TableGenerator; -import com.amazonaws.util.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import com.amazonaws.util.CollectionUtils; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; @@ -74,8 +79,6 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Ip; import com.cloud.utils.net.NetUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; @@ -1234,7 +1237,7 @@ public boolean expunge(final ID id) { // FIXME: Does not work for joins. @Override - public int expunge(final SearchCriteria sc) { + public int expunge(final SearchCriteria sc, final Filter filter) { if (sc == null) { throw new CloudRuntimeException("Call to throw new expunge with null search Criteria"); } @@ -1246,6 +1249,7 @@ public int expunge(final SearchCriteria sc) { if (sc != null && sc.getWhereClause().length() > 0) { str.append(sc.getWhereClause()); } + addFilter(str, filter); final String sql = str.toString(); @@ -1264,6 +1268,47 @@ public int expunge(final SearchCriteria sc) { throw new CloudRuntimeException("Caught: " + pstmt, e); } } + @Override + public int expunge(final SearchCriteria sc) { + return expunge(sc, null); + } + + @Override + public int batchExpunge(final SearchCriteria sc, final Long batchSize) { + Filter filter = null; + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + if (batchSizeFinal > 0) { + filter = new Filter(batchSizeFinal); + } + int expunged = 0; + int currentExpunged = 0; + do { + currentExpunged = expunge(sc, filter); + expunged += currentExpunged; + } while (batchSizeFinal > 0 && currentExpunged >= batchSizeFinal); + return expunged; + } + + @Override + public int expungeList(final List ids) { + if (org.apache.commons.collections.CollectionUtils.isEmpty(ids)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + Object obj = null; + try { + Method m = sb.entity().getClass().getMethod("getId"); + obj = m.invoke(sb.entity()); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {} + if (obj == null) { + logger.warn(String.format("Unable to get ID object for entity: %s", _entityBeanType.getSimpleName())); + return 0; + } + sb.and("id", obj, SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("id", ids.toArray()); + return expunge(sc); + } @DB() protected StringBuilder createPartialSelectSql(SearchCriteria sc, final boolean whereClause, final boolean enableQueryCache) { diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDao.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDao.java index 89601e6b5d20..b3bfda0334cf 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDao.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDao.java @@ -39,4 +39,5 @@ public interface VmWorkJobDao extends GenericDao { void expungeCompletedWorkJobs(Date cutDate); void expungeLeftoverWorkJobs(long msid); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java index e66221cc8fe0..3b167498a377 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java @@ -24,10 +24,10 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; - import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO.Step; import org.apache.cloudstack.jobs.JobInfo; +import org.apache.commons.collections.CollectionUtils; import com.cloud.utils.DateUtil; import com.cloud.utils.db.Filter; @@ -212,4 +212,16 @@ public void doInTransactionWithoutResult(TransactionStatus status) { } }); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVmInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImplTest.java b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImplTest.java new file mode 100644 index 000000000000..3e2bc15b1e0c --- /dev/null +++ b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImplTest.java @@ -0,0 +1,68 @@ +// 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 org.apache.cloudstack.framework.jobs.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class VmWorkJobDaoImplTest { + + @Spy + VmWorkJobDaoImpl vmWorkJobDaoImpl; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, vmWorkJobDaoImpl.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, vmWorkJobDaoImpl.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(vmWorkJobDaoImpl).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(vmWorkJobDaoImpl.createSearchBuilder()).thenReturn(sb); + final VmWorkJobVO mockedVO = Mockito.mock(VmWorkJobVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), vmWorkJobDaoImpl.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(vmWorkJobDaoImpl, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java index 6c0ac160ceb2..c1ea7823811b 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java @@ -224,4 +224,8 @@ public boolean handlesOnlyRulesInTransitionState() { return true; } + @Override + public void expungeLbVmRefs(List vmIds, Long batchSize) { + _lbMgr.expungeLbVmRefs(vmIds, batchSize); + } } diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManager.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManager.java index f885f7e1012d..a687c8113165 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManager.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManager.java @@ -37,4 +37,6 @@ public LoadBalancer handleCreateLoadBalancerRule(CreateLoadBalancerRuleCmd lb, A NetworkRuleConflictException; public void handleDeleteLoadBalancerRule(LoadBalancer lb, long callerUserId, Account caller); + + void expungeLbVmRefs(List vmIds, Long batchSize); } diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java index b47b7aad28e1..c7f4b8bf2443 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java @@ -599,4 +599,8 @@ public void prepareStop(VirtualMachineProfile profile) { public void finalizeUnmanage(VirtualMachine vm) { } + @Override + public void expungeLbVmRefs(List vmIds, Long batchSize) { + _elbVmMapDao.expungeByLbVmList(vmIds, batchSize); + } } diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDao.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDao.java index 83aa6118a63d..eec7eaa4f5f1 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDao.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDao.java @@ -40,4 +40,6 @@ public interface ElasticLbVmMapDao extends GenericDao { List listLbsForElbVm(long elbVmId); + int expungeByLbVmList(List vmIds, Long batchSize); + } diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDaoImpl.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDaoImpl.java index bbe79ef31030..ea1673967a59 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDaoImpl.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/dao/ElasticLbVmMapDaoImpl.java @@ -21,6 +21,7 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.network.ElasticLbVmMapVO; @@ -136,4 +137,16 @@ public List listLbsForElbVm(long elbVmId) { return _loadbalancerDao.search(sc, null); } + @Override + public int expungeByLbVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getElbVmId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } + } diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java index bf6167e7f3eb..48ccc356f1a6 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java @@ -19,35 +19,35 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import com.cloud.dc.VsphereStoragePolicyVO; -import com.cloud.dc.dao.VsphereStoragePolicyDao; -import com.cloud.user.AccountManager; -import com.cloud.utils.db.TransactionLegacy; +import javax.inject.Inject; + import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; -import com.cloud.storage.DiskOfferingVO; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; - +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.ServiceOfferingJoinVO; +import com.cloud.dc.VsphereStoragePolicyVO; +import com.cloud.dc.dao.VsphereStoragePolicyDao; import com.cloud.offering.ServiceOffering; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.user.AccountManager; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; - -import javax.inject.Inject; +import com.cloud.utils.db.TransactionLegacy; @Component public class ServiceOfferingJoinDaoImpl extends GenericDaoBase implements ServiceOfferingJoinDao { @@ -167,6 +167,10 @@ public ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO if (vsphereStoragePolicyVO != null) offeringResponse.setVsphereStoragePolicy(vsphereStoragePolicyVO.getName()); } + String purgeResource = offeringDetails.get(ServiceOffering.PURGE_DB_ENTITIES_KEY); + if (StringUtils.isNotBlank(purgeResource)) { + offeringResponse.setPurgeResources(Boolean.parseBoolean(purgeResource)); + } } long rootDiskSizeInGb = (long) offering.getRootDiskSize() / GB_TO_BYTES; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index e6f1eb018ce5..ff7f0d4935e4 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -3187,7 +3187,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(), cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId, - cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot()); + cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), cmd.isPurgeResources()); } protected ServiceOfferingVO createServiceOffering(final long userId, final boolean isSystem, final VirtualMachine.Type vmType, @@ -3198,8 +3198,9 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength, Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength, Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength, - final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID, final boolean dynamicScalingEnabled, final Long diskOfferingId, - final boolean diskOfferingStrictness, final boolean isCustomized, final boolean encryptRoot) { + final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID, + final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness, + final boolean isCustomized, final boolean encryptRoot, final boolean purgeResources) { // Filter child domains when both parent and child domains are present List filteredDomainIds = filterChildSubDomains(domainIds); @@ -3234,7 +3235,7 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole limitResourceUse, volatileVm, displayText, isSystem, vmType, hostTag, deploymentPlanner, dynamicScalingEnabled, isCustomized); - List detailsVO = new ArrayList(); + List detailsVOList = new ArrayList(); if (details != null) { // To have correct input, either both gpu card name and VGPU type should be passed or nothing should be passed. // Use XOR condition to verify that. @@ -3268,12 +3269,16 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole // Add in disk offering details continue; } - detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), detailEntry.getKey(), detailEntryValue, true)); + detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), detailEntry.getKey(), detailEntryValue, true)); } } if (storagePolicyID != null) { - detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.STORAGE_POLICY, String.valueOf(storagePolicyID), false)); + detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.STORAGE_POLICY, String.valueOf(storagePolicyID), false)); + } + if (purgeResources) { + detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), + ServiceOffering.PURGE_DB_ENTITIES_KEY, Boolean.TRUE.toString(), false)); } serviceOffering.setDiskOfferingStrictness(diskOfferingStrictness); @@ -3303,18 +3308,18 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole if ((serviceOffering = _serviceOfferingDao.persist(serviceOffering)) != null) { for (Long domainId : filteredDomainIds) { - detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); + detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); } if (CollectionUtils.isNotEmpty(zoneIds)) { for (Long zoneId : zoneIds) { - detailsVO.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); + detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); } } - if (CollectionUtils.isNotEmpty(detailsVO)) { - for (ServiceOfferingDetailsVO detail : detailsVO) { + if (CollectionUtils.isNotEmpty(detailsVOList)) { + for (ServiceOfferingDetailsVO detail : detailsVOList) { detail.setResourceId(serviceOffering.getId()); } - _serviceOfferingDetailsDao.saveDetails(detailsVO); + _serviceOfferingDetailsDao.saveDetails(detailsVOList); } CallContext.current().setEventDetails("Service offering id=" + serviceOffering.getId()); @@ -3474,6 +3479,7 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) String storageTags = cmd.getStorageTags(); String hostTags = cmd.getHostTags(); ServiceOffering.State state = cmd.getState(); + boolean purgeResources = cmd.isPurgeResources(); if (userId == null) { userId = Long.valueOf(User.UID_SYSTEM); @@ -3491,6 +3497,12 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) List existingZoneIds = _serviceOfferingDetailsDao.findZoneIds(id); Collections.sort(existingZoneIds); + String purgeResourceStr = _serviceOfferingDetailsDao.getDetail(id, ServiceOffering.PURGE_DB_ENTITIES_KEY); + boolean existingPurgeResources = false; + if (StringUtils.isNotBlank(purgeResourceStr)) { + existingPurgeResources = Boolean.parseBoolean(purgeResourceStr); + } + // check if valid domain if (CollectionUtils.isNotEmpty(domainIds)) { for (final Long domainId: domainIds) { @@ -3559,7 +3571,8 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) } final boolean updateNeeded = name != null || displayText != null || sortKey != null || storageTags != null || hostTags != null || state != null; - final boolean detailsUpdateNeeded = !filteredDomainIds.equals(existingDomainIds) || !filteredZoneIds.equals(existingZoneIds); + final boolean detailsUpdateNeeded = !filteredDomainIds.equals(existingDomainIds) || + !filteredZoneIds.equals(existingZoneIds) || purgeResources != existingPurgeResources; if (!updateNeeded && !detailsUpdateNeeded) { return _serviceOfferingDao.findById(id); } @@ -3618,6 +3631,14 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) detailsVO.add(new ServiceOfferingDetailsVO(id, ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); } } + if (purgeResources != existingPurgeResources) { + sc.setParameters("detailName", ServiceOffering.PURGE_DB_ENTITIES_KEY); + _serviceOfferingDetailsDao.remove(sc); + if (purgeResources) { + detailsVO.add(new ServiceOfferingDetailsVO(id, ServiceOffering.PURGE_DB_ENTITIES_KEY, + "true", false)); + } + } } if (!detailsVO.isEmpty()) { for (ServiceOfferingDetailsVO detailVO : detailsVO) { diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java index b815f21e2064..00e25284f428 100644 --- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java +++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java @@ -1092,4 +1092,9 @@ public ConfigKey[] getConfigKeys() { StopRetryInterval, RestartRetryInterval, MigrateRetryInterval, InvestigateRetryInterval, HAWorkers, ForceHA, KvmHAFenceHostIfHeartbeatFailsOnStorage}; } + + @Override + public int expungeWorkItemsByVmList(List vmIds, Long batchSize) { + return _haDao.expungeByVmList(vmIds, batchSize); + } } diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java index e8a3e17f8052..f6539105d78f 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java @@ -85,4 +85,5 @@ public interface HighAvailabilityDao extends GenericDao { List listPendingHaWorkForVm(long vmId); List listPendingMigrationsForVm(long vmId); + int expungeByVmList(List vmIds, Long batchSize); } diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java index 357796a6a70b..c722c6376c16 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; - +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.ha.HaWorkVO; @@ -258,4 +258,16 @@ public int releaseWorkItems(long nodeId) { return update(vo, sc); } + + @Override + public int expungeByVmList(List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return batchExpunge(sc, batchSize); + } } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index ee73818638cb..2086f6f15700 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -54,11 +54,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; -import com.cloud.kubernetes.cluster.KubernetesServiceHelper; -import com.cloud.network.dao.NsxProviderDao; -import com.cloud.network.element.NsxProviderVO; -import com.cloud.user.AccountVO; -import com.cloud.utils.exception.ExceptionProxyObject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -247,6 +242,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; @@ -265,7 +261,9 @@ import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.element.NsxProviderVO; import com.cloud.network.element.UserDataServiceProvider; import com.cloud.network.guru.NetworkGuru; import com.cloud.network.lb.LoadBalancingRulesManager; @@ -334,6 +332,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; import com.cloud.user.ResourceLimitService; import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; @@ -368,6 +367,7 @@ import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.db.UUIDManager; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.exception.ExceptionProxyObject; import com.cloud.utils.exception.ExecutionException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.net.Ip; diff --git a/server/src/main/java/org/apache/cloudstack/resource/ResourceCleanupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/resource/ResourceCleanupServiceImpl.java new file mode 100644 index 000000000000..c4abbb045144 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/resource/ResourceCleanupServiceImpl.java @@ -0,0 +1,827 @@ +// 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 org.apache.cloudstack.resource; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.admin.resource.PurgeExpungedResourcesCmd; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.framework.async.AsyncRpcContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.InlineLoadBalancerNicMapDao; +import com.cloud.network.dao.LoadBalancerVMMapDao; +import com.cloud.network.dao.OpRouterMonitorServiceDao; +import com.cloud.network.rules.dao.PortForwardingRulesDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.secstorage.CommandExecLogDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.component.PluggableService; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ItWorkDao; +import com.cloud.vm.NicVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.ConsoleSessionDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicDetailsDao; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; +import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; + +public class ResourceCleanupServiceImpl extends ManagerBase implements ResourceCleanupService, PluggableService, + Configurable { + + @Inject + VMInstanceDao vmInstanceDao; + @Inject + VolumeDao volumeDao; + @Inject + VolumeDetailsDao volumeDetailsDao; + @Inject + VolumeDataStoreDao volumeDataStoreDao; + @Inject + SnapshotDao snapshotDao; + @Inject + SnapshotDetailsDao snapshotDetailsDao; + @Inject + SnapshotDataStoreDao snapshotDataStoreDao; + @Inject + NicDao nicDao; + @Inject + NicDetailsDao nicDetailsDao; + @Inject + NicExtraDhcpOptionDao nicExtraDhcpOptionDao; + @Inject + InlineLoadBalancerNicMapDao inlineLoadBalancerNicMapDao; + @Inject + UserVmDetailsDao userVmDetailsDao; + @Inject + VMSnapshotDao vmSnapshotDao; + @Inject + VMSnapshotDetailsDao vmSnapshotDetailsDao; + @Inject + AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao; + @Inject + CommandExecLogDao commandExecLogDao; + @Inject + NetworkOrchestrationService networkOrchestrationService; + @Inject + LoadBalancerVMMapDao loadBalancerVMMapDao; + @Inject + NicSecondaryIpDao nicSecondaryIpDao; + @Inject + HighAvailabilityManager highAvailabilityManager; + @Inject + ItWorkDao itWorkDao; + @Inject + OpRouterMonitorServiceDao opRouterMonitorServiceDao; + @Inject + PortForwardingRulesDao portForwardingRulesDao; + @Inject + IPAddressDao ipAddressDao; + @Inject + VmWorkJobDao vmWorkJobDao; + @Inject + ConsoleSessionDao consoleSessionDao; + @Inject + ManagementServerHostDao managementServerHostDao; + @Inject + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + + private ScheduledExecutorService expungedResourcesCleanupExecutor; + private ExecutorService purgeExpungedResourcesJobExecutor; + + protected void purgeLinkedSnapshotEntities(final List snapshotIds, final Long batchSize) { + if (CollectionUtils.isEmpty(snapshotIds)) { + return; + } + snapshotDetailsDao.batchExpungeForResources(snapshotIds, batchSize); + snapshotDataStoreDao.expungeBySnapshotList(snapshotIds, batchSize); + // Snapshot policies are using ON DELETE CASCADE + } + + protected long purgeVolumeSnapshots(final List volumeIds, final Long batchSize) { + if (CollectionUtils.isEmpty(volumeIds)) { + return 0; + } + SearchBuilder sb = snapshotDao.createSearchBuilder(); + sb.and("volumeIds", sb.entity().getVolumeId(), SearchCriteria.Op.IN); + sb.and("removed", sb.entity().getRemoved(), SearchCriteria.Op.NNULL); + SearchCriteria sc = sb.create(); + sc.setParameters("volumeIds", volumeIds.toArray()); + int removed = 0; + long totalRemoved = 0; + Filter filter = new Filter(SnapshotVO.class, "id", true, 0L, batchSize); + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + do { + List snapshots = snapshotDao.searchIncludingRemoved(sc, filter, null, false); + List snapshotIds = snapshots.stream().map(SnapshotVO::getId).collect(Collectors.toList()); + purgeLinkedSnapshotEntities(snapshotIds, batchSize); + removed = snapshotDao.expungeList(snapshotIds); + totalRemoved += removed; + } while (batchSizeFinal > 0 && removed >= batchSizeFinal); + return totalRemoved; + } + + protected void purgeLinkedVolumeEntities(final List volumeIds, final Long batchSize) { + if (CollectionUtils.isEmpty(volumeIds)) { + return; + } + volumeDetailsDao.batchExpungeForResources(volumeIds, batchSize); + volumeDataStoreDao.expungeByVolumeList(volumeIds, batchSize); + purgeVolumeSnapshots(volumeIds, batchSize); + } + + protected long purgeVMVolumes(final List vmIds, final Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + int removed = 0; + long totalRemoved = 0; + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + do { + List volumes = volumeDao.searchRemovedByVms(vmIds, batchSize); + List volumeIds = volumes.stream().map(VolumeVO::getId).collect(Collectors.toList()); + purgeLinkedVolumeEntities(volumeIds, batchSize); + removed = volumeDao.expungeList(volumeIds); + totalRemoved += removed; + } while (batchSizeFinal > 0 && removed >= batchSizeFinal); + return totalRemoved; + } + + protected void purgeLinkedNicEntities(final List nicIds, final Long batchSize) { + if (CollectionUtils.isEmpty(nicIds)) { + return; + } + nicDetailsDao.batchExpungeForResources(nicIds, batchSize); + nicExtraDhcpOptionDao.expungeByNicList(nicIds, batchSize); + inlineLoadBalancerNicMapDao.expungeByNicList(nicIds, batchSize); + } + + protected long purgeVMNics(final List vmIds, final Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + int removed = 0; + long totalRemoved = 0; + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + do { + List nics = nicDao.searchRemovedByVms(vmIds, batchSize); + List nicIds = nics.stream().map(NicVO::getId).collect(Collectors.toList()); + purgeLinkedNicEntities(nicIds, batchSize); + removed = nicDao.expungeList(nicIds); + totalRemoved += removed; + } while (batchSizeFinal > 0 && removed >= batchSizeFinal); + return totalRemoved; + } + + protected long purgeVMSnapshots(final List vmIds, final Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return 0; + } + int removed = 0; + long totalRemoved = 0; + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + do { + List vmSnapshots = vmSnapshotDao.searchRemovedByVms(vmIds, batchSize); + List ids = vmSnapshots.stream().map(VMSnapshotVO::getId).collect(Collectors.toList()); + vmSnapshotDetailsDao.batchExpungeForResources(ids, batchSize); + removed = vmSnapshotDao.expungeList(ids); + totalRemoved += removed; + } while (batchSizeFinal > 0 && removed >= batchSizeFinal); + return totalRemoved; + } + + protected void purgeLinkedVMEntities(final List vmIds, final Long batchSize) { + if (CollectionUtils.isEmpty(vmIds)) { + return; + } + purgeVMVolumes(vmIds, batchSize); + purgeVMNics(vmIds, batchSize); + userVmDetailsDao.batchExpungeForResources(vmIds, batchSize); + purgeVMSnapshots(vmIds, batchSize); + autoScaleVmGroupVmMapDao.expungeByVmList(vmIds, batchSize); + commandExecLogDao.expungeByVmList(vmIds, batchSize); + networkOrchestrationService.expungeLbVmRefs(vmIds, batchSize); + loadBalancerVMMapDao.expungeByVmList(vmIds, batchSize); + nicSecondaryIpDao.expungeByVmList(vmIds, batchSize); + highAvailabilityManager.expungeWorkItemsByVmList(vmIds, batchSize); + itWorkDao.expungeByVmList(vmIds, batchSize); + opRouterMonitorServiceDao.expungeByVmList(vmIds, batchSize); + portForwardingRulesDao.expungeByVmList(vmIds, batchSize); + ipAddressDao.expungeByVmList(vmIds, batchSize); + vmWorkJobDao.expungeByVmList(vmIds, batchSize); + consoleSessionDao.expungeByVmList(vmIds, batchSize); + } + + protected HashSet getVmIdsWithActiveVolumeSnapshots(List vmIds) { + if (CollectionUtils.isEmpty(vmIds)) { + return new HashSet<>(); + } + List volumes = volumeDao.searchRemovedByVms(vmIds, null); + List volumeIds = volumes.stream().map(VolumeVO::getId).collect(Collectors.toList()); + List activeSnapshots = snapshotDao.searchByVolumes(volumeIds); + HashSet activeSnapshotVolumeIds = + activeSnapshots.stream().map(SnapshotVO::getVolumeId).collect(Collectors.toCollection(HashSet::new)); + List volumesWithActiveSnapshots = + volumes.stream().filter(v -> activeSnapshotVolumeIds.contains(v.getId())).collect(Collectors.toList()); + return volumesWithActiveSnapshots.stream().map(VolumeVO::getInstanceId) + .collect(Collectors.toCollection(HashSet::new)); + } + + protected Pair, List> getFilteredVmIdsForSnapshots(List vmIds) { + HashSet currentSkippedVmIds = new HashSet<>(); + List activeSnapshots = vmSnapshotDao.searchByVms(vmIds); + if (CollectionUtils.isNotEmpty(activeSnapshots)) { + HashSet vmIdsWithActiveSnapshots = activeSnapshots.stream().map(VMSnapshotVO::getVmId) + .collect(Collectors.toCollection(HashSet::new)); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Skipping purging VMs with IDs %s as they have active " + + "VM snapshots", StringUtils.join(vmIdsWithActiveSnapshots))); + } + currentSkippedVmIds.addAll(vmIdsWithActiveSnapshots); + } + HashSet vmIdsWithActiveVolumeSnapshots = getVmIdsWithActiveVolumeSnapshots(vmIds); + if (CollectionUtils.isNotEmpty(vmIdsWithActiveVolumeSnapshots)) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Skipping purging VMs with IDs %s as they have volumes with active " + + "snapshots", StringUtils.join(vmIdsWithActiveVolumeSnapshots))); + } + currentSkippedVmIds.addAll(vmIdsWithActiveVolumeSnapshots); + } + if (CollectionUtils.isNotEmpty(currentSkippedVmIds)) { + vmIds.removeAll(currentSkippedVmIds); + } + return new Pair<>(vmIds, new ArrayList<>(currentSkippedVmIds)); + } + + protected Pair, List> getVmIdsWithNoActiveSnapshots(final Date startDate, final Date endDate, + final Long batchSize, final List skippedVmIds) { + List vms = vmInstanceDao.searchRemovedByRemoveDate(startDate, endDate, batchSize, skippedVmIds); + if (CollectionUtils.isEmpty(vms)) { + return new Pair<>(new ArrayList<>(), new ArrayList<>()); + } + List vmIds = vms.stream().map(VMInstanceVO::getId).collect(Collectors.toList()); + return getFilteredVmIdsForSnapshots(vmIds); + } + + protected long purgeVMEntities(final Long batchSize, final Date startDate, final Date endDate) { + return Transaction.execute((TransactionCallbackWithException) status -> { + int count; + long totalRemoved = 0; + final long batchSizeFinal = ObjectUtils.defaultIfNull(batchSize, 0L); + List skippedVmIds = new ArrayList<>(); + do { + Pair, List> allVmIds = + getVmIdsWithNoActiveSnapshots(startDate, endDate, batchSize, skippedVmIds); + List vmIds = allVmIds.first(); + List currentSkippedVmIds = allVmIds.second(); + count = vmIds.size() + currentSkippedVmIds.size(); + skippedVmIds.addAll(currentSkippedVmIds); + purgeLinkedVMEntities(vmIds, batchSize); + totalRemoved += vmInstanceDao.expungeList(vmIds); + } while (batchSizeFinal > 0 && count >= batchSizeFinal); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Purged total %d VM records", totalRemoved)); + } + return totalRemoved; + }); + } + + protected boolean purgeVMEntity(final long vmId) { + return Transaction.execute((TransactionCallbackWithException) status -> { + final Long batchSize = ExpungedResourcesPurgeBatchSize.value().longValue(); + List vmIds = new ArrayList<>(); + vmIds.add(vmId); + Pair, List> allVmIds = getFilteredVmIdsForSnapshots(vmIds); + if (CollectionUtils.isEmpty(allVmIds.first())) { + return false; + } + purgeLinkedVMEntities(vmIds, batchSize); + return vmInstanceDao.expunge(vmId); + }); + } + + protected long purgeEntities(final List resourceTypes, final Long batchSize, + final Date startDate, final Date endDate) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Expunging entities with parameters - resourceType: %s, batchSize: %d, " + + "startDate: %s, endDate: %s", StringUtils.join(resourceTypes), batchSize, startDate, endDate)); + } + long totalPurged = 0; + if (CollectionUtils.isEmpty(resourceTypes) || resourceTypes.contains(ResourceType.VirtualMachine)) { + totalPurged += purgeVMEntities(batchSize, startDate, endDate); + } + return totalPurged; + } + + protected Void purgeExpungedResourcesCallback( + AsyncCallbackDispatcher callback, + PurgeExpungedResourcesContext context) { + PurgeExpungedResourcesResult result = callback.getResult(); + context.future.complete(result); + return null; + } + + protected ResourceType getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(final String resourceTypeStr, + final Date startDate, final Date endDate, final Long batchSize) { + ResourceType resourceType = null; + if (StringUtils.isNotBlank(resourceTypeStr)) { + resourceType = EnumUtils.getEnumIgnoreCase(ResourceType.class, resourceTypeStr, null); + if (resourceType == null) { + throw new InvalidParameterValueException("Invalid resource type specified"); + } + } + if (batchSize != null && batchSize <= 0) { + throw new InvalidParameterValueException(String.format("Invalid %s specified", ApiConstants.BATCH_SIZE)); + } + if (endDate != null && startDate != null && endDate.before(startDate)) { + throw new InvalidParameterValueException(String.format("Invalid %s specified", ApiConstants.END_DATE)); + } + return resourceType; + } + + protected long purgeExpungedResourceUsingJob(final ResourceType resourceType, final Long batchSize, + final Date startDate, final Date endDate) { + AsyncCallFuture future = new AsyncCallFuture<>(); + PurgeExpungedResourcesContext context = + new PurgeExpungedResourcesContext<>(null, future); + AsyncCallbackDispatcher caller = + AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().purgeExpungedResourcesCallback(null, null)) + .setContext(context); + PurgeExpungedResourceThread job = new PurgeExpungedResourceThread(resourceType, batchSize, startDate, endDate, + caller); + purgeExpungedResourcesJobExecutor.submit(job); + long expungedCount; + try { + PurgeExpungedResourcesResult result = future.get(); + if (result.isFailed()) { + throw new CloudRuntimeException(String.format("Failed to purge expunged resources due to: %s", result.getResult())); + } + expungedCount = result.getPurgedCount(); + } catch (InterruptedException | ExecutionException e) { + logger.error(String.format("Failed to purge expunged resources due to: %s", e.getMessage()), e); + throw new CloudRuntimeException("Failed to purge expunged resources"); + } + return expungedCount; + } + + protected boolean isVmOfferingPurgeResourcesEnabled(long vmServiceOfferingId) { + String detail = + serviceOfferingDetailsDao.getDetail(vmServiceOfferingId, ServiceOffering.PURGE_DB_ENTITIES_KEY); + return StringUtils.isNotBlank(detail) && Boolean.parseBoolean(detail); + } + + protected boolean purgeExpungedResource(long resourceId, ResourceType resourceType) { + if (!ResourceType.VirtualMachine.equals(resourceType)) { + return false; + } + return purgeVMEntity(resourceId); + } + + protected void purgeExpungedResourceLater(long resourceId, ResourceType resourceType) { + AsyncCallFuture future = new AsyncCallFuture<>(); + PurgeExpungedResourcesContext context = + new PurgeExpungedResourcesContext<>(null, future); + AsyncCallbackDispatcher caller = + AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().purgeExpungedResourcesCallback(null, null)) + .setContext(context); + PurgeExpungedResourceThread job = new PurgeExpungedResourceThread(resourceId, resourceType, caller); + purgeExpungedResourcesJobExecutor.submit(job); + } + + protected Date parseDateFromConfig(String configKey, String configValue) { + if (StringUtils.isBlank(configValue)) { + return null; + } + final List dateFormats = List.of("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd"); + Date date = null; + for (String format : dateFormats) { + final SimpleDateFormat dateFormat = new SimpleDateFormat(format); + try { + date = dateFormat.parse(configValue); + break; + } catch (ParseException e) { + logger.trace(String.format("Unable to parse value for config %s: %s with date " + + "format: %s due to %s", configKey, configValue, format, e.getMessage())); + } + } + if (date == null) { + throw new CloudRuntimeException(String.format("Unable to parse value for config %s: %s with date " + + "formats: %s", configKey, configValue, StringUtils.join(dateFormats))); + } + return date; + } + + protected Date getStartDateFromConfig() { + return parseDateFromConfig(ExpungedResourcesPurgeStartTime.key(), ExpungedResourcesPurgeStartTime.value()); + } + + protected Date calculatePastDateFromConfig(String configKey, Integer configValue) { + if (configValue == null || configValue == 0) { + return null; + } + if (configValue < 0) { + throw new CloudRuntimeException(String.format("Unable to retrieve a valid value for config %s: %s", + configKey, configValue)); + } + Calendar cal = Calendar.getInstance(); + Date endDate = new Date(); + cal.setTime(endDate); + cal.add(Calendar.DATE, -1 * configValue); + return cal.getTime(); + } + + protected Date getEndDateFromConfig() { + return calculatePastDateFromConfig(ExpungedResourcesPurgeKeepPastDays.key(), + ExpungedResourcesPurgeKeepPastDays.value()); + } + + protected List getResourceTypesFromConfig() { + String resourceTypesConfig = ExpungedResourcePurgeResources.value(); + if (StringUtils.isBlank(resourceTypesConfig)) { + return null; + } + List resourceTypes = new ArrayList<>(); + for (String type : resourceTypesConfig.split(",")) { + ResourceType resourceType = EnumUtils.getEnum(ResourceType.class, type.trim(), null); + if (resourceType == null) { + throw new CloudRuntimeException(String.format("Invalid resource type: '%s' specified in " + + "the config: %s", type, ExpungedResourcePurgeResources.key())); + } + resourceTypes.add(resourceType); + } + return resourceTypes; + } + + protected long getBatchSizeFromConfig() { + Integer batchSize = ExpungedResourcesPurgeBatchSize.value(); + if (batchSize == null || batchSize <= 0) { + throw new CloudRuntimeException(String.format("Unable to retrieve a valid value for config %s: %s", + ExpungedResourcesPurgeBatchSize.key(), batchSize)); + } + return batchSize.longValue(); + } + + @Override + public long purgeExpungedResources(PurgeExpungedResourcesCmd cmd) { + final String resourceTypeStr = cmd.getResourceType(); + final Date startDate = cmd.getStartDate(); + final Date endDate = cmd.getEndDate(); + Long batchSize = cmd.getBatchSize(); + ResourceType resourceType = getResourceTypeAndValidatePurgeExpungedResourcesCmdParams(resourceTypeStr, + startDate, endDate, batchSize); + Integer globalBatchSize = ExpungedResourcesPurgeBatchSize.value(); + if (batchSize == null && globalBatchSize > 0) { + batchSize = globalBatchSize.longValue(); + } + long expungedCount = purgeExpungedResourceUsingJob(resourceType, batchSize, startDate, endDate); + if (expungedCount <= 0) { + logger.debug("No resource expunged during purgeExpungedResources execution"); + } + return expungedCount; + } + + @Override + public void purgeExpungedVmResourcesLaterIfNeeded(VirtualMachine vm) { + if (!isVmOfferingPurgeResourcesEnabled(vm.getServiceOfferingId())) { + return; + } + purgeExpungedResourceLater(vm.getId(), ResourceType.VirtualMachine); + } + + @Override + public boolean start() { + if (Boolean.TRUE.equals(ExpungedResourcePurgeEnabled.value())) { + expungedResourcesCleanupExecutor = new ScheduledThreadPoolExecutor(1, + new NamedThreadFactory("ExpungedResourceCleanupWorker")); + expungedResourcesCleanupExecutor.scheduleWithFixedDelay(new ExpungedResourceCleanupWorker(), + ExpungedResourcesPurgeDelay.value(), ExpungedResourcesPurgeInterval.value(), TimeUnit.SECONDS); + } + purgeExpungedResourcesJobExecutor = Executors.newFixedThreadPool(3, + new NamedThreadFactory("Purge-Expunged-Resources-Job-Executor")); + return true; + } + + @Override + public boolean stop() { + purgeExpungedResourcesJobExecutor.shutdown(); + expungedResourcesCleanupExecutor.shutdownNow(); + return true; + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList<>(); + cmdList.add(PurgeExpungedResourcesCmd.class); + return cmdList; + } + + @Override + public String getConfigComponentName() { + return ResourceCleanupService.class.getName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + ExpungedResourcePurgeEnabled, + ExpungedResourcePurgeResources, + ExpungedResourcesPurgeInterval, + ExpungedResourcesPurgeDelay, + ExpungedResourcesPurgeBatchSize, + ExpungedResourcesPurgeStartTime, + ExpungedResourcesPurgeKeepPastDays, + ExpungedResourcePurgeJobDelay + }; + } + + public class ExpungedResourceCleanupWorker extends ManagedContextRunnable { + @Override + protected void runInContext() { + GlobalLock gcLock = GlobalLock.getInternLock("Expunged.Resource.Cleanup.Lock"); + try { + if (gcLock.lock(3)) { + try { + runCleanupForLongestRunningManagementServer(); + } finally { + gcLock.unlock(); + } + } + } finally { + gcLock.releaseRef(); + } + } + + protected void runCleanupForLongestRunningManagementServer() { + ManagementServerHostVO msHost = managementServerHostDao.findOneByLongestRuntime(); + if (msHost == null || (msHost.getMsid() != ManagementServerNode.getManagementServerId())) { + logger.debug("Skipping the expunged resource cleanup task on this management server"); + return; + } + reallyRun(); + } + + public void reallyRun() { + try { + Date startDate = getStartDateFromConfig(); + Date endDate = getEndDateFromConfig(); + List resourceTypes = getResourceTypesFromConfig(); + long batchSize = getBatchSizeFromConfig(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Purging resources: %s as part of cleanup with start date: %s, " + + "end date: %s and batch size: %d", StringUtils.join(resourceTypes), startDate, endDate, batchSize)); + } + purgeEntities(resourceTypes, batchSize, startDate, endDate); + } catch (Exception e) { + logger.warn("Caught exception while running expunged resources cleanup task: ", e); + } + } + } + + protected class PurgeExpungedResourceThread extends ManagedContextRunnable { + ResourceType resourceType; + Long resourceId; + Long batchSize; + Date startDate; + Date endDate; + AsyncCompletionCallback callback; + long taskTimestamp; + + public PurgeExpungedResourceThread(final ResourceType resourceType, final Long batchSize, + final Date startDate, final Date endDate, + AsyncCompletionCallback callback) { + this.resourceType = resourceType; + this.batchSize = batchSize; + this.startDate = startDate; + this.endDate = endDate; + this.callback = callback; + } + + public PurgeExpungedResourceThread(final Long resourceId, final ResourceType resourceType, + AsyncCompletionCallback callback) { + this.resourceType = resourceType; + this.resourceId = resourceId; + this.callback = callback; + this.taskTimestamp = System.currentTimeMillis(); + } + + @Override + protected void runInContext() { + logger.trace(String.format("Executing purge for resource type: %s with batch size: %d start: %s, end: %s", + resourceType, batchSize, startDate, endDate)); + reallyRun(); + } + + protected void waitForPurgeSingleResourceDelay(String resourceAsString) throws InterruptedException { + long jobDelayConfig = ExpungedResourcePurgeJobDelay.value(); + if (jobDelayConfig < MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS) { + logger.debug(String.format("Value: %d for config: %s is lesser than the minimum value: %d, " + + "using minimum value", + jobDelayConfig, + ExpungedResourcePurgeJobDelay.key(), + MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS)); + jobDelayConfig = MINIMUM_EXPUNGED_RESOURCE_PURGE_JOB_DELAY_IN_SECONDS; + } + long delay = (jobDelayConfig * 1000) - + (System.currentTimeMillis() - taskTimestamp); + + if (delay > 0) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Waiting for %d before purging %s", delay, resourceAsString)); + } + Thread.sleep(delay); + } + } + + protected void purgeSingleResource() { + String resourceAsString = String.format("resource [type: %s, ID: %d]", resourceType, resourceId); + try { + waitForPurgeSingleResourceDelay(resourceAsString); + if (!purgeExpungedResource(resourceId, resourceType)) { + throw new CloudRuntimeException(String.format("Failed to purge %s", resourceAsString)); + } + if (logger.isDebugEnabled()) { + logger.info(String.format("Purged %s", resourceAsString)); + } + callback.complete(new PurgeExpungedResourcesResult(resourceId, resourceType, null)); + } catch (CloudRuntimeException e) { + logger.error(String.format("Caught exception while purging %s: ", resourceAsString), e); + callback.complete(new PurgeExpungedResourcesResult(resourceId, resourceType, e.getMessage())); + } catch (InterruptedException e) { + logger.error(String.format("Caught exception while waiting for purging %s: ", resourceAsString), e); + callback.complete(new PurgeExpungedResourcesResult(resourceId, resourceType, e.getMessage())); + } + } + + protected void purgeMultipleResources() { + try { + long purged = purgeEntities(resourceType == null ? null : List.of(resourceType), + batchSize, startDate, endDate); + callback.complete(new PurgeExpungedResourcesResult(resourceType, batchSize, startDate, endDate, purged)); + } catch (CloudRuntimeException e) { + logger.error("Caught exception while expunging resources: ", e); + callback.complete(new PurgeExpungedResourcesResult(resourceType, batchSize, startDate, endDate, e.getMessage())); + } + } + + public void reallyRun() { + if (resourceId != null) { + purgeSingleResource(); + return; + } + purgeMultipleResources(); + } + } + + public static class PurgeExpungedResourcesResult extends CommandResult { + ResourceType resourceType; + Long resourceId; + Long batchSize; + Date startDate; + Date endDate; + Long purgedCount; + + public PurgeExpungedResourcesResult(final ResourceType resourceType, final Long batchSize, + final Date startDate, final Date endDate, final long purgedCount) { + super(); + this.resourceType = resourceType; + this.batchSize = batchSize; + this.startDate = startDate; + this.endDate = endDate; + this.purgedCount = purgedCount; + this.setSuccess(true); + } + + public PurgeExpungedResourcesResult(final ResourceType resourceType, final Long batchSize, + final Date startDate, final Date endDate, final String error) { + super(); + this.resourceType = resourceType; + this.batchSize = batchSize; + this.startDate = startDate; + this.endDate = endDate; + this.setResult(error); + } + + public PurgeExpungedResourcesResult(final Long resourceId, final ResourceType resourceType, + final String error) { + super(); + this.resourceId = resourceId; + this.resourceType = resourceType; + if (error != null) { + this.setResult(error); + } else { + this.purgedCount = 1L; + this.setSuccess(true); + } + } + + public ResourceType getResourceType() { + return resourceType; + } + + public Long getResourceId() { + return resourceId; + } + + public Long getBatchSize() { + return batchSize; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Long getPurgedCount() { + return purgedCount; + } + } + + public static class PurgeExpungedResourcesContext extends AsyncRpcContext { + final AsyncCallFuture future; + + public PurgeExpungedResourcesContext(AsyncCompletionCallback callback, + AsyncCallFuture future) { + super(callback); + this.future = future; + } + + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 163e54a14f3c..1ca630cfa8a6 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -81,6 +81,8 @@ value="#{resourceDiscoverersRegistry.registered}" /> + + diff --git a/server/src/test/java/com/cloud/ha/dao/HighAvailabilityDaoImplTest.java b/server/src/test/java/com/cloud/ha/dao/HighAvailabilityDaoImplTest.java new file mode 100644 index 000000000000..783497740fd6 --- /dev/null +++ b/server/src/test/java/com/cloud/ha/dao/HighAvailabilityDaoImplTest.java @@ -0,0 +1,68 @@ +// 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.ha.dao; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.cloud.ha.HaWorkVO; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class HighAvailabilityDaoImplTest { + + @Spy + HighAvailabilityDaoImpl highAvailabilityDaoImpl; + + @Test + public void testExpungeByVmListNoVms() { + Assert.assertEquals(0, highAvailabilityDaoImpl.expungeByVmList( + new ArrayList<>(), 100L)); + Assert.assertEquals(0, highAvailabilityDaoImpl.expungeByVmList( + null, 100L)); + } + + @Test + public void testExpungeByVmList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doAnswer((Answer) invocationOnMock -> { + Long batchSize = (Long)invocationOnMock.getArguments()[1]; + return batchSize == null ? 0 : batchSize.intValue(); + }).when(highAvailabilityDaoImpl).batchExpunge(Mockito.any(SearchCriteria.class), Mockito.anyLong()); + Mockito.when(highAvailabilityDaoImpl.createSearchBuilder()).thenReturn(sb); + final HaWorkVO mockedVO = Mockito.mock(HaWorkVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + List vmIds = List.of(1L, 2L); + Object[] array = vmIds.toArray(); + Long batchSize = 50L; + Assert.assertEquals(batchSize.intValue(), highAvailabilityDaoImpl.expungeByVmList(List.of(1L, 2L), batchSize)); + Mockito.verify(sc).setParameters("vmIds", array); + Mockito.verify(highAvailabilityDaoImpl, Mockito.times(1)) + .batchExpunge(sc, batchSize); + } +} diff --git a/server/src/test/java/com/cloud/user/MockUsageEventDao.java b/server/src/test/java/com/cloud/user/MockUsageEventDao.java index 52e1b1a02b49..7e15a4f0aaf3 100644 --- a/server/src/test/java/com/cloud/user/MockUsageEventDao.java +++ b/server/src/test/java/com/cloud/user/MockUsageEventDao.java @@ -332,4 +332,19 @@ public void saveDetails(long eventId, Map details) { public Pair, Integer> searchAndCount(SearchCriteria sc, Filter filter, boolean includeRemoved) { return null; } + + @Override + public int expunge(SearchCriteria sc, Filter filter) { + return 0; + } + + @Override + public int batchExpunge(SearchCriteria sc, Long batchSize) { + return 0; + } + + @Override + public int expungeList(List longs) { + return 0; + } } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 7007ad6b33f5..8355648ad1de 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -1115,4 +1115,8 @@ public InternalLoadBalancerElementService getInternalLoadBalancerElementById(lon public List getInternalLoadBalancerElements() { return null; } + + @Override + public void expungeLbVmRefs(List vmIds, Long batchSize) { + } } diff --git a/server/src/test/java/org/apache/cloudstack/resource/ResourceCleanupServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/resource/ResourceCleanupServiceImplTest.java new file mode 100644 index 000000000000..7446e2904885 --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/resource/ResourceCleanupServiceImplTest.java @@ -0,0 +1,656 @@ +// 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 org.apache.cloudstack.resource; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.cloudstack.api.command.admin.resource.PurgeExpungedResourcesCmd; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.InlineLoadBalancerNicMapDao; +import com.cloud.network.dao.LoadBalancerVMMapDao; +import com.cloud.network.dao.OpRouterMonitorServiceDao; +import com.cloud.network.rules.dao.PortForwardingRulesDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.secstorage.CommandExecLogDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ItWorkDao; +import com.cloud.vm.NicVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.ConsoleSessionDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicDetailsDao; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; +import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; + +@RunWith(MockitoJUnitRunner.class) +public class ResourceCleanupServiceImplTest { + + @Mock + VMInstanceDao vmInstanceDao; + @Mock + VolumeDao volumeDao; + @Mock + VolumeDetailsDao volumeDetailsDao; + @Mock + VolumeDataStoreDao volumeDataStoreDao; + @Mock + SnapshotDao snapshotDao; + @Mock + SnapshotDetailsDao snapshotDetailsDao; + @Mock + SnapshotDataStoreDao snapshotDataStoreDao; + @Mock + NicDao nicDao; + @Mock + NicDetailsDao nicDetailsDao; + @Mock + NicExtraDhcpOptionDao nicExtraDhcpOptionDao; + @Mock + InlineLoadBalancerNicMapDao inlineLoadBalancerNicMapDao; + @Mock + VMSnapshotDao vmSnapshotDao; + @Mock + VMSnapshotDetailsDao vmSnapshotDetailsDao; + @Mock + UserVmDetailsDao userVmDetailsDao; + @Mock + AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao; + @Mock + CommandExecLogDao commandExecLogDao; + @Mock + NetworkOrchestrationService networkOrchestrationService; + @Mock + LoadBalancerVMMapDao loadBalancerVMMapDao; + @Mock + NicSecondaryIpDao nicSecondaryIpDao; + @Mock + HighAvailabilityManager highAvailabilityManager; + @Mock + ItWorkDao itWorkDao; + @Mock + OpRouterMonitorServiceDao opRouterMonitorServiceDao; + @Mock + PortForwardingRulesDao portForwardingRulesDao; + @Mock + IPAddressDao ipAddressDao; + @Mock + VmWorkJobDao vmWorkJobDao; + @Mock + ConsoleSessionDao consoleSessionDao; + @Mock + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + + @Spy + @InjectMocks + ResourceCleanupServiceImpl resourceCleanupService = Mockito.spy(new ResourceCleanupServiceImpl()); + + List ids = List.of(1L, 2L); + Long batchSize = 100L; + + private void overrideConfigValue(final ConfigKey configKey, final Object value) { + try { + Field f = ConfigKey.class.getDeclaredField("_value"); + f.setAccessible(true); + f.set(configKey, value); + } catch (IllegalAccessException | NoSuchFieldException e) { + Assert.fail(e.getMessage()); + } + } + + + @Test + public void testPurgeLinkedSnapshotEntitiesNoSnapshots() { + resourceCleanupService.purgeLinkedSnapshotEntities(new ArrayList<>(), batchSize); + Mockito.verify(snapshotDetailsDao, Mockito.never()) + .batchExpungeForResources(Mockito.anyList(), Mockito.anyLong()); + Mockito.verify(snapshotDataStoreDao, Mockito.never()) + .expungeBySnapshotList(Mockito.anyList(), Mockito.anyLong()); + } + + + @Test + public void testPurgeLinkedSnapshotEntities() { + Mockito.when(snapshotDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L); + Mockito.when(snapshotDataStoreDao.expungeBySnapshotList(ids, batchSize)).thenReturn(2); + resourceCleanupService.purgeLinkedSnapshotEntities(ids, batchSize); + Mockito.verify(snapshotDetailsDao, Mockito.times(1)) + .batchExpungeForResources(ids, batchSize); + Mockito.verify(snapshotDataStoreDao, Mockito.times(1)) + .expungeBySnapshotList(ids, batchSize); + } + + @Test + public void testPurgeVolumeSnapshotsNoVolumes() { + Assert.assertEquals(0, resourceCleanupService.purgeVolumeSnapshots(new ArrayList<>(), 50L)); + Mockito.verify(snapshotDao, Mockito.never()).createSearchBuilder(); + } + + @Test + public void testPurgeVolumeSnapshots() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + Mockito.when(sb.entity()).thenReturn(Mockito.mock(SnapshotVO.class)); + Mockito.when(sb.create()).thenReturn(Mockito.mock(SearchCriteria.class)); + Mockito.when(snapshotDao.createSearchBuilder()).thenReturn(sb); + Assert.assertEquals(0, resourceCleanupService.purgeVolumeSnapshots(new ArrayList<>(), 50L)); + Mockito.when(snapshotDao.searchIncludingRemoved(Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.anyBoolean())) + .thenReturn(List.of(Mockito.mock(SnapshotVO.class), Mockito.mock(SnapshotVO.class))); + Mockito.when(snapshotDao.expungeList(Mockito.anyList())).thenReturn(2); + Assert.assertEquals(2, resourceCleanupService.purgeVolumeSnapshots(ids, batchSize)); + } + + @Test + public void testPurgeLinkedVolumeEntitiesNoVolumes() { + resourceCleanupService.purgeLinkedVolumeEntities(new ArrayList<>(), 50L); + Mockito.verify(volumeDetailsDao, Mockito.never()).batchExpungeForResources(Mockito.anyList(), + Mockito.anyLong()); + } + + @Test + public void testPurgeLinkedVolumeEntities() { + Mockito.when(volumeDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L); + Mockito.when(volumeDataStoreDao.expungeByVolumeList(ids, batchSize)).thenReturn(2); + Mockito.doReturn(2L).when(resourceCleanupService).purgeVolumeSnapshots(ids, batchSize); + resourceCleanupService.purgeLinkedVolumeEntities(ids, batchSize); + Mockito.verify(volumeDetailsDao, Mockito.times(1)) + .batchExpungeForResources(ids, batchSize); + Mockito.verify(volumeDataStoreDao, Mockito.times(1)) + .expungeByVolumeList(ids, batchSize); + Mockito.verify(resourceCleanupService, Mockito.times(1)) + .purgeVolumeSnapshots(ids, batchSize); + } + + @Test + public void testPurgeVMVolumesNoVms() { + Assert.assertEquals(0, resourceCleanupService.purgeVMVolumes(new ArrayList<>(), 50L)); + Mockito.verify(volumeDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong()); + } + + @Test + public void testPurgeVMVolumes() { + Mockito.when(volumeDao.searchRemovedByVms(ids, batchSize)) + .thenReturn(List.of(Mockito.mock(VolumeVO.class), Mockito.mock(VolumeVO.class))); + Mockito.when(volumeDao.expungeList(Mockito.anyList())).thenReturn(2); + Mockito.doNothing().when(resourceCleanupService).purgeLinkedVolumeEntities(Mockito.anyList(), + Mockito.eq(batchSize)); + Assert.assertEquals(2, resourceCleanupService.purgeVMVolumes(ids, batchSize)); + } + + @Test + public void testPurgeLinkedNicEntitiesNoNics() { + resourceCleanupService.purgeLinkedNicEntities(new ArrayList<>(), batchSize); + Mockito.verify(nicDetailsDao, Mockito.never()) + .batchExpungeForResources(ids, batchSize); + Mockito.verify(nicExtraDhcpOptionDao, Mockito.never()) + .expungeByNicList(ids, batchSize); + Mockito.verify(inlineLoadBalancerNicMapDao, Mockito.never()) + .expungeByNicList(ids, batchSize); + } + + @Test + public void testPurgeLinkedNicEntities() { + Mockito.when(nicDetailsDao.batchExpungeForResources(ids, batchSize)).thenReturn(2L); + Mockito.when(nicExtraDhcpOptionDao.expungeByNicList(ids, batchSize)).thenReturn(2); + Mockito.when(inlineLoadBalancerNicMapDao.expungeByNicList(ids, batchSize)).thenReturn(2); + resourceCleanupService.purgeLinkedNicEntities(ids, batchSize); + Mockito.verify(nicDetailsDao, Mockito.times(1)) + .batchExpungeForResources(ids, batchSize); + Mockito.verify(nicExtraDhcpOptionDao, Mockito.times(1)) + .expungeByNicList(ids, batchSize); + Mockito.verify(inlineLoadBalancerNicMapDao, Mockito.times(1)) + .expungeByNicList(ids, batchSize); + } + + @Test + public void testPurgeVMNicsNoVms() { + Assert.assertEquals(0, resourceCleanupService.purgeVMNics(new ArrayList<>(), 50L)); + Mockito.verify(nicDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong()); + } + + @Test + public void testPurgeVMNics() { + Mockito.when(nicDao.searchRemovedByVms(ids, batchSize)) + .thenReturn(List.of(Mockito.mock(NicVO.class), Mockito.mock(NicVO.class))); + Mockito.when(nicDao.expungeList(Mockito.anyList())).thenReturn(2); + Mockito.doNothing().when(resourceCleanupService).purgeLinkedNicEntities(Mockito.anyList(), + Mockito.eq(batchSize)); + Assert.assertEquals(2, resourceCleanupService.purgeVMNics(ids, batchSize)); + } + + @Test + public void testPurgeVMSnapshotsNoVms() { + Assert.assertEquals(0, resourceCleanupService.purgeVMSnapshots(new ArrayList<>(), 50L)); + Mockito.verify(vmSnapshotDao, Mockito.never()).searchRemovedByVms(Mockito.anyList(), Mockito.anyLong()); + } + + @Test + public void testPurgeVMSnapshots() { + Mockito.when(vmSnapshotDao.searchRemovedByVms(ids, batchSize)) + .thenReturn(List.of(Mockito.mock(VMSnapshotVO.class), Mockito.mock(VMSnapshotVO.class))); + Mockito.when(vmSnapshotDao.expungeList(Mockito.anyList())).thenReturn(2); + Mockito.when(vmSnapshotDetailsDao.batchExpungeForResources(Mockito.anyList(), + Mockito.eq(batchSize))).thenReturn(2L); + Assert.assertEquals(2, resourceCleanupService.purgeVMSnapshots(ids, batchSize)); + } + + @Test + public void testPurgeLinkedVMEntitiesNoVms() { + resourceCleanupService.purgeLinkedVMEntities(new ArrayList<>(), 50L); + Mockito.verify(resourceCleanupService, Mockito.never()).purgeVMVolumes(Mockito.anyList(), + Mockito.anyLong()); + Mockito.verify(userVmDetailsDao, Mockito.never()) + .batchExpungeForResources(Mockito.anyList(), Mockito.anyLong()); + } + + @Test + public void testPurgeLinkedVMEntities() { + Mockito.doReturn(2L).when(resourceCleanupService).purgeVMVolumes(Mockito.anyList(), + Mockito.eq(batchSize)); + Mockito.doReturn(2L).when(resourceCleanupService).purgeVMNics(Mockito.anyList(), + Mockito.eq(batchSize)); + Mockito.when(userVmDetailsDao.batchExpungeForResources(Mockito.anyList(), Mockito.anyLong())).thenReturn(2L); + Mockito.doReturn(2L).when(resourceCleanupService).purgeVMSnapshots(Mockito.anyList(), + Mockito.eq(batchSize)); + Mockito.when(autoScaleVmGroupVmMapDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(commandExecLogDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(loadBalancerVMMapDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(nicSecondaryIpDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(highAvailabilityManager.expungeWorkItemsByVmList(Mockito.anyList(), Mockito.anyLong())) + .thenReturn(2); + Mockito.when(itWorkDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(opRouterMonitorServiceDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(portForwardingRulesDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(ipAddressDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(vmWorkJobDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + Mockito.when(consoleSessionDao.expungeByVmList(Mockito.anyList(), Mockito.anyLong())).thenReturn(2); + + resourceCleanupService.purgeLinkedVMEntities(ids, batchSize); + + Mockito.verify(resourceCleanupService, Mockito.times(1)).purgeVMVolumes(ids, batchSize); + Mockito.verify(resourceCleanupService, Mockito.times(1)).purgeVMNics(ids, batchSize); + Mockito.verify(userVmDetailsDao, Mockito.times(1)) + .batchExpungeForResources(ids, batchSize); + Mockito.verify(resourceCleanupService, Mockito.times(1)) + .purgeVMSnapshots(ids, batchSize); + Mockito.verify(autoScaleVmGroupVmMapDao, Mockito.times(1)) + .expungeByVmList(ids, batchSize); + Mockito.verify(commandExecLogDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(loadBalancerVMMapDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(nicSecondaryIpDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(highAvailabilityManager, Mockito.times(1)). + expungeWorkItemsByVmList(ids, batchSize); + Mockito.verify(itWorkDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(opRouterMonitorServiceDao, Mockito.times(1)) + .expungeByVmList(ids, batchSize); + Mockito.verify(portForwardingRulesDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(ipAddressDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(vmWorkJobDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + Mockito.verify(consoleSessionDao, Mockito.times(1)).expungeByVmList(ids, batchSize); + } + + @Test + public void testGetVmIdsWithActiveVolumeSnapshotsNoVms() { + Assert.assertTrue(CollectionUtils.isEmpty( + resourceCleanupService.getVmIdsWithActiveVolumeSnapshots(new ArrayList<>()))); + } + + @Test + public void testGetVmIdsWithActiveVolumeSnapshots() { + VolumeVO vol1 = Mockito.mock(VolumeVO.class); + Mockito.when(vol1.getId()).thenReturn(1L); + Mockito.when(vol1.getInstanceId()).thenReturn(1L); + VolumeVO vol2 = Mockito.mock(VolumeVO.class); + Mockito.when(vol2.getId()).thenReturn(2L); + Mockito.when(volumeDao.searchRemovedByVms(ids, null)).thenReturn(List.of(vol1, vol2)); + SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); + Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L); + Mockito.when(snapshotDao.searchByVolumes(Mockito.anyList())).thenReturn(List.of(snapshotVO)); + HashSet vmIds = resourceCleanupService.getVmIdsWithActiveVolumeSnapshots(ids); + Assert.assertTrue(CollectionUtils.isNotEmpty(vmIds)); + Assert.assertEquals(1, vmIds.size()); + Assert.assertEquals(1L, vmIds.toArray()[0]); + } + + @Test + public void testGetFilteredVmIdsForSnapshots() { + Long skippedVmIds = ids.get(0); + Long notSkippedVmIds = ids.get(1); + VMSnapshotVO vmSnapshotVO = Mockito.mock(VMSnapshotVO.class); + Mockito.when(vmSnapshotVO.getVmId()).thenReturn(1L); + Mockito.when(vmSnapshotDao.searchByVms(Mockito.anyList())).thenReturn(List.of(vmSnapshotVO)); + HashSet set = new HashSet<>(); + set.add(1L); + Mockito.doReturn(set).when(resourceCleanupService).getVmIdsWithActiveVolumeSnapshots(ids); + Pair, List> result = resourceCleanupService.getFilteredVmIdsForSnapshots(new ArrayList<>(ids)); + Assert.assertEquals(1, result.first().size()); + Assert.assertEquals(1, result.second().size()); + Assert.assertEquals(notSkippedVmIds, result.first().get(0)); + Assert.assertEquals(skippedVmIds, result.second().get(0)); + } + + @Test + public void testGetVmIdsWithNoActiveSnapshots() { + VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm1.getId()).thenReturn(ids.get(0)); + VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm2.getId()).thenReturn(ids.get(1)); + Mockito.when(vmInstanceDao.searchRemovedByRemoveDate(Mockito.any(), Mockito.any(), + Mockito.anyLong(), Mockito.anyList())).thenReturn(List.of(vm1, vm2)); + Long skippedVmIds = ids.get(0); + Long notSkippedVmIds = ids.get(1); + VMSnapshotVO vmSnapshotVO = Mockito.mock(VMSnapshotVO.class); + Mockito.when(vmSnapshotVO.getVmId()).thenReturn(1L); + Mockito.when(vmSnapshotDao.searchByVms(Mockito.anyList())).thenReturn(List.of(vmSnapshotVO)); + HashSet set = new HashSet<>(); + set.add(1L); + Mockito.doReturn(set).when(resourceCleanupService).getVmIdsWithActiveVolumeSnapshots(Mockito.anyList()); + Pair, List> result = + resourceCleanupService.getVmIdsWithNoActiveSnapshots(new Date(), new Date(), batchSize, + new ArrayList<>()); + Assert.assertEquals(1, result.first().size()); + Assert.assertEquals(1, result.second().size()); + Assert.assertEquals(notSkippedVmIds, result.first().get(0)); + Assert.assertEquals(skippedVmIds, result.second().get(0)); + } + + @Test + public void testPurgeVMEntitiesNoVms() { + Mockito.when(vmInstanceDao.searchRemovedByRemoveDate(Mockito.any(), Mockito.any(), + Mockito.anyLong(), Mockito.anyList())).thenReturn(new ArrayList<>()); + Assert.assertEquals(0, resourceCleanupService.purgeVMEntities(batchSize, new Date(), new Date())); + } + + @Test + public void testPurgeVMEntities() { + Mockito.doReturn(new Pair<>(ids, new ArrayList<>())).when(resourceCleanupService) + .getVmIdsWithNoActiveSnapshots(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyList()); + Mockito.when(vmInstanceDao.expungeList(ids)).thenReturn(ids.size()); + Assert.assertEquals(ids.size(), resourceCleanupService.purgeVMEntities(batchSize, new Date(), new Date())); + } + + @Test + public void testExpungeVMEntityFiltered() { + Mockito.doReturn(new Pair<>(new ArrayList<>(), List.of(ids.get(0)))).when(resourceCleanupService) + .getFilteredVmIdsForSnapshots(Mockito.anyList()); + Assert.assertFalse(resourceCleanupService.purgeVMEntity(ids.get(0))); + } + + @Test + public void testPurgeVMEntityFiltered() { + Mockito.doReturn(new Pair<>(List.of(ids.get(0)), new ArrayList<>())).when(resourceCleanupService) + .getFilteredVmIdsForSnapshots(Mockito.anyList()); + Mockito.doNothing().when(resourceCleanupService) + .purgeLinkedVMEntities(Mockito.anyList(), Mockito.anyLong()); + Mockito.when(vmInstanceDao.expunge(ids.get(0))).thenReturn(true); + Assert.assertTrue(resourceCleanupService.purgeVMEntity(ids.get(0))); + } + + @Test + public void testPurgeVMEntity() { + Mockito.doReturn(new Pair<>(List.of(ids.get(0)), new ArrayList<>())).when(resourceCleanupService) + .getFilteredVmIdsForSnapshots(Mockito.anyList()); + Mockito.doNothing().when(resourceCleanupService) + .purgeLinkedVMEntities(Mockito.anyList(), Mockito.anyLong()); + Mockito.when(vmInstanceDao.expunge(ids.get(0))).thenReturn(true); + Assert.assertTrue(resourceCleanupService.purgeVMEntity(ids.get(0))); + } + + @Test + public void testPurgeEntities() { + Mockito.doReturn((long)ids.size()).when(resourceCleanupService) + .purgeVMEntities(Mockito.anyLong(), Mockito.any(), Mockito.any()); + long result = resourceCleanupService.purgeEntities( + List.of(ResourceCleanupService.ResourceType.VirtualMachine), batchSize, new Date(), new Date()); + Assert.assertEquals(ids.size(), result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidResourceType() { + resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams("Volume", + new Date(), new Date(), batchSize); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidBatchSize() { + resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams( + ResourceCleanupService.ResourceType.VirtualMachine.toString(), + new Date(), new Date(), -1L); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsInvalidDates() { + Calendar cal = Calendar.getInstance(); + Date startDate = new Date(); + cal.setTime(startDate); + cal.add(Calendar.DATE, -1); + Date endDate = cal.getTime(); + resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams( + ResourceCleanupService.ResourceType.VirtualMachine.toString(), + startDate, endDate, 100L); + } + + @Test + public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParams() { + Calendar cal = Calendar.getInstance(); + Date endDate = new Date(); + cal.setTime(endDate); + cal.add(Calendar.DATE, -1); + Date startDate = cal.getTime(); + ResourceCleanupService.ResourceType type = + resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams( + ResourceCleanupService.ResourceType.VirtualMachine.toString(), + startDate, endDate, 100L); + Assert.assertEquals(ResourceCleanupService.ResourceType.VirtualMachine, type); + } + + @Test + public void testGetResourceTypeAndValidatePurgeExpungedResourcesCmdParamsNoValues() { + ResourceCleanupService.ResourceType type = + resourceCleanupService.getResourceTypeAndValidatePurgeExpungedResourcesCmdParams( + null, null, null, null); + Assert.assertNull(type); + } + + @Test + public void testIsVmOfferingPurgeResourcesEnabled() { + Mockito.when(serviceOfferingDetailsDao.getDetail(1L, + ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn(null); + Assert.assertFalse(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(1L)); + Mockito.when(serviceOfferingDetailsDao.getDetail(2L, + ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn("false"); + Assert.assertFalse(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(2L)); + Mockito.when(serviceOfferingDetailsDao.getDetail(3L, + ServiceOffering.PURGE_DB_ENTITIES_KEY)).thenReturn("true"); + Assert.assertTrue(resourceCleanupService.isVmOfferingPurgeResourcesEnabled(3L)); + } + + @Test + public void testPurgeExpungedResource() { + Assert.assertFalse(resourceCleanupService.purgeExpungedResource(1L, null)); + + Mockito.doReturn(true).when(resourceCleanupService) + .purgeExpungedResource(Mockito.anyLong(), Mockito.any()); + Assert.assertTrue(resourceCleanupService.purgeExpungedResource(1L, + ResourceCleanupService.ResourceType.VirtualMachine)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testPurgeExpungedResourcesInvalidResourceType() { + PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class); + Mockito.when(cmd.getResourceType()).thenReturn("Volume"); + resourceCleanupService.purgeExpungedResources(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testPurgeExpungedResourcesInvalidBatchSize() { + PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class); + Mockito.when(cmd.getBatchSize()).thenReturn(-1L); + resourceCleanupService.purgeExpungedResources(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testPurgeExpungedResourcesInvalidDates() { + Calendar cal = Calendar.getInstance(); + Date startDate = new Date(); + cal.setTime(startDate); + cal.add(Calendar.DATE, -1); + Date endDate = cal.getTime(); + PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class); + Mockito.when(cmd.getStartDate()).thenReturn(startDate); + Mockito.when(cmd.getEndDate()).thenReturn(endDate); + resourceCleanupService.purgeExpungedResources(cmd); + } + + @Test + public void testPurgeExpungedResources() { + Mockito.doReturn((long)ids.size()).when(resourceCleanupService).purgeExpungedResourceUsingJob( + ResourceCleanupService.ResourceType.VirtualMachine, batchSize, null, null); + PurgeExpungedResourcesCmd cmd = Mockito.mock(PurgeExpungedResourcesCmd.class); + Mockito.when(cmd.getResourceType()).thenReturn(ResourceCleanupService.ResourceType.VirtualMachine.toString()); + Mockito.when(cmd.getBatchSize()).thenReturn(batchSize); + long result = resourceCleanupService.purgeExpungedResources(cmd); + Assert.assertEquals(ids.size(), result); + } + + @Test + public void testExpungedVmResourcesLaterIfNeededFalse() { + VirtualMachine vm = Mockito.mock(VirtualMachine.class); + Mockito.when(vm.getServiceOfferingId()).thenReturn(1L); + Mockito.doReturn(false).when(resourceCleanupService).isVmOfferingPurgeResourcesEnabled(1L); + resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm); + Mockito.verify(resourceCleanupService, Mockito.never()).purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any()); + } + + @Test + public void testExpungedVmResourcesLaterIfNeeded() { + VirtualMachine vm = Mockito.mock(VirtualMachine.class); + Mockito.when(vm.getServiceOfferingId()).thenReturn(1L); + Mockito.doReturn(true).when(resourceCleanupService).isVmOfferingPurgeResourcesEnabled(1L); + Mockito.doNothing().when(resourceCleanupService).purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any()); + resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm); + Mockito.verify(resourceCleanupService, Mockito.times(1)) + .purgeExpungedResourceLater(Mockito.anyLong(), Mockito.any()); + } + + @Test + public void testGetBatchSizeFromConfig() { + int value = 50; + overrideConfigValue(ResourceCleanupService.ExpungedResourcesPurgeBatchSize, String.valueOf(value)); + Assert.assertEquals(value, resourceCleanupService.getBatchSizeFromConfig()); + } + + @Test + public void testGetResourceTypesFromConfigEmpty() { + overrideConfigValue(ResourceCleanupService.ExpungedResourcePurgeResources, ""); + Assert.assertNull(resourceCleanupService.getResourceTypesFromConfig()); + } + + @Test + public void testGetResourceTypesFromConfig() { + overrideConfigValue(ResourceCleanupService.ExpungedResourcePurgeResources, "VirtualMachine"); + List types = resourceCleanupService.getResourceTypesFromConfig(); + Assert.assertEquals(1, types.size()); + } + + @Test + public void testCalculatePastDateFromConfigNull() { + Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(), + null)); + Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(), + 0)); + } + + @Test(expected = CloudRuntimeException.class) + public void testCalculatePastDateFromConfigFail() { + Assert.assertNull(resourceCleanupService.calculatePastDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(), + -1)); + } + + @Test + public void testCalculatePastDateFromConfig() { + int days = 10; + Date result = resourceCleanupService.calculatePastDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeKeepPastDays.key(), + days); + Date today = new Date(); + long diff = today.getTime() - result.getTime(); + Assert.assertEquals(days, TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS)); + } + + @Test + public void testParseDateFromConfig() { + Assert.assertNull(resourceCleanupService.parseDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), "")); + Date date = resourceCleanupService.parseDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), "2020-01-01"); + Assert.assertNotNull(date); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + Assert.assertEquals(2020, calendar.get(Calendar.YEAR)); + Assert.assertEquals(0, calendar.get(Calendar.MONTH)); + Assert.assertEquals(1, calendar.get(Calendar.DATE)); + } + + @Test(expected = CloudRuntimeException.class) + public void testParseDateFromConfigFail() { + resourceCleanupService.parseDateFromConfig( + ResourceCleanupService.ExpungedResourcesPurgeStartTime.key(), "ABC"); + } +} diff --git a/test/integration/smoke/test_purge_expunged_vms.py b/test/integration/smoke/test_purge_expunged_vms.py new file mode 100644 index 000000000000..4d885dc68e1f --- /dev/null +++ b/test/integration/smoke/test_purge_expunged_vms.py @@ -0,0 +1,364 @@ +# 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. +""" BVT tests for purging expunged VMs and their resources +""" +# Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackAPI import (purgeExpungedResources, + listInfrastructure, + listManagementServers) +from marvin.cloudstackException import CloudstackAPIException +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (Account, + Domain, + ServiceOffering, + DiskOffering, + NetworkOffering, + Network, + VirtualMachine, + Configurations) +from marvin.lib.common import (get_domain, + get_zone, + get_template) +from marvin.lib.utils import (random_gen) +from marvin.lib.decoratorGenerators import skipTestIf +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr +import logging +# Import System modules +import time +from datetime import datetime, timedelta +import pytz +import threading + + +_multiprocess_shared_ = True +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" + +class TestPurgeExpungedVms(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestPurgeExpungedVms, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.dbConnection = cls.testClient.getDbConnection() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + + + cls.hypervisor = cls.testClient.getHypervisorInfo().lower() + cls.hypervisorIsSimulator = False + if cls.hypervisor == 'simulator': + cls.hypervisorIsSimulator = True + + cls._cleanup = [] + cls.logger = logging.getLogger('TestPurgeExpungedVms') + cls.logger.setLevel(logging.DEBUG) + + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + + cls.compute_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"]) + cls._cleanup.append(cls.compute_offering) + + cls.purge_resource_compute_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"], + purgeresources=True) + cls._cleanup.append(cls.purge_resource_compute_offering) + + cls.disk_offering = DiskOffering.create( + cls.apiclient, + cls.services["disk_offering"] + ) + cls._cleanup.append(cls.disk_offering) + + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["l2-network_offering"], + ) + cls._cleanup.append(cls.network_offering) + cls.network_offering.update(cls.apiclient, state='Enabled') + cls.services["network"]["networkoffering"] = cls.network_offering.id + + cls.domain1 = Domain.create( + cls.apiclient, + cls.services["domain"]) + cls._cleanup.append(cls.domain1) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain1.id) + cls._cleanup.append(cls.account) + cls.userapiclient = cls.testClient.getUserApiClient( + UserName=cls.account.name, + DomainName=cls.account.domain + ) + cls.l2_network = Network.create( + cls.userapiclient, + cls.services["l2-network"], + zoneid=cls.zone.id, + networkofferingid=cls.network_offering.id + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = template.id + + @classmethod + def tearDownClass(cls): + super(TestPurgeExpungedVms, cls).tearDownClass() + + def updateVmCreatedRemovedInDb(self, vm_id, timestamp): + # Assuming DB is UTC + utc_timestamp = datetime.strptime(timestamp, DATETIME_FORMAT).astimezone(pytz.utc).strftime(DATETIME_FORMAT) + logging.info("Updating VM: %s created and removed in DB with timestamp: %s" % (vm_id, timestamp)) + query = "UPDATE cloud.vm_instance SET created='%s', removed='%s' WHERE uuid='%s'" % (utc_timestamp, utc_timestamp, vm_id) + self.dbConnection.execute(query) + + def setupExpungedVm(self, timestamp): + logging.info("Setting up expunged VM with timestamp: %s" % timestamp) + vm = VirtualMachine.create( + self.userapiclient, + self.services["virtual_machine"], + serviceofferingid=self.compute_offering.id, + networkids=self.l2_network.id + ) + self.cleanup.append(vm) + vm_id = vm.id + self.vm_ids[timestamp] = vm_id + vm.delete(self.apiclient, expunge=True) + self.cleanup.remove(vm) + self.updateVmCreatedRemovedInDb(vm_id, timestamp) + + def setupExpungedVms(self): + logging.info("Setup VMs") + self.vm_ids = {} + self.threads = [] + days = 3 + for i in range(days): + logging.info("Setting up expunged VMs for day: %d" % (i + 1)) + thread = threading.Thread(target=self.setupExpungedVm, args=(self.timestamps[i],)) + self.threads.append(thread) + thread.start() + + for index, thread in enumerate(self.threads): + logging.info("Before joining thread %d." % index) + thread.join() + logging.info("Thread %d done" % index) + + def setUp(self): + self.cleanup = [] + self.changedConfigurations = {} + self.staticConfigurations = [] + if 'service_offering' in self._testMethodName: + return + if 'background_task' in self._testMethodName and self.hypervisorIsSimulator: + return + self.days = 3 + self.timestamps = [] + for i in range(self.days): + days_ago = (self.days - i) * 2 + now = (datetime.now() - timedelta(days = days_ago)) + timestamp = now.strftime(DATETIME_FORMAT) + self.timestamps.append(timestamp) + self.setupExpungedVms() + + def tearDown(self): + restartServer = False + for config in self.changedConfigurations: + value = self.changedConfigurations[config] + logging.info("Reverting value of config: %s to %s" % (config, value)) + Configurations.update(self.apiclient, + config, + value=value) + if config in self.staticConfigurations: + restartServer = True + if restartServer: + self.restartAllManagementServers() + super(TestPurgeExpungedVms, self).tearDown() + + def executePurgeExpungedResources(self, start_date, end_date): + cmd = purgeExpungedResources.purgeExpungedResourcesCmd() + if start_date is not None: + cmd.startdate = start_date + if end_date is not None: + cmd.enddate = end_date + self.apiclient.purgeExpungedResources(cmd) + + def getVmsInDb(self, vm_ids): + vm_id_str = "','".join(vm_ids) + vm_id_str = "'" + vm_id_str + "'" + query = "SELECT * FROM cloud.vm_instance WHERE uuid IN (%s)" % vm_id_str + response = self.dbConnection.execute(query) + logging.info("DB response from VM: %s:: %s" % (vm_id_str, response)) + return response + + def validatePurgedVmEntriesInDb(self, purged, not_purged): + if purged is not None: + response = self.getVmsInDb(purged) + self.assertTrue(response is None or len(response) == 0, + "Purged VMs still present in DB") + if not_purged is not None: + response = self.getVmsInDb(not_purged) + self.assertTrue(response is not None or len(response) == len(not_purged), + "Not purged VM not present in DB") + + def changeConfiguration(self, name, value): + current_config = Configurations.list(self.apiclient, name=name)[0] + if current_config.value == value: + return + logging.info("Current value for config: %s is %s, changing it to %s" % (name, current_config.value, value)) + self.changedConfigurations[name] = current_config.value + if current_config.isdynamic == False: + self.staticConfigurations.append(name) + Configurations.update(self.apiclient, + name, + value=value) + + def isManagementUp(self): + try: + self.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd()) + return True + except Exception: + return False + + def getManagementServerIps(self): + if self.mgtSvrDetails["mgtSvrIp"] == 'localhost': + return None + cmd = listManagementServers.listManagementServersCmd() + servers = self.apiclient.listManagementServers(cmd) + active_server_ips = [] + active_server_ips.append(self.mgtSvrDetails["mgtSvrIp"]) + for idx, server in enumerate(servers): + if server.state == 'Up' and server.serviceip != self.mgtSvrDetails["mgtSvrIp"]: + active_server_ips.append(server.serviceip) + return active_server_ips + + def restartAllManagementServers(self): + """Restart all management servers + Assumes all servers have same username and password""" + server_ips = self.getManagementServerIps() + if server_ips is None: + self.staticConfigurations.clear() + self.fail("MS restarts cannot be done on %s" % self.mgtSvrDetails["mgtSvrIp"]) + return False + self.debug("Restarting all management server") + for idx, server_ip in enumerate(server_ips): + sshClient = SshClient( + server_ip, + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + command = "service cloudstack-management stop" + sshClient.execute(command) + command = "service cloudstack-management start" + sshClient.execute(command) + + # Waits for management to come up in 10 mins, when it's up it will continue + timeout = time.time() + (10 * 60) + while time.time() < timeout: + if self.isManagementUp() is True: return True + time.sleep(5) + self.debug("Management server did not come up, failing") + return False + + @attr(tags=["advanced"], required_hardware="true") + def test_01_purge_expunged_api_vm_start_date(self): + self.executePurgeExpungedResources(self.timestamps[1], None) + self.validatePurgedVmEntriesInDb( + [self.vm_ids[self.timestamps[1]], self.vm_ids[self.timestamps[2]]], + [self.vm_ids[self.timestamps[0]]] + ) + + @attr(tags=["advanced"], required_hardware="true") + def test_02_purge_expunged_api_vm_end_date(self): + self.executePurgeExpungedResources(None, self.timestamps[1]) + self.validatePurgedVmEntriesInDb( + [self.vm_ids[self.timestamps[0]], self.vm_ids[self.timestamps[1]]], + [self.vm_ids[self.timestamps[2]]] + ) + + @attr(tags=["advanced"], required_hardware="true") + def test_03_purge_expunged_api_vm_start_end_date(self): + self.executePurgeExpungedResources(self.timestamps[0], self.timestamps[2]) + self.validatePurgedVmEntriesInDb( + [self.vm_ids[self.timestamps[0]], self.vm_ids[self.timestamps[1]], self.vm_ids[self.timestamps[2]]], + None + ) + + @attr(tags=["advanced"], required_hardware="true") + def test_04_purge_expunged_api_vm_no_date(self): + self.executePurgeExpungedResources(None, None) + self.validatePurgedVmEntriesInDb( + [self.vm_ids[self.timestamps[0]], self.vm_ids[self.timestamps[1]], self.vm_ids[self.timestamps[2]]], + None + ) + + @attr(tags=["advanced", "skip_setup_vms"], required_hardware="true") + def test_05_purge_expunged_vm_service_offering(self): + purge_delay = 181 + self.changeConfiguration('expunged.resource.purge.job.delay', purge_delay) + vm = VirtualMachine.create( + self.userapiclient, + self.services["virtual_machine"], + serviceofferingid=self.purge_resource_compute_offering.id, + networkids=self.l2_network.id + ) + self.cleanup.append(vm) + vm_id = vm.id + vm.delete(self.apiclient, expunge=True) + self.cleanup.remove(vm) + wait = 1.25 * purge_delay + logging.info("Waiting for 1.25x%d = %d seconds for VM to get purged" % (purge_delay, wait)) + time.sleep(wait) + self.validatePurgedVmEntriesInDb( + [vm_id], + None + ) + + @skipTestIf("hypervisorIsSimulator") + @attr(tags=["advanced"], required_hardware="true") + def test_06_purge_expunged_vm_background_task(self): + purge_task_delay = 60 + self.changeConfiguration('expunged.resources.purge.enabled', 'true') + self.changeConfiguration('expunged.resources.purge.delay', purge_task_delay) + self.changeConfiguration('expunged.resources.purge.keep.past.days', 1) + if len(self.staticConfigurations) > 0: + self.restartAllManagementServers() + wait = 2 * purge_task_delay + logging.info("Waiting for 2x%d = %d seconds for background task to execute" % (purge_task_delay, wait)) + time.sleep(wait) + self.validatePurgedVmEntriesInDb( + [self.vm_ids[self.timestamps[0]], self.vm_ids[self.timestamps[1]], self.vm_ids[self.timestamps[2]]], + None + ) diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index d980583c8ce9..73e81ff597b5 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -276,7 +276,8 @@ 'listVmsForImport': 'Virtual Machine', 'importVm': 'Virtual Machine', 'Webhook': 'Webhook', - 'Webhooks': 'Webhook' + 'Webhooks': 'Webhook', + 'purgeExpungedResources': 'Resource' } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 26e8358779b5..1995f9363db8 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1686,6 +1686,7 @@ "label.publickey": "Public key", "label.publicnetwork": "Public Network", "label.publicport": "Public port", +"label.purgeresources": "Purge Resources", "label.purpose": "Purpose", "label.qostype": "QoS type", "label.quickview": "Quick view", diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index 8e4be8b0423b..37973344f65a 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -40,7 +40,7 @@ export default { filters: ['active', 'inactive'], columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot'] + var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources'] if (store.getters.apis.createServiceOffering && store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') diff --git a/ui/src/views/offering/AddComputeOffering.vue b/ui/src/views/offering/AddComputeOffering.vue index 80d200f1f25d..1fd600ae566e 100644 --- a/ui/src/views/offering/AddComputeOffering.vue +++ b/ui/src/views/offering/AddComputeOffering.vue @@ -343,6 +343,12 @@ + + + +