182 changed files with 1252 additions and 429 deletions
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.cf.ctx.state.geofencing; |
||||
|
|
||||
|
public interface ScheduledRefreshSupported { |
||||
|
|
||||
|
void resetScheduledRefreshTs(); |
||||
|
|
||||
|
long getLastScheduledRefreshTs(); |
||||
|
|
||||
|
void updateScheduledRefreshTs(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.edge; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.edge.Edge; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEvent; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
||||
|
import org.thingsboard.server.common.data.edge.EdgeEventType; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.edge.stats.EdgeStatsCounterService; |
||||
|
import org.thingsboard.server.dao.edge.stats.EdgeStatsKey; |
||||
|
import org.thingsboard.server.dao.edge.stats.MsgCounters; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
import org.thingsboard.server.service.edge.stats.EdgeStatsService; |
||||
|
|
||||
|
import java.time.Duration; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.junit.jupiter.api.Assertions.assertEquals; |
||||
|
import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_ADDED; |
||||
|
import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_PERMANENTLY_FAILED; |
||||
|
import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_PUSHED; |
||||
|
import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_TMP_FAILED; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
@Slf4j |
||||
|
public class EdgeStatsIntegrationTest extends AbstractEdgeTest { |
||||
|
|
||||
|
private static final String STATISTICS_DEVICE_PROFILE = "STATISTICS"; |
||||
|
|
||||
|
private static final long EXPECTED_MSGS_ADDED = 6L; |
||||
|
private static final long EXPECTED_MSGS_PUSHED = 6L; |
||||
|
private static final long EXPECTED_MSGS_PERMANENTLY_FAILED = 0L; |
||||
|
private static final long EXPECTED_MSGS_TMP_FAILED = 0L; |
||||
|
|
||||
|
@Autowired |
||||
|
private EdgeStatsService edgeStatsService; |
||||
|
@Autowired |
||||
|
private EdgeStatsCounterService statsCounterService; |
||||
|
|
||||
|
@Test |
||||
|
public void testReportStats() throws Exception { |
||||
|
// GIVEN
|
||||
|
simulateEdgeEventsAddedDownlinkPushed(); |
||||
|
|
||||
|
// Await Edge Counters Updated
|
||||
|
await().atMost(10, TimeUnit.SECONDS).pollInterval(Duration.ofMillis(200)).untilAsserted(() -> { |
||||
|
MsgCounters counters = statsCounterService.getMsgCountersByEdge().get(edge.getId()); |
||||
|
assertEquals(EXPECTED_MSGS_ADDED, counters.getMsgsAdded().get()); |
||||
|
assertEquals(EXPECTED_MSGS_PUSHED, counters.getMsgsPushed().get()); |
||||
|
assertEquals(EXPECTED_MSGS_PERMANENTLY_FAILED, counters.getMsgsPermanentlyFailed().get()); |
||||
|
assertEquals(EXPECTED_MSGS_TMP_FAILED, counters.getMsgsTmpFailed().get()); |
||||
|
}); |
||||
|
|
||||
|
Thread.sleep(1000); |
||||
|
|
||||
|
// WHEN
|
||||
|
edgeStatsService.reportStats(); |
||||
|
|
||||
|
// THEN
|
||||
|
await().atMost(10, TimeUnit.SECONDS).pollInterval(Duration.ofMillis(200)).untilAsserted(() -> { |
||||
|
List<TsKvEntry> actualStats = fetchLatestStats(); |
||||
|
assertEquals(EXPECTED_MSGS_ADDED, getStatsLongValue(actualStats, DOWNLINK_MSGS_ADDED)); |
||||
|
assertEquals(EXPECTED_MSGS_PUSHED, getStatsLongValue(actualStats, DOWNLINK_MSGS_PUSHED)); |
||||
|
assertEquals(EXPECTED_MSGS_PERMANENTLY_FAILED, getStatsLongValue(actualStats, DOWNLINK_MSGS_PERMANENTLY_FAILED)); |
||||
|
assertEquals(EXPECTED_MSGS_TMP_FAILED, getStatsLongValue(actualStats, DOWNLINK_MSGS_TMP_FAILED)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private long getStatsLongValue(List<TsKvEntry> stats, EdgeStatsKey key) { |
||||
|
return stats.stream().filter(e -> e.getKey().equals(key.getKey())).findFirst().get().getLongValue().orElse(0L); |
||||
|
} |
||||
|
|
||||
|
private List<TsKvEntry> fetchLatestStats() throws ExecutionException, InterruptedException { |
||||
|
return tsService.findLatest( |
||||
|
tenantId, |
||||
|
edge.getId(), |
||||
|
Arrays.stream(EdgeStatsKey.values()).map(EdgeStatsKey::getKey).toList()).get(); |
||||
|
} |
||||
|
|
||||
|
private void simulateEdgeEventsAddedDownlinkPushed() throws InterruptedException, ExecutionException { |
||||
|
statsCounterService.clear(edge.getId()); |
||||
|
|
||||
|
// Save device and assign to edge
|
||||
|
// 2 DOWNLINK_MSGS_ADDED, EdgeEvents: [{DEVICE_PROFILE: ADDED}, {DEVICE: ASSIGNED_TO_EDGE}]
|
||||
|
// 2 DOWNLINK_MSGS_PUSHED, Downlinks: [{deviceProfileUpdateMsg}, {deviceUpdateMsg, deviceProfileUpdateMsg, deviceCredentialsUpdateMsg}]
|
||||
|
edgeImitator.expectMessageAmount(4); |
||||
|
Device savedDevice = saveDevice("StatisticDevice", STATISTICS_DEVICE_PROFILE); |
||||
|
doPost("/api/edge/" + edge.getUuidId() + "/device/" + savedDevice.getUuidId(), Device.class); |
||||
|
Assert.assertTrue(edgeImitator.waitForMessages()); |
||||
|
|
||||
|
// Save asset and assign to edge
|
||||
|
// 1 DOWNLINK_MSGS_ADDED, EdgeEvents: [{ASSET: ASSIGNED_TO_EDGE}]
|
||||
|
// 1 DOWNLINK_MSGS_PUSHED, Downlinks: [{assetUpdateMsg, assetProfileUpdateMsg}]
|
||||
|
edgeImitator.expectMessageAmount(2); |
||||
|
Asset savedAsset = saveAsset("Edge Asset"); |
||||
|
doPost("/api/edge/" + edge.getUuidId() |
||||
|
+ "/asset/" + savedAsset.getUuidId(), Asset.class); |
||||
|
Assert.assertTrue(edgeImitator.waitForMessages()); |
||||
|
|
||||
|
// Create customer and assign edge to the customer
|
||||
|
// 2 DOWNLINK_MSGS_ADDED, EdgeEvents: [{CUSTOMER: ADDED}, {EDGE: ASSIGNED_TO_CUSTOMER}]
|
||||
|
// 2 DOWNLINK_MSGS_PUSHED, Downlinks: [{customerUpdateMsg}, {edgeConfiguration}]
|
||||
|
edgeImitator.expectMessageAmount(2); |
||||
|
Customer customer = new Customer(); |
||||
|
customer.setTitle("Edge Customer"); |
||||
|
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); |
||||
|
doPost("/api/customer/" + savedCustomer.getUuidId() |
||||
|
+ "/edge/" + edge.getUuidId(), Edge.class); |
||||
|
Assert.assertTrue(edgeImitator.waitForMessages()); |
||||
|
|
||||
|
// Send device telemetry downlink for the device
|
||||
|
// 1 DOWNLINK_MSGS_ADDED, EdgeEvents: [{DEVICE: TIMESERIES_UPDATED}]
|
||||
|
// 1 DOWNLINK_MSGS_PUSHED, Downlinks: [{entityData}]
|
||||
|
edgeImitator.expectMessageAmount(1); |
||||
|
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; |
||||
|
JsonNode timeseriesEntityData = JacksonUtil.toJsonNode(timeseriesData); |
||||
|
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, savedDevice.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); |
||||
|
edgeEventService.saveAsync(edgeEvent).get(); |
||||
|
Assert.assertTrue(edgeImitator.waitForMessages()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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.exception; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
|
||||
|
public class ThingsboardRuntimeException extends RuntimeException { |
||||
|
|
||||
|
private ThingsboardErrorCode errorCode; |
||||
|
|
||||
|
public ThingsboardRuntimeException() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public ThingsboardRuntimeException(ThingsboardErrorCode errorCode) { |
||||
|
this.errorCode = errorCode; |
||||
|
} |
||||
|
|
||||
|
public ThingsboardRuntimeException(String message, ThingsboardErrorCode errorCode) { |
||||
|
super(message); |
||||
|
this.errorCode = errorCode; |
||||
|
} |
||||
|
|
||||
|
public ThingsboardRuntimeException(String message, Throwable cause, ThingsboardErrorCode errorCode) { |
||||
|
super(message, cause); |
||||
|
this.errorCode = errorCode; |
||||
|
} |
||||
|
|
||||
|
public ThingsboardRuntimeException(Throwable cause, ThingsboardErrorCode errorCode) { |
||||
|
super(cause); |
||||
|
this.errorCode = errorCode; |
||||
|
} |
||||
|
|
||||
|
public ThingsboardErrorCode getErrorCode() { |
||||
|
return errorCode; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue