diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java index e449f4b002bc..2a45be9ccfee 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java @@ -31,7 +31,6 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.event.Event; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; @APICommand(name = "archiveEvents", description = "Archive one or more events.", responseObject = SuccessResponse.class, entityType = {Event.class}, @@ -82,6 +81,22 @@ public String getType() { return type; } + public void setIds(List ids) { + this.ids = ids; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public void setType(String type) { + this.type = type; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -97,17 +112,12 @@ public long getEntityOwnerId() { @Override public void execute() { - if (ids == null && type == null && endDate == null) { - throw new InvalidParameterValueException("either ids, type or enddate must be specified"); - } else if (startDate != null && endDate == null) { - throw new InvalidParameterValueException("enddate must be specified with startdate parameter"); - } boolean result = _mgr.archiveEvents(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); - } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to archive Events, one or more parameters has invalid values"); + return; } + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to archive Events. One or more parameters have invalid values."); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/event/DeleteEventsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/event/DeleteEventsCmd.java index db183a08e5ab..1f18ecde5a45 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/event/DeleteEventsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/event/DeleteEventsCmd.java @@ -31,7 +31,6 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.event.Event; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; @APICommand(name = "deleteEvents", description = "Delete one or more events.", responseObject = SuccessResponse.class, entityType = {Event.class}, @@ -82,6 +81,22 @@ public String getType() { return type; } + public void setIds(List ids) { + this.ids = ids; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public void setType(String type) { + this.type = type; + } + // /////////////////////////////////////////////////// // ///////////// API Implementation/////////////////// // /////////////////////////////////////////////////// @@ -97,17 +112,12 @@ public long getEntityOwnerId() { @Override public void execute() { - if (ids == null && type == null && endDate == null) { - throw new InvalidParameterValueException("either ids, type or enddate must be specified"); - } else if (startDate != null && endDate == null) { - throw new InvalidParameterValueException("enddate must be specified with startdate parameter"); - } boolean result = _mgr.deleteEvents(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to delete Events, one or more parameters has invalid values"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to delete any events. One or more parameters have invalid values."); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java index b86d1f8b956a..a7ef3560dace 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java @@ -134,7 +134,6 @@ public String getState() { @Override public void execute() { - ListResponse response = _queryService.searchForEvents(this); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/engine/schema/src/main/java/com/cloud/event/dao/EventDao.java b/engine/schema/src/main/java/com/cloud/event/dao/EventDao.java index c50451b03e4a..9c81e6818b28 100644 --- a/engine/schema/src/main/java/com/cloud/event/dao/EventDao.java +++ b/engine/schema/src/main/java/com/cloud/event/dao/EventDao.java @@ -20,19 +20,12 @@ import java.util.List; import com.cloud.event.EventVO; -import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; -import com.cloud.utils.db.SearchCriteria; public interface EventDao extends GenericDao { - public List searchAllEvents(SearchCriteria sc, Filter filter); - - public List listOlderEvents(Date oldTime); - - EventVO findCompletedEvent(long startId); - - public List listToArchiveOrDeleteEvents(List ids, String type, Date startDate, Date endDate, List accountIds); - - public void archiveEvents(List events); + long archiveEvents(List ids, String type, Date startDate, Date endDate, Long accountId, List domainIds, + long limitPerQuery); + long purgeAll(List ids, Date startDate, Date endDate, Date limitDate, String type, Long accountId, List domainIds, + long limitPerQuery); } diff --git a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java index e748e98900eb..1b86ab242b26 100644 --- a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java @@ -16,15 +16,20 @@ // under the License. package com.cloud.event.dao; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; -import com.cloud.event.Event.State; import com.cloud.event.EventVO; -import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -33,83 +38,90 @@ @Component public class EventDaoImpl extends GenericDaoBase implements EventDao { - protected final SearchBuilder CompletedEventSearch; + protected final SearchBuilder ToArchiveOrDeleteEventSearch; public EventDaoImpl() { - CompletedEventSearch = createSearchBuilder(); - CompletedEventSearch.and("state", CompletedEventSearch.entity().getState(), SearchCriteria.Op.EQ); - CompletedEventSearch.and("startId", CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ); - CompletedEventSearch.and("archived", CompletedEventSearch.entity().getArchived(), Op.EQ); - CompletedEventSearch.done(); - ToArchiveOrDeleteEventSearch = createSearchBuilder(); + ToArchiveOrDeleteEventSearch.select("id", SearchCriteria.Func.NATIVE, ToArchiveOrDeleteEventSearch.entity().getId()); ToArchiveOrDeleteEventSearch.and("id", ToArchiveOrDeleteEventSearch.entity().getId(), Op.IN); ToArchiveOrDeleteEventSearch.and("type", ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ); - ToArchiveOrDeleteEventSearch.and("accountIds", ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN); + ToArchiveOrDeleteEventSearch.and("accountId", ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.EQ); + ToArchiveOrDeleteEventSearch.and("domainIds", ToArchiveOrDeleteEventSearch.entity().getDomainId(), Op.IN); ToArchiveOrDeleteEventSearch.and("createdDateB", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.BETWEEN); ToArchiveOrDeleteEventSearch.and("createdDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ); + ToArchiveOrDeleteEventSearch.and("createdDateLT", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT); ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ); ToArchiveOrDeleteEventSearch.done(); } - @Override - public List searchAllEvents(SearchCriteria sc, Filter filter) { - return listIncludingRemovedBy(sc, filter); - } - - @Override - public List listOlderEvents(Date oldTime) { - if (oldTime == null) - return null; - SearchCriteria sc = createSearchCriteria(); - sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime); - sc.addAnd("archived", SearchCriteria.Op.EQ, false); - return listIncludingRemovedBy(sc, null); - } - - @Override - public EventVO findCompletedEvent(long startId) { - SearchCriteria sc = CompletedEventSearch.create(); - sc.setParameters("state", State.Completed); - sc.setParameters("startId", startId); - sc.setParameters("archived", false); - return findOneIncludingRemovedBy(sc); - } - - @Override - public List listToArchiveOrDeleteEvents(List ids, String type, Date startDate, Date endDate, List accountIds) { + private SearchCriteria createEventSearchCriteria(List ids, String type, Date startDate, Date endDate, + Date limitDate, Long accountId, List domainIds) { SearchCriteria sc = ToArchiveOrDeleteEventSearch.create(); - if (ids != null) { - sc.setParameters("id", ids.toArray(new Object[ids.size()])); + + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters("id", ids.toArray(new Object[0])); } - if (type != null) { - sc.setParameters("type", type); + if (CollectionUtils.isNotEmpty(domainIds)) { + sc.setParameters("domainIds", domainIds.toArray(new Object[0])); } if (startDate != null && endDate != null) { sc.setParameters("createdDateB", startDate, endDate); } else if (endDate != null) { sc.setParameters("createdDateL", endDate); } - if (accountIds != null && !accountIds.isEmpty()) { - sc.setParameters("accountIds", accountIds.toArray(new Object[accountIds.size()])); - } + sc.setParametersIfNotNull("accountId", accountId); + sc.setParametersIfNotNull("createdDateLT", limitDate); + sc.setParametersIfNotNull("type", type); sc.setParameters("archived", false); - return search(sc, null); + + return sc; } @Override - public void archiveEvents(List events) { - if (events != null && !events.isEmpty()) { - TransactionLegacy txn = TransactionLegacy.currentTxn(); - txn.start(); - for (EventVO event : events) { - event = lockRow(event.getId(), true); - event.setArchived(true); - update(event.getId(), event); - txn.commit(); + public long archiveEvents(List ids, String type, Date startDate, Date endDate, Long accountId, List domainIds, + long limitPerQuery) { + SearchCriteria sc = createEventSearchCriteria(ids, type, startDate, endDate, null, accountId, domainIds); + Filter filter = null; + if (limitPerQuery > 0) { + filter = new Filter(limitPerQuery); + } + + long archived; + long totalArchived = 0L; + + do { + List events = search(sc, filter); + if (events.isEmpty()) { + break; } - txn.close(); + + archived = archiveEventsInternal(events); + totalArchived += archived; + } while (limitPerQuery > 0 && archived >= limitPerQuery); + + return totalArchived; + } + + @DB + private long archiveEventsInternal(List events) { + final String idsAsString = events.stream() + .map(e -> Long.toString(e.getId())) + .collect(Collectors.joining(",")); + final String query = String.format("UPDATE event SET archived=true WHERE id IN (%s)", idsAsString); + + try (TransactionLegacy txn = TransactionLegacy.currentTxn(); + PreparedStatement pstmt = txn.prepareStatement(query)) { + return pstmt.executeUpdate(); + } catch (SQLException e) { + throw new CloudRuntimeException(e); } } + + @Override + public long purgeAll(List ids, Date startDate, Date endDate, Date limitDate, String type, Long accountId, + List domainIds, long limitPerQuery) { + SearchCriteria sc = createEventSearchCriteria(ids, type, startDate, endDate, limitDate, accountId, domainIds); + return batchExpunge(sc, limitPerQuery); + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java index be073fcce770..892e0c418539 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java @@ -25,7 +25,10 @@ public class DbUpgradeUtils { public static void addIndexIfNeeded(Connection conn, String tableName, String... columnNames) { String indexName = dao.generateIndexName(tableName, columnNames); + addIndexWithNameIfNeeded(conn, tableName, indexName, columnNames); + } + public static void addIndexWithNameIfNeeded(Connection conn, String tableName, String indexName, String... columnNames) { if (!dao.indexExists(conn, tableName, indexName)) { dao.createIndex(conn, tableName, indexName, columnNames); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java index df4743894c9d..73c52411e102 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java @@ -17,6 +17,7 @@ package com.cloud.upgrade.dao; import java.io.InputStream; +import java.sql.Connection; import com.cloud.utils.exception.CloudRuntimeException; @@ -42,4 +43,15 @@ public InputStream[] getPrepareScripts() { return new InputStream[] {script}; } + + @Override + public void performDataMigration(Connection conn) { + addIndexes(conn); + } + + private void addIndexes(Connection conn) { + DbUpgradeUtils.addIndexWithNameIfNeeded(conn, "event", "i_event__multiple_columns_for_generic_search", + "account_id", "domain_id", "archived", "display", "resource_type", "resource_id", "start_id", "type", "level", + "created", "id"); + } } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 4b469abe1fc2..8eb1f831b991 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -920,7 +920,7 @@ private Pair, Integer> searchForEventIdsAndCount(ListEventsCmd cmd) { searchFilter.addOrderBy(EventVO.class, "id", false); SearchBuilder eventSearchBuilder = eventDao.createSearchBuilder(); - eventSearchBuilder.select(null, Func.DISTINCT, eventSearchBuilder.entity().getId()); + eventSearchBuilder.select(null, Func.NATIVE, eventSearchBuilder.entity().getId()); accountMgr.buildACLSearchBuilder(eventSearchBuilder, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); eventSearchBuilder.and("id", eventSearchBuilder.entity().getId(), SearchCriteria.Op.EQ); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 21995d5ae650..9a4af1f1c0ae 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -537,7 +537,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public static final ConfigKey DELETE_QUERY_BATCH_SIZE = new ConfigKey<>("Advanced", Long.class, "delete.query.batch.size", "0", "Indicates the limit applied while deleting entries in bulk. With this, the delete query will apply the limit as many times as necessary," + " to delete all the entries. This is advised when retaining several days of records, which can lead to slowness. <= 0 means that no limit will " + - "be applied. Default value is 0. For now, this is used for deletion of vm & volume stats only.", true); + "be applied. Default value is 0. For now, this is used for deletion of VM stats, volume stats and events.", true); private static final String IOPS_READ_RATE = "IOPS Read"; private static final String IOPS_WRITE_RATE = "IOPS Write"; diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 47dcf60eb32d..ad55381818a5 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -725,7 +725,6 @@ import com.cloud.event.ActionEvent; import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; -import com.cloud.event.EventVO; import com.cloud.event.dao.EventDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; @@ -1216,57 +1215,64 @@ protected void checkPortParameters(final String publicPort, final String private } @Override - public boolean archiveEvents(final ArchiveEventsCmd cmd) { - final Account caller = getCaller(); - final List ids = cmd.getIds(); - boolean result = true; - List permittedAccountIds = new ArrayList<>(); + public boolean archiveEvents(ArchiveEventsCmd cmd) { + List ids = cmd.getIds(); + String type = cmd.getType(); + Date startDate = cmd.getStartDate(); + Date endDate = cmd.getEndDate(); + + validateEventOperationParameters(ids, type, endDate, startDate); + + Long accountId = null; + List domainIds = null; + Account caller = getCaller(); + Account.Type callerType = caller.getType(); - if (_accountService.isNormalUser(caller.getId()) || caller.getType() == Account.Type.PROJECT) { - permittedAccountIds.add(caller.getId()); + if (callerType == Account.Type.NORMAL || callerType == Account.Type.PROJECT) { + accountId = caller.getId(); } else { - final DomainVO domain = _domainDao.findById(caller.getDomainId()); - final List permittedDomainIds = _domainDao.getDomainChildrenIds(domain.getPath()); - permittedAccountIds = _accountDao.getAccountIdsForDomains(permittedDomainIds); + domainIds = _domainDao.getDomainAndChildrenIds(caller.getDomainId()); } - final List events = _eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), cmd.getEndDate(), permittedAccountIds); - final ControlledEntity[] sameOwnerEvents = events.toArray(new ControlledEntity[events.size()]); - _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, false, sameOwnerEvents); + long totalArchived = _eventDao.archiveEvents(ids, type, startDate, endDate, accountId, domainIds, + ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); - if (ids != null && events.size() < ids.size()) { - return false; - } - _eventDao.archiveEvents(events); - return result; + return totalArchived > 0; } @Override - public boolean deleteEvents(final DeleteEventsCmd cmd) { - final Account caller = getCaller(); - final List ids = cmd.getIds(); - boolean result = true; - List permittedAccountIds = new ArrayList<>(); + public boolean deleteEvents(DeleteEventsCmd cmd) { + List ids = cmd.getIds(); + String type = cmd.getType(); + Date startDate = cmd.getStartDate(); + Date endDate = cmd.getEndDate(); - if (_accountMgr.isNormalUser(caller.getId()) || caller.getType() == Account.Type.PROJECT) { - permittedAccountIds.add(caller.getId()); + validateEventOperationParameters(ids, type, endDate, startDate); + + Long accountId = null; + List domainIds = null; + Account caller = getCaller(); + Account.Type callerType = caller.getType(); + + if (callerType == Account.Type.NORMAL || callerType == Account.Type.PROJECT) { + accountId = caller.getId(); } else { - final DomainVO domain = _domainDao.findById(caller.getDomainId()); - final List permittedDomainIds = _domainDao.getDomainChildrenIds(domain.getPath()); - permittedAccountIds = _accountDao.getAccountIdsForDomains(permittedDomainIds); + domainIds = _domainDao.getDomainAndChildrenIds(caller.getDomainId()); } - final List events = _eventDao.listToArchiveOrDeleteEvents(ids, cmd.getType(), cmd.getStartDate(), cmd.getEndDate(), permittedAccountIds); - final ControlledEntity[] sameOwnerEvents = events.toArray(new ControlledEntity[events.size()]); - _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, false, sameOwnerEvents); + long totalRemoved = _eventDao.purgeAll(ids, startDate, endDate, null, type, accountId, domainIds, + ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); - if (ids != null && events.size() < ids.size()) { - return false; + return totalRemoved > 0; + } + + protected void validateEventOperationParameters(List ids, String type, Date endDate, Date startDate) { + if (CollectionUtils.isEmpty(ids) && ObjectUtils.allNull(type, endDate)) { + throw new InvalidParameterValueException("Either 'ids', 'type' or 'enddate' must be specified."); } - for (final EventVO event : events) { - _eventDao.remove(event.getId()); + if (startDate != null && endDate == null) { + throw new InvalidParameterValueException("'startdate' must be specified with 'enddate' parameter."); } - return result; } @Override @@ -4408,12 +4414,10 @@ protected void runInContext() { final Calendar purgeCal = Calendar.getInstance(); purgeCal.add(Calendar.DAY_OF_YEAR, -_purgeDelay); final Date purgeTime = purgeCal.getTime(); - logger.debug("Deleting events older than: " + purgeTime); - final List oldEvents = _eventDao.listOlderEvents(purgeTime); - logger.debug("Found " + oldEvents.size() + " events to be purged"); - for (final EventVO event : oldEvents) { - _eventDao.expunge(event.getId()); - } + logger.debug("Starting to purge all event rows older than [{}].", purgeTime); + long totalRemoved = _eventDao.purgeAll(null, null, null, purgeTime, null, null, null, + ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); + logger.info("Purged a total of [{}] event rows older than [{}].", totalRemoved, purgeTime); } catch (final Exception e) { logger.error("Exception ", e); } finally { diff --git a/server/src/test/java/com/cloud/event/EventControlsUnitTest.java b/server/src/test/java/com/cloud/event/EventControlsUnitTest.java deleted file mode 100644 index 8a968ed5eb9c..000000000000 --- a/server/src/test/java/com/cloud/event/EventControlsUnitTest.java +++ /dev/null @@ -1,88 +0,0 @@ -// 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 -// 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.event; - -import com.cloud.event.dao.EventDao; -import com.cloud.server.ManagementServerImpl; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import junit.framework.TestCase; -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.util.Date; -import java.util.List; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; - -public class EventControlsUnitTest extends TestCase { - private Logger logger = LogManager.getLogger(EventControlsUnitTest.class); - - @Spy - ManagementServerImpl _mgmtServer = new ManagementServerImpl(); - @Mock - AccountManager _accountMgr; - @Mock - EventDao _eventDao; - List _events = null; - private AutoCloseable closeable; - - @Override - @Before - public void setUp() { - closeable = MockitoAnnotations.openMocks(this); - _mgmtServer._eventDao = _eventDao; - _mgmtServer._accountMgr = _accountMgr; - doNothing().when(_accountMgr).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class)); - when(_eventDao.listToArchiveOrDeleteEvents(anyList(), anyString(), any(Date.class), any(Date.class), anyList())).thenReturn(_events); - } - - @Override - @After - public void tearDown() throws Exception { - closeable.close(); - } - - @Test - public void testInjected() throws Exception { - logger.info("Starting test to archive and delete events"); - archiveEvents(); - deleteEvents(); - logger.info("archive/delete events: TEST PASSED"); - } - - protected void archiveEvents() { - // archive alerts - doNothing().when(_eventDao).archiveEvents(_events); - } - - protected void deleteEvents() { - // delete alerts - } -} diff --git a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java index b569368f2482..78e4d766da92 100644 --- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java +++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java @@ -23,6 +23,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.cloud.configuration.ConfigurationManagerImpl; +import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd; +import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd; +import org.apache.commons.lang3.time.DateUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -42,6 +46,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -72,6 +77,7 @@ import com.cloud.dc.Vlan.VlanType; import com.cloud.domain.dao.DomainDao; import com.cloud.api.ApiDBUtils; +import com.cloud.event.dao.EventDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.DetailVO; import com.cloud.host.Host; @@ -174,6 +180,9 @@ public class ManagementServerImplTest { @Mock ExtensionsManager extensionManager; + @Mock + EventDao eventDao; + @Spy @InjectMocks ManagementServerImpl spy = new ManagementServerImpl(); @@ -188,13 +197,6 @@ public class ManagementServerImplTest { public void setup() throws IllegalAccessException, NoSuchFieldException { closeable = MockitoAnnotations.openMocks(this); CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class)); - spy._accountMgr = accountManager; - spy.userDataDao = userDataDao; - spy.templateDao = templateDao; - spy._userVmDao = userVmDao; - spy.annotationDao = annotationDao; - spy._detailsDao = hostDetailsDao; - spy.userDataManager = userDataManager; spy.setHostAllocators(List.of(hostAllocator)); @@ -1059,4 +1061,100 @@ public void testGetExternalVmConsole() { Assert.assertNotNull(spy.getExternalVmConsole(virtualMachine, host)); Mockito.verify(extensionManager).getInstanceConsole(virtualMachine, host); } + + @Test(expected = InvalidParameterValueException.class) + public void validateEventOperationParametersTestIdsAndTypeAndEndDateIsNullThrowInvalidParameterValueException() { + spy.validateEventOperationParameters(null, null, null, new Date()); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateEventOperationParametersTestStartDateIsNotNullAndEndDateIsNullThrowInvalidParameterValueException() { + spy.validateEventOperationParameters(List.of(1L), null, null, new Date()); + } + + private ArchiveEventsCmd createArchiveEventsCmdForTests() { + ArchiveEventsCmd cmd = new ArchiveEventsCmd(); + cmd.setIds(List.of(10L, 11L, 12L, 13L)); + cmd.setType("type"); + cmd.setEndDate(new Date()); + cmd.setStartDate(DateUtils.addDays(cmd.getEndDate(), -1)); + return cmd; + } + + @Test + public void archiveEventsTestArchivesOnlyOwnEventsWhenCallerIsNormalUser() { + Long callerId = 5L; + ArchiveEventsCmd cmd = createArchiveEventsCmdForTests(); + + Mockito.doReturn(Account.Type.NORMAL).when(account).getType(); + Mockito.doReturn(callerId).when(account).getId(); + Mockito.doReturn(account).when(spy).getCaller(); + + spy.archiveEvents(cmd); + + Mockito.verify(spy).validateEventOperationParameters(cmd.getIds(), cmd.getType(), cmd.getEndDate(), cmd.getStartDate()); + Mockito.verify(eventDao).archiveEvents(cmd.getIds(), cmd.getType(), cmd.getStartDate(), cmd.getEndDate(), callerId, + null, ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); + } + + @Test + public void archiveEventsTestArchivesOnlyEventsOfOwnDomainWhenCallerIsAdmin() { + Long callerDomainId = 6L; + List domainIds = List.of(callerDomainId, 7L, 8L); + ArchiveEventsCmd cmd = createArchiveEventsCmdForTests(); + + Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(account).getType(); + Mockito.doReturn(callerDomainId).when(account).getDomainId(); + Mockito.doReturn(account).when(spy).getCaller(); + Mockito.doReturn(domainIds).when(domainDao).getDomainAndChildrenIds(callerDomainId); + + spy.archiveEvents(cmd); + + Mockito.verify(spy).validateEventOperationParameters(cmd.getIds(), cmd.getType(), cmd.getEndDate(), cmd.getStartDate()); + Mockito.verify(eventDao).archiveEvents(cmd.getIds(), cmd.getType(), cmd.getStartDate(), cmd.getEndDate(), null, + domainIds, ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); + } + + private DeleteEventsCmd createDeleteEventsCmdForTests() { + DeleteEventsCmd cmd = new DeleteEventsCmd(); + cmd.setIds(List.of(10L, 11L, 12L, 13L)); + cmd.setType("type"); + cmd.setEndDate(new Date()); + cmd.setStartDate(DateUtils.addDays(cmd.getEndDate(), -1)); + return cmd; + } + + @Test + public void deleteEventsTestDeletesOnlyOwnEventsWhenCallerIsNormalUser() { + Long callerId = 5L; + DeleteEventsCmd cmd = createDeleteEventsCmdForTests(); + + Mockito.doReturn(Account.Type.NORMAL).when(account).getType(); + Mockito.doReturn(callerId).when(account).getId(); + Mockito.doReturn(account).when(spy).getCaller(); + + spy.deleteEvents(cmd); + + Mockito.verify(spy).validateEventOperationParameters(cmd.getIds(), cmd.getType(), cmd.getEndDate(), cmd.getStartDate()); + Mockito.verify(eventDao).purgeAll(cmd.getIds(), cmd.getStartDate(), cmd.getEndDate(), null, cmd.getType(), + callerId, null, ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); + } + + @Test + public void deleteEventsTestDeletesOnlyEventsOfOwnDomainCallerIsAdmin() { + Long callerDomainId = 6L; + List domainIds = List.of(callerDomainId, 7L, 8L); + DeleteEventsCmd cmd = createDeleteEventsCmdForTests(); + + Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(account).getType(); + Mockito.doReturn(callerDomainId).when(account).getDomainId(); + Mockito.doReturn(account).when(spy).getCaller(); + Mockito.doReturn(domainIds).when(domainDao).getDomainAndChildrenIds(callerDomainId); + + spy.deleteEvents(cmd); + + Mockito.verify(spy).validateEventOperationParameters(cmd.getIds(), cmd.getType(), cmd.getEndDate(), cmd.getStartDate()); + Mockito.verify(eventDao).purgeAll(cmd.getIds(), cmd.getStartDate(), cmd.getEndDate(), null, cmd.getType(), + null, domainIds, ConfigurationManagerImpl.DELETE_QUERY_BATCH_SIZE.value()); + } }