9 changed files with 344 additions and 34 deletions
@ -0,0 +1,303 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed 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.thingsboard.server.service.housekeeper; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.TextNode; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.mock.mockito.SpyBean; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; |
|||
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.EventInfo; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.event.EventType; |
|||
import org.thingsboard.server.common.data.event.LifecycleEvent; |
|||
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.RuleNodeId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|||
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.page.TimePageLink; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
|||
import org.thingsboard.server.common.data.rule.RuleChainType; |
|||
import org.thingsboard.server.common.data.rule.RuleNode; |
|||
import org.thingsboard.server.controller.AbstractControllerTest; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.dao.housekeeper.HousekeeperService; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.mockito.ArgumentMatchers.argThat; |
|||
import static org.mockito.Mockito.verify; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@DaoSqlTest |
|||
@TestPropertySource(properties = { |
|||
"transport.http.enabled=true" |
|||
}) |
|||
public class HousekeeperServiceTest extends AbstractControllerTest { |
|||
|
|||
@SpyBean |
|||
private HousekeeperService housekeeperService; |
|||
@Autowired |
|||
private EventService eventService; |
|||
@Autowired |
|||
private TimeseriesService timeseriesService; |
|||
@Autowired |
|||
private AttributesService attributesService; |
|||
@Autowired |
|||
private RuleChainService ruleChainService; |
|||
@Autowired |
|||
private AlarmService alarmService; |
|||
|
|||
private TenantId tenantId; |
|||
|
|||
private static final String TELEMETRY_KEY = "telemetry1"; |
|||
private static final String ATTRIBUTE_KEY = "_attribute1"; |
|||
private static final String KV_VALUE = "ewfewfwef"; |
|||
|
|||
@Before |
|||
public void setUp() throws Exception { |
|||
loginTenantAdmin(); |
|||
this.tenantId = super.tenantId; |
|||
} |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
} |
|||
|
|||
@Test |
|||
public void whenDeviceIsDeleted_thenCleanUpRelatedData() throws Exception { |
|||
Device device = createDevice("test", "test"); |
|||
createRelatedData(device.getId()); |
|||
|
|||
doDelete("/api/device/" + device.getId()).andExpect(status().isOk()); |
|||
|
|||
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { |
|||
verifyNoRelatedData(device.getId()); |
|||
}); |
|||
} |
|||
|
|||
@Test |
|||
public void whenRuleChainIsDeleted_thenCleanUpRelatedData() throws Exception { |
|||
RuleChainMetaData ruleChainMetaData = createRuleChain(); |
|||
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId(); |
|||
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId(); |
|||
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId(); |
|||
createRelatedData(ruleChainId); |
|||
createRelatedData(ruleNode1Id); |
|||
createRelatedData(ruleNode2Id); |
|||
|
|||
doDelete("/api/ruleChain/" + ruleChainId).andExpect(status().isOk()); |
|||
|
|||
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { |
|||
verifyNoRelatedData(ruleNode1Id); |
|||
verifyNoRelatedData(ruleNode2Id); |
|||
verifyNoRelatedData(ruleChainId); |
|||
}); |
|||
} |
|||
|
|||
@Test |
|||
public void whenUserIsDeleted_thenCleanUpRelatedData() throws Exception { |
|||
Device device = createDevice("test", "test"); |
|||
UserId userId = customerUserId; |
|||
createRelatedData(userId); |
|||
Alarm alarm = Alarm.builder() |
|||
.type("test") |
|||
.tenantId(tenantId) |
|||
.originator(device.getId()) |
|||
.severity(AlarmSeverity.MAJOR) |
|||
.build(); |
|||
alarm = doPost("/api/alarm", alarm, Alarm.class); |
|||
alarm = doPost("/api/alarm/" + alarm.getId() + "/assign/" + userId, "", Alarm.class); |
|||
assertThat(alarm.getAssigneeId()).isEqualTo(userId); |
|||
assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, new PageLink(100)).getData()).isNotEmpty(); |
|||
|
|||
doDelete("/api/user/" + userId).andExpect(status().isOk()); |
|||
|
|||
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { |
|||
verifyNoRelatedData(userId); |
|||
}); |
|||
assertThat(alarmService.findAlarmById(tenantId, alarm.getId()).getAssigneeId()).isNull(); |
|||
} |
|||
|
|||
@Test |
|||
public void whenTenantIsDeleted_thenDeleteAllEntitiesAndCleanUpRelatedData() throws Exception { |
|||
loginDifferentTenant(); |
|||
tenantId = differentTenantId; |
|||
|
|||
createRelatedData(tenantId); |
|||
|
|||
Device device = createDevice("test", "test"); |
|||
createRelatedData(device.getId()); |
|||
|
|||
RuleChainMetaData ruleChainMetaData = createRuleChain(); |
|||
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId(); |
|||
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId(); |
|||
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId(); |
|||
createRelatedData(ruleChainId); |
|||
createRelatedData(ruleNode1Id); |
|||
createRelatedData(ruleNode2Id); |
|||
|
|||
UserId userId = savedDifferentTenantUser.getId(); |
|||
createRelatedData(userId); |
|||
|
|||
loginSysAdmin(); |
|||
deleteDifferentTenant(); |
|||
|
|||
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> { |
|||
verifyNoRelatedData(device.getId()); |
|||
verifyNoRelatedData(ruleNode1Id); |
|||
verifyNoRelatedData(ruleNode2Id); |
|||
verifyNoRelatedData(ruleChainId); |
|||
verifyNoRelatedData(userId); |
|||
verifyNoRelatedData(tenantId); |
|||
}); |
|||
} |
|||
|
|||
private void createRelatedData(EntityId entityId) throws Exception { |
|||
createTelemetry(entityId); |
|||
for (String scope : List.of(DataConstants.SERVER_SCOPE, DataConstants.SHARED_SCOPE, DataConstants.CLIENT_SCOPE)) { |
|||
createAttribute(entityId, scope, scope + ATTRIBUTE_KEY); |
|||
} |
|||
createEvent(entityId); |
|||
} |
|||
|
|||
private void verifyNoRelatedData(EntityId entityId) throws Exception { |
|||
List<HousekeeperTaskType> expectedTaskTypes = List.of(HousekeeperTaskType.DELETE_TELEMETRY, HousekeeperTaskType.DELETE_ATTRIBUTES, HousekeeperTaskType.DELETE_EVENTS, HousekeeperTaskType.DELETE_ENTITY_ALARMS); |
|||
for (HousekeeperTaskType taskType : expectedTaskTypes) { |
|||
verify(housekeeperService).submitTask(argThat(task -> task.getTaskType() == taskType && task.getEntityId().equals(entityId))); |
|||
} |
|||
|
|||
assertThat(getLatestTelemetry(entityId)).isNull(); |
|||
assertThat(getTimeseriesHistory(entityId)).isEmpty(); |
|||
for (String scope : List.of(DataConstants.SERVER_SCOPE, DataConstants.SHARED_SCOPE, DataConstants.CLIENT_SCOPE)) { |
|||
assertThat(getAttribute(entityId, scope, scope + ATTRIBUTE_KEY)).isNull(); |
|||
} |
|||
assertThat(getEvents(entityId)).isEmpty(); |
|||
} |
|||
|
|||
private void createAttribute(EntityId entityId, String scope, String key) throws Exception { |
|||
attributesService.save(tenantId, entityId, scope, new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry(key, KV_VALUE))).get(); |
|||
} |
|||
|
|||
private void createTelemetry(EntityId entityId) throws Exception { |
|||
timeseriesService.save(tenantId, entityId, new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(TELEMETRY_KEY, KV_VALUE))).get(); |
|||
} |
|||
|
|||
private void createEvent(EntityId entityId) { |
|||
LifecycleEvent event = LifecycleEvent.builder() |
|||
.tenantId(tenantId) |
|||
.entityId(entityId.getId()) |
|||
.serviceId("test") |
|||
.lcEventType("test") |
|||
.success(true) |
|||
.build(); |
|||
eventService.saveAsync(event); |
|||
await().atMost(10, TimeUnit.SECONDS) |
|||
.until(() -> !getEvents(entityId).isEmpty()); |
|||
|
|||
} |
|||
|
|||
private TsKvEntry getLatestTelemetry(EntityId entityId) throws Exception { |
|||
return timeseriesService.findLatest(tenantId, entityId, HousekeeperServiceTest.TELEMETRY_KEY).get().orElse(null); |
|||
} |
|||
|
|||
private List<TsKvEntry> getTimeseriesHistory(EntityId entityId) throws Exception { |
|||
return timeseriesService.findAll(tenantId, entityId, List.of(new BaseReadTsKvQuery(HousekeeperServiceTest.TELEMETRY_KEY, 0, System.currentTimeMillis(), 10, "DESC"))).get(); |
|||
} |
|||
|
|||
private AttributeKvEntry getAttribute(EntityId entityId, String scope, String key) throws Exception { |
|||
return attributesService.find(tenantId, entityId, scope, key).get().orElse(null); |
|||
} |
|||
|
|||
private List<EventInfo> getEvents(EntityId entityId) { |
|||
return eventService.findEvents(tenantId, entityId, EventType.LC_EVENT, new TimePageLink(100)).getData() |
|||
.stream().filter(event -> Optional.ofNullable(event.getBody()).map(body -> body.get("event")) |
|||
.map(JsonNode::asText).orElse("").equals("test")) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
private RuleChainMetaData createRuleChain() { |
|||
RuleChain ruleChain = new RuleChain(); |
|||
ruleChain.setTenantId(tenantId); |
|||
ruleChain.setName("Test"); |
|||
ruleChain.setType(RuleChainType.CORE); |
|||
ruleChain.setDebugMode(true); |
|||
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); |
|||
ruleChain = ruleChainService.saveRuleChain(ruleChain); |
|||
RuleChainId ruleChainId = ruleChain.getId(); |
|||
|
|||
RuleChainMetaData metaData = new RuleChainMetaData(); |
|||
metaData.setRuleChainId(ruleChainId); |
|||
|
|||
RuleNode ruleNode1 = new RuleNode(); |
|||
ruleNode1.setName("Simple Rule Node 1"); |
|||
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); |
|||
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); |
|||
ruleNode1.setDebugMode(true); |
|||
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); |
|||
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); |
|||
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1)); |
|||
|
|||
RuleNode ruleNode2 = new RuleNode(); |
|||
ruleNode2.setName("Simple Rule Node 2"); |
|||
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); |
|||
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); |
|||
ruleNode2.setDebugMode(true); |
|||
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); |
|||
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); |
|||
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2)); |
|||
|
|||
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); |
|||
metaData.setFirstNodeIndex(0); |
|||
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); |
|||
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity()); |
|||
return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId); |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue