diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 57d77e8166..efa5aee965 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -194,17 +194,25 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement processMqttMsg(ctx, message); } else { log.error("[{}] Message decoding failed: {}", sessionId, message.decoderResult().cause().getMessage()); - ctx.close(); + closeCtx(ctx); } } else { log.debug("[{}] Received non mqtt message: {}", sessionId, msg.getClass().getSimpleName()); - ctx.close(); + closeCtx(ctx); } } finally { ReferenceCountUtil.safeRelease(msg); } } + private void closeCtx(ChannelHandlerContext ctx) { + if (!rpcAwaitingAck.isEmpty()) { + log.debug("[{}] Cleanup rpc awaiting ack map due to session close!", sessionId); + rpcAwaitingAck.clear(); + } + ctx.close(); + } + InetSocketAddress getAddress(ChannelHandlerContext ctx) { var address = ctx.channel().attr(MqttTransportService.ADDRESS).get(); if (address == null) { @@ -221,7 +229,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) { if (msg.fixedHeader() == null) { log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort()); - ctx.close(); + closeCtx(ctx); return; } deviceSessionCtx.setChannel(ctx); @@ -258,21 +266,21 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } else { log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName); - ctx.close(); + closeCtx(ctx); } } catch (RuntimeException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); - ctx.close(); + closeCtx(ctx); } catch (AdaptorException e) { log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); - ctx.close(); + closeCtx(ctx); } break; case PINGREQ: ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; case DISCONNECT: - ctx.close(); + closeCtx(ctx); break; } } @@ -282,7 +290,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) { log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}", deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize()); - ctx.close(); + closeCtx(ctx); return; } @@ -316,7 +324,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } break; case DISCONNECT: - ctx.close(); + closeCtx(ctx); break; case PUBACK: int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId(); @@ -381,7 +389,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } catch (RuntimeException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC); - ctx.close(); + closeCtx(ctx); } catch (AdaptorException e) { log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); sendAckOrCloseSession(ctx, topicName, msgId); @@ -421,7 +429,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } catch (RuntimeException e) { log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC); - ctx.close(); + closeCtx(ctx); } catch (AdaptorException | ThingsboardException | InvalidProtocolBufferException e) { log.error("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); sendAckOrCloseSession(ctx, topicName, msgId); @@ -523,7 +531,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(createMqttPubAckMsg(deviceSessionCtx, msgId, ReturnCode.PAYLOAD_FORMAT_INVALID)); } else { log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); - ctx.close(); + closeCtx(ctx); } } @@ -579,7 +587,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); - ctx.close(); + closeCtx(ctx); } }; } @@ -615,7 +623,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onError(Throwable e) { log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); ack(ctx, msgId, ReturnCode.IMPLEMENTATION_SPECIFIC); - ctx.close(); + closeCtx(ctx); } } @@ -650,7 +658,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e); - ctx.close(); + closeCtx(ctx); } } @@ -672,7 +680,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx .getPayloadAdaptor() .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes())); - ctx.close(); + closeCtx(ctx); } private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { @@ -922,7 +930,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onError(Throwable e) { log.trace("[{}] Failed to process credentials: {}", address, userName, e); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); + closeCtx(ctx); } }); } @@ -945,14 +953,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onError(Throwable e) { log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); + closeCtx(ctx); } }); } catch (Exception e) { context.onAuthFailure(address); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.NOT_AUTHORIZED_5, connectMessage)); log.trace("[{}] X509 auth failure: {}", sessionId, address, e); - ctx.close(); + closeCtx(ctx); } } @@ -1000,7 +1008,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.error("[{}] Unexpected Exception", sessionId, cause); } - ctx.close(); + closeCtx(ctx); if (cause instanceof OutOfMemoryError) { log.error("Received critical error. Going to shutdown the service."); System.exit(1); @@ -1082,7 +1090,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } catch (Exception e) { log.trace("[{}][{}] Failed to fetch sparkplugDevice connect, sparkplugTopicName", sessionId, deviceSessionCtx.getDeviceInfo().getDeviceName(), e); ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); + closeCtx(ctx); } } @@ -1139,7 +1147,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } ctx.writeAndFlush(createMqttConnAckMsg(returnCode, connectMessage)); - ctx.close(); + closeCtx(ctx); } else { context.onAuthSuccess(address); deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo()); @@ -1168,7 +1176,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.warn("[{}] Failed to submit session event", sessionId, e); } ctx.writeAndFlush(createMqttConnAckMsg(ReturnCode.SERVER_UNAVAILABLE_5, connectMessage)); - ctx.close(); + closeCtx(ctx); } }); } @@ -1216,7 +1224,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); - deviceSessionCtx.getChannel().close(); + closeCtx(deviceSessionCtx.getChannel()); } @Override @@ -1327,7 +1335,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onDeviceDeleted(DeviceId deviceId) { context.onAuthFailure(address); ChannelHandlerContext ctx = deviceSessionCtx.getChannel(); - ctx.close(); + closeCtx(ctx); } public void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, ThingsboardErrorCode result, String errorMsg) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java b/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java index 7ed0c811b2..8b4b650766 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java +++ b/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java @@ -36,7 +36,7 @@ public class TimescaleSqlInitializer { "sql/schema-views-and-functions.sql", "sql/system-data.sql", "sql/system-test-psql.sql"); - private static final String dropAllTablesSqlFile = "sql/timescale/drop-all-tables.sql"; + private static final String dropAllTablesSqlFile = "sql/psql/drop-all-tables.sql"; public static void initDb(Connection conn) { cleanUpDb(conn); diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql deleted file mode 100644 index 3ad3dac3b3..0000000000 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ /dev/null @@ -1,42 +0,0 @@ -DROP TABLE IF EXISTS admin_settings; -DROP TABLE IF EXISTS entity_alarm; -DROP TABLE IF EXISTS alarm; -DROP TABLE IF EXISTS asset; -DROP TABLE IF EXISTS audit_log; -DROP TABLE IF EXISTS attribute_kv; -DROP TABLE IF EXISTS component_descriptor; -DROP TABLE IF EXISTS customer; -DROP TABLE IF EXISTS device; -DROP TABLE IF EXISTS device_credentials; -DROP TABLE IF EXISTS event; -DROP TABLE IF EXISTS relation; -DROP TABLE IF EXISTS tb_user; -DROP TABLE IF EXISTS tenant; -DROP TABLE IF EXISTS ts_kv; -DROP TABLE IF EXISTS ts_kv_dictionary; -DROP TABLE IF EXISTS ts_kv_latest; -DROP TABLE IF EXISTS user_credentials; -DROP TABLE IF EXISTS widget_type; -DROP TABLE IF EXISTS widgets_bundle; -DROP TABLE IF EXISTS entity_view; -DROP TABLE IF EXISTS device_profile; -DROP TABLE IF EXISTS tenant_profile; -DROP TABLE IF EXISTS dashboard; -DROP TABLE IF EXISTS rule_node_state; -DROP TABLE IF EXISTS rule_node; -DROP TABLE IF EXISTS rule_chain; -DROP TABLE IF EXISTS oauth2_mobile; -DROP TABLE IF EXISTS oauth2_domain; -DROP TABLE IF EXISTS oauth2_registration; -DROP TABLE IF EXISTS oauth2_params; -DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS oauth2_client_registration; -DROP TABLE IF EXISTS oauth2_client_registration_info; -DROP TABLE IF EXISTS api_usage_state; -DROP TABLE IF EXISTS resource; -DROP TABLE IF EXISTS ota_package; -DROP TABLE IF EXISTS edge; -DROP TABLE IF EXISTS edge_event; -DROP TABLE IF EXISTS rpc; -DROP TABLE IF EXISTS queue; -DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 2116bc367d..2fca79e01e 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -1,5 +1,23 @@ +DROP FUNCTION IF EXISTS to_uuid; +DROP FUNCTION IF EXISTS create_or_update_active_alarm; +DROP FUNCTION IF EXISTS update_alarm; +DROP FUNCTION IF EXISTS acknowledge_alarm; +DROP FUNCTION IF EXISTS clear_alarm; +DROP FUNCTION IF EXISTS assign_alarm; +DROP FUNCTION IF EXISTS unassign_alarm; + +DROP PROCEDURE IF EXISTS cleanup_edge_events_by_ttl; +DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl; +DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv; + +DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; +DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; +DROP VIEW IF EXISTS device_info_view CASCADE; +DROP VIEW IF EXISTS alarm_info CASCADE; + DROP TABLE IF EXISTS admin_settings; DROP TABLE IF EXISTS entity_alarm; +DROP TABLE IF EXISTS alarm_comment; DROP TABLE IF EXISTS alarm; DROP TABLE IF EXISTS asset; DROP TABLE IF EXISTS audit_log; @@ -8,9 +26,12 @@ DROP TABLE IF EXISTS component_descriptor; DROP TABLE IF EXISTS customer; DROP TABLE IF EXISTS device; DROP TABLE IF EXISTS device_credentials; -DROP TABLE IF EXISTS event; +DROP TABLE IF EXISTS rule_node_debug_event; +DROP TABLE IF EXISTS rule_chain_debug_event; +DROP TABLE IF EXISTS stats_event; +DROP TABLE IF EXISTS lc_event; +DROP TABLE IF EXISTS error_event; DROP TABLE IF EXISTS relation; -DROP TABLE IF EXISTS tb_user; DROP TABLE IF EXISTS tenant; DROP TABLE IF EXISTS ts_kv; DROP TABLE IF EXISTS ts_kv_latest; @@ -31,13 +52,22 @@ DROP TABLE IF EXISTS oauth2_mobile; DROP TABLE IF EXISTS oauth2_domain; DROP TABLE IF EXISTS oauth2_registration; DROP TABLE IF EXISTS oauth2_params; -DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; +DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS ota_package; DROP TABLE IF EXISTS api_usage_state; DROP TABLE IF EXISTS resource; DROP TABLE IF EXISTS firmware; DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS edge_event; DROP TABLE IF EXISTS rpc; -DROP TABLE IF EXISTS queue; \ No newline at end of file +DROP TABLE IF EXISTS queue; +DROP TABLE IF EXISTS notification; +DROP TABLE IF EXISTS notification_request; +DROP TABLE IF EXISTS notification_rule; +DROP TABLE IF EXISTS notification_template; +DROP TABLE IF EXISTS notification_target; +DROP TABLE IF EXISTS user_settings; +DROP TABLE IF EXISTS user_auth_settings; +DROP TABLE IF EXISTS tb_user; \ No newline at end of file diff --git a/dao/src/test/resources/sql/system-test-psql.sql b/dao/src/test/resources/sql/system-test-psql.sql index 8d3f08a32f..172731b9c5 100644 --- a/dao/src/test/resources/sql/system-test-psql.sql +++ b/dao/src/test/resources/sql/system-test-psql.sql @@ -1,2 +1,2 @@ --PostgreSQL specific truncate to fit constraints -TRUNCATE TABLE device_credentials, device, device_profile, asset, asset_profile, ota_package, rule_node_state, rule_node, rule_chain; \ No newline at end of file +TRUNCATE TABLE device_credentials, device, device_profile, asset, asset_profile, ota_package, rule_node_state, rule_node, rule_chain, alarm_comment, alarm, entity_alarm; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql deleted file mode 100644 index 80330a5ef5..0000000000 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ /dev/null @@ -1,38 +0,0 @@ -DROP TABLE IF EXISTS admin_settings; -DROP TABLE IF EXISTS entity_alarm; -DROP TABLE IF EXISTS alarm; -DROP TABLE IF EXISTS asset; -DROP TABLE IF EXISTS audit_log; -DROP TABLE IF EXISTS attribute_kv; -DROP TABLE IF EXISTS component_descriptor; -DROP TABLE IF EXISTS customer; -DROP TABLE IF EXISTS device; -DROP TABLE IF EXISTS device_credentials; -DROP TABLE IF EXISTS event; -DROP TABLE IF EXISTS relation; -DROP TABLE IF EXISTS tb_user; -DROP TABLE IF EXISTS tenant; -DROP TABLE IF EXISTS ts_kv; -DROP TABLE IF EXISTS ts_kv_latest; -DROP TABLE IF EXISTS ts_kv_dictionary; -DROP TABLE IF EXISTS user_credentials; -DROP TABLE IF EXISTS widget_type; -DROP TABLE IF EXISTS widgets_bundle; -DROP TABLE IF EXISTS rule_node_state; -DROP TABLE IF EXISTS rule_node; -DROP TABLE IF EXISTS rule_chain; -DROP TABLE IF EXISTS entity_view; -DROP TABLE IF EXISTS device_profile; -DROP TABLE IF EXISTS tenant_profile; -DROP TABLE IF EXISTS asset_profile; -DROP TABLE IF EXISTS dashboard; -DROP TABLE IF EXISTS edge; -DROP TABLE IF EXISTS edge_event; -DROP TABLE IF EXISTS tb_schema_settings; -DROP TABLE IF EXISTS oauth2_client_registration; -DROP TABLE IF EXISTS oauth2_client_registration_info; -DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS api_usage_state; -DROP TABLE IF EXISTS resource; -DROP TABLE IF EXISTS firmware; -DROP TABLE IF EXISTS queue; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java index 4de6fbaf17..ea1e669537 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java @@ -55,6 +55,7 @@ import java.io.ByteArrayInputStream; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -157,6 +158,13 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest { .findFirst().orElse(null); } + public List getDevicesByName(List deviceNames) { + List allDevices = testRestClient.getDevices(pageLink).getData(); + return allDevices.stream() + .filter(device -> deviceNames.contains(device.getName())) + .collect(Collectors.toList()); + } + public List getRuleChainsByName(String name) { return testRestClient.getRuleChains(pageLink).getData().stream() .filter(s -> s.getName().equals(name)) @@ -174,13 +182,10 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest { } public DeviceProfile getDeviceProfileByName(String name) { - try { - return testRestClient.getDeviceProfiles(pageLink).getData().stream() - .filter(x -> x.getName().equals(name)).collect(Collectors.toList()).get(0); - } catch (Exception e) { - log.error("No such device profile with name: " + name); - return null; - } + return testRestClient.getDeviceProfiles(pageLink).getData().stream() + .filter(x -> x.getName().equals(name)) + .findFirst() + .orElse(null); } public AssetProfile getAssetProfileByName(String name) { @@ -291,6 +296,15 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest { } } + public void deleteDevicesByName(List deviceNames) { + List devices = getDevicesByName(deviceNames); + for (Device device : devices) { + if (device != null) { + testRestClient.deleteDevice(device.getId()); + } + } + } + public void deleteDeviceProfileByTitle(String deviceProfileTitle) { DeviceProfile deviceProfile = getDeviceProfileByName(deviceProfileTitle); if (deviceProfile != null) { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java index a4404858d9..e400a67567 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java @@ -18,6 +18,8 @@ package org.thingsboard.server.msa.ui.pages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import java.util.List; + public class DevicePageElements extends OtherPageElementsHelper { public DevicePageElements(WebDriver driver) { super(driver); @@ -31,7 +33,7 @@ public class DevicePageElements extends OtherPageElementsHelper { private static final String CHOOSE_CUSTOMER_FOR_ASSIGN_FIELD = "//input[@formcontrolname='entity']"; private static final String ENTITY_FROM_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']"; private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button"; - private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']"; + private static final String SUBMIT_BTN = "//button[@type='submit']"; private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button"; private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span"; private static final String ADD_DEVICE_VIEW = "//tb-device-wizard"; @@ -47,12 +49,17 @@ public class DevicePageElements extends OtherPageElementsHelper { private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span"; private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']"; private static final String DEVICE_DEVICE_PROFILE_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-deviceProfileName')]/span"; - protected static final String ASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),'assignment_ind')]/ancestor::button"; - protected static final String UNASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),' assignment_return')]/ancestor::button"; - protected static final String ASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Assign to customer')]/parent::button"; - protected static final String UNASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Unassign from customer')]/parent::button"; - protected static final String ASSIGNED_FIELD_DETAILS_TAB = "//mat-label[text() = 'Assigned to customer']/parent::label/parent::div/input"; - protected static final String ASSIGN_MARKED_DEVICE_BTN = "//mat-icon[text() = 'assignment_ind']/parent::button"; + private static final String ASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),'assignment_ind')]/ancestor::button"; + private static final String UNASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),' assignment_return')]/ancestor::button"; + private static final String ASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Assign to customer')]/parent::button"; + private static final String UNASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Unassign from customer')]/parent::button"; + private static final String ASSIGNED_FIELD_DETAILS_TAB = "//mat-label[text() = 'Assigned to customer']/parent::label/parent::div/input"; + private static final String ASSIGN_MARKED_DEVICE_BTN = "//mat-icon[text() = 'assignment_ind']/parent::button"; + private static final String FILTER_BTN = "//tb-device-info-filter/button"; + private static final String DEVICE_PROFILE_FIELD = "(//input[@formcontrolname='deviceProfile'])[2]"; + private static final String DEVICE_STATE_SELECT = "//div[contains(@class,'tb-filter-panel')]//mat-select[@role='combobox']"; + private static final String LIST_OF_DEVICES_STATE = "//div[@class='status']"; + private static final String LIST_OF_DEVICES_PROFILE = "//mat-cell[contains(@class,'deviceProfileName')]"; public WebElement device(String deviceName) { return waitUntilElementToBeClickable(String.format(DEVICE, deviceName)); @@ -82,8 +89,8 @@ public class DevicePageElements extends OtherPageElementsHelper { return waitUntilElementToBeClickable(CLOSE_DEVICE_DETAILS_VIEW); } - public WebElement submitAssignToCustomerBtn() { - return waitUntilElementToBeClickable(SUBMIT_ASSIGN_TO_CUSTOMER_BTN); + public WebElement submitBtn() { + return waitUntilElementToBeClickable(SUBMIT_BTN); } public WebElement addDeviceBtn() { @@ -177,4 +184,24 @@ public class DevicePageElements extends OtherPageElementsHelper { public WebElement assignMarkedDeviceBtn() { return waitUntilVisibilityOfElementLocated(ASSIGN_MARKED_DEVICE_BTN); } + + public WebElement filterBtn() { + return waitUntilElementToBeClickable(FILTER_BTN); + } + + public WebElement deviceProfileField() { + return waitUntilElementToBeClickable(DEVICE_PROFILE_FIELD); + } + + public WebElement deviceStateSelect() { + return waitUntilElementToBeClickable(DEVICE_STATE_SELECT); + } + + public List listOfDevicesState() { + return waitUntilVisibilityOfElementsLocated(LIST_OF_DEVICES_STATE); + } + + public List listOfDevicesProfile() { + return waitUntilVisibilityOfElementsLocated(LIST_OF_DEVICES_PROFILE); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java index 3239bb5484..80f4ff49b3 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java @@ -35,7 +35,7 @@ public class DevicePageHelper extends DevicePageElements { public void assignToCustomer(String customerTitle) { chooseCustomerForAssignField().click(); entityFromDropdown(customerTitle).click(); - submitAssignToCustomerBtn().click(); + submitBtn().click(); } public void openCreateDeviceView() { @@ -101,4 +101,26 @@ public class DevicePageHelper extends DevicePageElements { deleteSelectedBtn().click(); warningPopUpYesBtn().click(); } + + public void filterDeviceByDeviceProfile(String deviceProfileTitle) { + clearProfileFieldBtn().click(); + entityFromDropdown(deviceProfileTitle).click(); + submitBtn().click(); + } + + public void filterDeviceByState(String state) { + deviceStateSelect().click(); + entityFromDropdown(" " + state + " ").click(); + sleep(2); //wait until the action is counted + submitBtn().click(); + } + + public void filterDeviceByDeviceProfileAndState(String deviceProfileTitle, String state) { + clearProfileFieldBtn().click(); + entityFromDropdown(deviceProfileTitle).click(); + deviceStateSelect().click(); + entityFromDropdown(" " + state + " ").click(); + sleep(2); //wait until the action is counted + submitBtn().click(); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java index 5b4f1c0685..d173de961e 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java @@ -45,9 +45,5 @@ abstract public class AbstractDeviceTest extends AbstractDriverBaseTest { public void delete() { deleteDeviceByName(deviceName); deviceName = null; - if (deviceProfileTitle != null) { - deleteDeviceProfileByTitle(deviceProfileTitle); - deviceProfileTitle = null; - } } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java index 7f13614b37..64b965ac58 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.msa.ui.tests.devicessmoke; import io.qameta.allure.Description; import io.qameta.allure.Feature; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.msa.ui.pages.ProfilesPageElements; @@ -33,6 +34,16 @@ import static org.thingsboard.server.msa.ui.utils.Const.SAME_NAME_WARNING_DEVICE @Feature("Create device") public class CreateDeviceTest extends AbstractDeviceTest { + @AfterMethod + public void delete() { + deleteDeviceByName(deviceName); + deviceName = null; + if (deviceProfileTitle != null) { + deleteDeviceProfileByTitle(deviceProfileTitle); + deviceProfileTitle = null; + } + } + @Test(groups = "smoke") @Description("Add device after specifying the name (text/numbers /special characters)") public void createDevice() { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeviceFilterTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeviceFilterTest.java new file mode 100644 index 0000000000..c0fa238dee --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeviceFilterTest.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2023 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.msa.ui.tests.devicessmoke; + +import io.qameta.allure.Description; +import io.qameta.allure.Epic; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.msa.ui.utils.DataProviderCredential; +import org.thingsboard.server.msa.ui.utils.EntityPrototypes; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random; +import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME; + +@Epic("Filter devices (By device profile and state)") +public class DeviceFilterTest extends AbstractDeviceTest { + + private String activeDeviceName; + private String deviceWithProfileName; + private String activeDeviceWithProfileName; + + @BeforeClass + public void createTestEntities() { + DeviceProfile deviceProfile = testRestClient.postDeviceProfile(EntityPrototypes.defaultDeviceProfile(ENTITY_NAME + random())); + Device deviceWithProfile = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME + random(), deviceProfile.getId())); + Device activeDevice = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME + random())); + Device activeDeviceWithProfile = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME + random(), deviceProfile.getId())); + + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(activeDevice.getId()); + DeviceCredentials deviceCredentials1 = testRestClient.getDeviceCredentialsByDeviceId(activeDeviceWithProfile.getId()); + testRestClient.postTelemetry(deviceCredentials.getCredentialsId(), JacksonUtil.toJsonNode(createPayload().toString())); + testRestClient.postTelemetry(deviceCredentials1.getCredentialsId(), JacksonUtil.toJsonNode(createPayload().toString())); + + deviceProfileTitle = deviceProfile.getName(); + deviceWithProfileName = deviceWithProfile.getName(); + activeDeviceName = activeDevice.getName(); + activeDeviceWithProfileName = activeDeviceWithProfile.getName(); + } + + @AfterClass + public void deleteTestEntities() { + deleteDevicesByName(List.of(deviceWithProfileName, activeDeviceName, activeDeviceWithProfileName)); + deleteDeviceProfileByTitle(deviceProfileTitle); + } + + @Test(groups = "smoke") + @Description("Filter by device profile") + public void filterDevicesByProfile() { + sideBarMenuView.goToDevicesPage(); + devicePage.filterBtn().click(); + devicePage.filterDeviceByDeviceProfile(deviceProfileTitle); + + devicePage.listOfDevicesProfile().forEach(d -> assertThat(d.getText()) + .as("There are only devices with the selected profile(%s) on the page", deviceProfileTitle) + .isEqualTo(deviceProfileTitle)); + } + + @Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "filterData") + @Description("Filter by state") + public void filterDevicesByState(String state) { + sideBarMenuView.goToDevicesPage(); + devicePage.filterBtn().click(); + devicePage.filterDeviceByState(state); + + devicePage.listOfDevicesState().forEach(d -> assertThat(d.getText()) + .as("There are only devices with '%s' state on the page", state) + .isEqualTo(state)); + } + + @Test(groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "filterData") + @Description("Filter device by device profile and state") + public void filterDevicesByDeviceProfileAndState(String state) { + sideBarMenuView.goToDevicesPage(); + devicePage.filterBtn().click(); + devicePage.filterDeviceByDeviceProfileAndState(deviceProfileTitle, state); + + devicePage.listOfDevicesProfile().forEach(d -> assertThat(d.getText()) + .as("There are only devices with the selected profile(%s) on the page", deviceProfileTitle) + .isEqualTo(deviceProfileTitle)); + devicePage.listOfDevicesState().forEach(d -> assertThat(d.getText()) + .as("There are only devices with '%s' state on the page", state) + .isEqualTo(state)); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java index 28bdac556f..0b9e173e6d 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/Const.java @@ -45,4 +45,6 @@ public class Const { public static final String PHONE_NUMBER_ERROR_MESSAGE = "Phone number is invalid or not possible"; public static final String NAME_IS_REQUIRED_MESSAGE = "Name is required."; public static final String DEVICE_PROFILE_IS_REQUIRED_MESSAGE = "Device profile is required"; + public static final String DEVICE_ACTIVE_STATE = "Active"; + public static final String DEVICE_INACTIVE_STATE = "Inactive"; } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java index e7ee242cab..218751f709 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java @@ -21,6 +21,8 @@ import org.testng.annotations.DataProvider; import static org.thingsboard.server.msa.ui.base.AbstractBasePage.getRandomNumber; import static org.thingsboard.server.msa.ui.base.AbstractBasePage.getRandomSymbol; import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random; +import static org.thingsboard.server.msa.ui.utils.Const.DEVICE_ACTIVE_STATE; +import static org.thingsboard.server.msa.ui.utils.Const.DEVICE_INACTIVE_STATE; import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME; public class DataProviderCredential { @@ -166,4 +168,12 @@ public class DataProviderCredential { {label, newLabel, label + newLabel}, {label, Keys.CONTROL + "A" + Keys.BACK_SPACE, ""}}; } + + @DataProvider(name = "filterData") + public static Object[][] getFilterData() { + return new Object[][]{ + {DEVICE_ACTIVE_STATE}, + {DEVICE_INACTIVE_STATE} + }; + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java index ec66442bd2..252061fca8 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.rule.RuleChain; @@ -227,6 +228,14 @@ public class EntityPrototypes { return device; } + public static Device defaultDevicePrototype(String name, DeviceProfileId deviceProfileId) { + Device device = new Device(); + device.setName(name + RandomStringUtils.randomAlphanumeric(7)); + device.setType("DEFAULT"); + device.setDeviceProfileId(deviceProfileId); + return device; + } + public static Asset defaultAssetPrototype(String name, CustomerId id) { Asset asset = new Asset(); asset.setName(name + RandomStringUtils.randomAlphanumeric(7)); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 352f94e091..ca495c678f 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -192,6 +192,13 @@ metrics: # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" +management: + endpoints: + web: + exposure: + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). + include: '${METRICS_ENDPOINTS_EXPOSE:info}' + service: type: "${TB_SERVICE_TYPE:tb-vc-executor}" # Unique id for this service (autogenerated if empty) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java index e243f66633..fa88fa0254 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java @@ -35,7 +35,9 @@ import io.netty.handler.codec.mqtt.MqttSubAckMessage; import io.netty.handler.codec.mqtt.MqttUnsubAckMessage; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Promise; +import lombok.extern.slf4j.Slf4j; +@Slf4j final class MqttChannelHandler extends SimpleChannelInboundHandler { private final MqttClientImpl client; @@ -48,31 +50,36 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception { - switch (msg.fixedHeader().messageType()) { - case CONNACK: - handleConack(ctx.channel(), (MqttConnAckMessage) msg); - break; - case SUBACK: - handleSubAck((MqttSubAckMessage) msg); - break; - case PUBLISH: - handlePublish(ctx.channel(), (MqttPublishMessage) msg); - break; - case UNSUBACK: - handleUnsuback((MqttUnsubAckMessage) msg); - break; - case PUBACK: - handlePuback((MqttPubAckMessage) msg); - break; - case PUBREC: - handlePubrec(ctx.channel(), msg); - break; - case PUBREL: - handlePubrel(ctx.channel(), msg); - break; - case PUBCOMP: - handlePubcomp(msg); - break; + if (msg.decoderResult().isSuccess()) { + switch (msg.fixedHeader().messageType()) { + case CONNACK: + handleConack(ctx.channel(), (MqttConnAckMessage) msg); + break; + case SUBACK: + handleSubAck((MqttSubAckMessage) msg); + break; + case PUBLISH: + handlePublish(ctx.channel(), (MqttPublishMessage) msg); + break; + case UNSUBACK: + handleUnsuback((MqttUnsubAckMessage) msg); + break; + case PUBACK: + handlePuback((MqttPubAckMessage) msg); + break; + case PUBREC: + handlePubrec(ctx.channel(), msg); + break; + case PUBREL: + handlePubrel(ctx.channel(), msg); + break; + case PUBCOMP: + handlePubcomp(msg); + break; + } + } else { + log.error("[{}] Message decoding failed: {}", client.getClientConfig().getClientId(), msg.decoderResult().cause().getMessage()); + ctx.close(); } }