Browse Source

Merge pull request #8757 from thingsboard/fix/snmp

Improvements for SNMP
pull/9217/head
Andrew Shvayka 3 years ago
committed by GitHub
parent
commit
1256f78552
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  2. 68
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  3. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  4. 4
      application/src/main/resources/thingsboard.yml
  5. 23
      common/cluster-api/src/main/proto/queue.proto
  6. 9
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  7. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java
  8. 4
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java
  9. 18
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java
  10. 3
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java
  11. 3
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java
  12. 29
      common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java
  13. 32
      common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java
  14. 74
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java
  15. 97
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java
  16. 221
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java
  17. 31
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java
  18. 23
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java
  19. 43
      common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java
  20. 7
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  21. 49
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  22. 2
      docker/docker-compose.yml
  23. 1
      docker/tb-snmp-transport.env
  24. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  25. 8
      ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts
  26. 4
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts
  27. 12
      ui-ngx/src/app/shared/models/device.models.ts
  28. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

49
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -33,7 +33,10 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
@ -45,6 +48,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
@ -76,18 +80,18 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
private final Lock findOrCreateDeviceProfileLock = new ReentrantLock(); private final Lock findOrCreateDeviceProfileLock = new ReentrantLock();
@Override @Override
protected void setEntityFields(Device entity, Map<BulkImportColumnType, String> fields) { protected void setEntityFields(Device device, Map<BulkImportColumnType, String> fields) {
ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(device);
fields.forEach((columnType, value) -> { fields.forEach((columnType, value) -> {
switch (columnType) { switch (columnType) {
case NAME: case NAME:
entity.setName(value); device.setName(value);
break; break;
case TYPE: case TYPE:
entity.setType(value); device.setType(value);
break; break;
case LABEL: case LABEL:
entity.setLabel(value); device.setLabel(value);
break; break;
case DESCRIPTION: case DESCRIPTION:
additionalInfo.set("description", new TextNode(value)); additionalInfo.set("description", new TextNode(value));
@ -96,16 +100,17 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value))); additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value)));
break; break;
} }
entity.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
}); });
setUpDeviceConfiguration(device, fields);
} }
@Override @Override
@SneakyThrows @SneakyThrows
protected Device saveEntity(SecurityUser user, Device entity, Map<BulkImportColumnType, String> fields) { protected Device saveEntity(SecurityUser user, Device device, Map<BulkImportColumnType, String> fields) {
DeviceCredentials deviceCredentials; DeviceCredentials deviceCredentials;
try { try {
deviceCredentials = createDeviceCredentials(entity.getTenantId(), entity.getId(), fields); deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), fields);
deviceCredentialsService.formatCredentials(deviceCredentials); deviceCredentialsService.formatCredentials(deviceCredentials);
} catch (Exception e) { } catch (Exception e) {
throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage()); throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage());
@ -113,15 +118,15 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
DeviceProfile deviceProfile; DeviceProfile deviceProfile;
if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
deviceProfile = setUpLwM2mDeviceProfile(entity.getTenantId(), entity); deviceProfile = setUpLwM2mDeviceProfile(device.getTenantId(), device);
} else if (StringUtils.isNotEmpty(entity.getType())) { } else if (StringUtils.isNotEmpty(device.getType())) {
deviceProfile = deviceProfileService.findOrCreateDeviceProfile(entity.getTenantId(), entity.getType()); deviceProfile = deviceProfileService.findOrCreateDeviceProfile(device.getTenantId(), device.getType());
} else { } else {
deviceProfile = deviceProfileService.findDefaultDeviceProfile(entity.getTenantId()); deviceProfile = deviceProfileService.findDefaultDeviceProfile(device.getTenantId());
} }
entity.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
return tbDeviceService.saveDeviceWithCredentials(entity, deviceCredentials, user); return tbDeviceService.saveDeviceWithCredentials(device, deviceCredentials, user);
} }
@Override @Override
@ -136,6 +141,22 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
entity.setCustomerId(user.getCustomerId()); entity.setCustomerId(user.getCustomerId());
} }
private void setUpDeviceConfiguration(Device device, Map<BulkImportColumnType, String> fields) {
if (fields.containsKey(BulkImportColumnType.SNMP_HOST)) {
SnmpDeviceTransportConfiguration transportConfiguration = new SnmpDeviceTransportConfiguration();
transportConfiguration.setHost(fields.get(BulkImportColumnType.SNMP_HOST));
transportConfiguration.setPort(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_PORT))
.map(Integer::parseInt).orElse(161));
transportConfiguration.setProtocolVersion(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_VERSION))
.map(version -> SnmpProtocolVersion.valueOf(version.toUpperCase())).orElse(SnmpProtocolVersion.V2C));
transportConfiguration.setCommunity(fields.getOrDefault(BulkImportColumnType.SNMP_COMMUNITY_STRING, "public"));
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(transportConfiguration);
device.setDeviceData(deviceData);
}
}
@SneakyThrows @SneakyThrows
private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) { private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) {
DeviceCredentials credentials = new DeviceCredentials(); DeviceCredentials credentials = new DeviceCredentials();

68
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -24,10 +24,14 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.event.ErrorEvent;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -45,7 +49,9 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ErrorEventProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.LifecycleEventProto;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
@ -274,6 +280,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg(); TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg()); log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback); forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasErrorEventMsg()) {
forwardToEventService(toCoreMsg.getErrorEventMsg(), callback);
} else if (toCoreMsg.hasLifecycleEventMsg()) {
forwardToEventService(toCoreMsg.getLifecycleEventMsg(), callback);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e); log.warn("[{}] Failed to process message: {}", id, msg, e);
@ -519,50 +529,52 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback); subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback);
} else if (msg.hasTsUpdate()) { } else if (msg.hasTsUpdate()) {
TbTimeSeriesUpdateProto proto = msg.getTsUpdate(); TbTimeSeriesUpdateProto proto = msg.getTsUpdate();
long tenantIdMSB = proto.getTenantIdMSB();
long tenantIdLSB = proto.getTenantIdLSB();
subscriptionManagerService.onTimeSeriesUpdate( subscriptionManagerService.onTimeSeriesUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(tenantIdMSB, tenantIdLSB),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback); TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback);
} else if (msg.hasAttrUpdate()) { } else if (msg.hasAttrUpdate()) {
TbAttributeUpdateProto proto = msg.getAttrUpdate(); TbAttributeUpdateProto proto = msg.getAttrUpdate();
subscriptionManagerService.onAttributesUpdate( subscriptionManagerService.onAttributesUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback);
} else if (msg.hasAttrDelete()) { } else if (msg.hasAttrDelete()) {
TbAttributeDeleteProto proto = msg.getAttrDelete(); TbAttributeDeleteProto proto = msg.getAttrDelete();
subscriptionManagerService.onAttributesDelete( subscriptionManagerService.onAttributesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback); proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback);
} else if (msg.hasTsDelete()) { } else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete(); TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete( subscriptionManagerService.onTimeSeriesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getKeysList(), callback); proto.getKeysList(), callback);
} else if (msg.hasAlarmUpdate()) { } else if (msg.hasAlarmUpdate()) {
TbAlarmUpdateProto proto = msg.getAlarmUpdate(); TbAlarmUpdateProto proto = msg.getAlarmUpdate();
subscriptionManagerService.onAlarmUpdate( subscriptionManagerService.onAlarmUpdate(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class),
callback); callback);
} else if (msg.hasAlarmDelete()) { } else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete(); TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted( subscriptionManagerService.onAlarmDeleted(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), toTenantId(proto.getTenantIdMSB(), proto.getTenantIdLSB()),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback); JacksonUtil.fromString(proto.getAlarm(), AlarmInfo.class), callback);
} else if (msg.hasNotificationUpdate()) { } else if (msg.hasNotificationUpdate()) {
TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate(); TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB())); TenantId tenantId = toTenantId(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB());
UserId recipientId = new UserId(new UUID(updateProto.getRecipientIdMSB(), updateProto.getRecipientIdLSB())); UserId recipientId = new UserId(new UUID(updateProto.getRecipientIdMSB(), updateProto.getRecipientIdLSB()));
NotificationUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationUpdate.class); NotificationUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationUpdate.class);
subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, callback); subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, callback);
} else if (msg.hasNotificationRequestUpdate()) { } else if (msg.hasNotificationRequestUpdate()) {
TransportProtos.NotificationRequestUpdateProto updateProto = msg.getNotificationRequestUpdate(); TransportProtos.NotificationRequestUpdateProto updateProto = msg.getNotificationRequestUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB())); TenantId tenantId = toTenantId(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB());
NotificationRequestUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationRequestUpdate.class); NotificationRequestUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationRequestUpdate.class);
subscriptionManagerService.onNotificationRequestUpdate(tenantId, update, callback); subscriptionManagerService.onNotificationRequestUpdate(tenantId, update, callback);
} else { } else {
@ -584,7 +596,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
if (statsEnabled) { if (statsEnabled) {
stats.log(deviceActivityMsg); stats.log(deviceActivityMsg);
} }
TenantId tenantId = TenantId.fromUUID(new UUID(deviceActivityMsg.getTenantIdMSB(), deviceActivityMsg.getTenantIdLSB())); TenantId tenantId = toTenantId(deviceActivityMsg.getTenantIdMSB(), deviceActivityMsg.getTenantIdLSB());
DeviceId deviceId = new DeviceId(new UUID(deviceActivityMsg.getDeviceIdMSB(), deviceActivityMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceActivityMsg.getDeviceIdMSB(), deviceActivityMsg.getDeviceIdLSB()));
try { try {
stateService.onDeviceActivity(tenantId, deviceId, deviceActivityMsg.getLastActivityTime()); stateService.onDeviceActivity(tenantId, deviceId, deviceActivityMsg.getLastActivityTime());
@ -595,7 +607,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} }
private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) { private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) {
TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); TenantId tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
NotificationRequestId notificationRequestId = new NotificationRequestId(new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB())); NotificationRequestId notificationRequestId = new NotificationRequestId(new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()));
try { try {
notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs()); notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs());
@ -627,11 +639,47 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
callback.onSuccess(); callback.onSuccess();
} }
private void forwardToEventService(ErrorEventProto eventProto, TbCallback callback) {
Event event = ErrorEvent.builder()
.tenantId(toTenantId(eventProto.getTenantIdMSB(), eventProto.getTenantIdLSB()))
.entityId(new UUID(eventProto.getEntityIdMSB(), eventProto.getEntityIdLSB()))
.serviceId(eventProto.getServiceId())
.ts(System.currentTimeMillis())
.method(eventProto.getMethod())
.error(eventProto.getError())
.build();
forwardToEventService(event, callback);
}
private void forwardToEventService(LifecycleEventProto eventProto, TbCallback callback) {
Event event = LifecycleEvent.builder()
.tenantId(toTenantId(eventProto.getTenantIdMSB(), eventProto.getTenantIdLSB()))
.entityId(new UUID(eventProto.getEntityIdMSB(), eventProto.getEntityIdLSB()))
.serviceId(eventProto.getServiceId())
.ts(System.currentTimeMillis())
.lcEventType(eventProto.getLcEventType())
.success(eventProto.getSuccess())
.error(StringUtils.isNotEmpty(eventProto.getError()) ? eventProto.getError() : null)
.build();
forwardToEventService(event, callback);
}
private void forwardToEventService(Event event, TbCallback callback) {
DonAsynchron.withCallback(actorContext.getEventService().saveAsync(event),
result -> callback.onSuccess(),
callback::onFailure,
actorContext.getDbCallbackExecutor());
}
private void throwNotHandled(Object msg, TbCallback callback) { private void throwNotHandled(Object msg, TbCallback callback) {
log.warn("Message not handled: {}", msg); log.warn("Message not handled: {}", msg);
callback.onFailure(new RuntimeException("Message not handled!")); callback.onFailure(new RuntimeException("Message not handled!"));
} }
private TenantId toTenantId(long tenantIdMSB, long tenantIdLSB) {
return TenantId.fromUUID(new UUID(tenantIdMSB, tenantIdLSB));
}
@Override @Override
protected void stopMainConsumers() { protected void stopMainConsumers() {
if (mainConsumer != null) { if (mainConsumer != null) {

5
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive;
import lombok.Data; import lombok.Data;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -57,7 +58,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.CsvUtils; import org.thingsboard.server.utils.CsvUtils;
import org.thingsboard.server.utils.TypeCastUtil; import org.thingsboard.server.common.data.util.TypeCastUtil;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
if (!entry.getKey().getType().isKv()) { if (!entry.getKey().getType().isKv()) {
entityData.getFields().put(entry.getKey().getType(), entry.getValue()); entityData.getFields().put(entry.getKey().getType(), entry.getValue());
} else { } else {
Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue()); Pair<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey())); entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
} }
}); });

4
application/src/main/resources/thingsboard.yml

@ -991,11 +991,15 @@ transport:
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" # value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
bind_port: "${SNMP_BIND_PORT:1620}"
response_processing: response_processing:
# parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
# to configure SNMP to work over UDP or TCP # to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
stats: stats:
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"

23
common/cluster-api/src/main/proto/queue.proto

@ -975,6 +975,8 @@ message ToCoreMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 5; EdgeNotificationMsgProto edgeNotificationMsg = 5;
DeviceActivityProto deviceActivityMsg = 6; DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
LifecycleEventProto lifecycleEventMsg = 8;
ErrorEventProto errorEventMsg = 9;
} }
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */ /* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -1066,3 +1068,24 @@ message NotificationSchedulerServiceMsg {
message NotificationRuleProcessorMsg { message NotificationRuleProcessorMsg {
bytes trigger = 1; bytes trigger = 1;
} }
message ErrorEventProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 entityIdMSB = 3;
int64 entityIdLSB = 4;
string serviceId = 5;
string method = 6;
string error = 7;
}
message LifecycleEventProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 entityIdMSB = 3;
int64 entityIdLSB = 4;
string serviceId = 5;
string lcEventType = 6;
bool success = 7;
string error = 8;
}

9
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -163,6 +163,15 @@ public class StringUtils {
return false; return false;
} }
public static boolean equalsAnyIgnoreCase(String string, String... otherStrings) {
for (String otherString : otherStrings) {
if (equalsIgnoreCase(string, otherString)) {
return true;
}
}
return false;
}
public static String substringAfterLast(String str, String sep) { public static String substringAfterLast(String str, String sep) {
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep); return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java

@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import java.util.List; import java.util.List;
@ -45,8 +44,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra
private boolean isValid() { private boolean isValid() {
return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0
&& communicationConfigs != null && communicationConfigs != null
&& communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) && communicationConfigs.stream().allMatch(config -> config != null && config.isValid());
&& communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid)
.distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum();
} }
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java

@ -43,6 +43,10 @@ public enum BulkImportColumnType {
LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()), LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()),
LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"), LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"),
LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"), LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"),
SNMP_HOST,
SNMP_PORT,
SNMP_VERSION,
SNMP_COMMUNITY_STRING,
IS_GATEWAY, IS_GATEWAY,
DESCRIPTION, DESCRIPTION,
ROUTING_KEY, ROUTING_KEY,

18
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java

@ -15,11 +15,21 @@
*/ */
package org.thingsboard.server.common.data.transport.snmp; package org.thingsboard.server.common.data.transport.snmp;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum SnmpCommunicationSpec { public enum SnmpCommunicationSpec {
TELEMETRY_QUERYING,
CLIENT_ATTRIBUTES_QUERYING, TELEMETRY_QUERYING("telemetryQuerying"),
SHARED_ATTRIBUTES_SETTING, CLIENT_ATTRIBUTES_QUERYING("clientAttributesQuerying"),
SHARED_ATTRIBUTES_SETTING("sharedAttributesSetting"),
TO_DEVICE_RPC_REQUEST("toDeviceRpcRequest"),
TO_SERVER_RPC_REQUEST("toServerRpcRequest");
private final String label;
TO_DEVICE_RPC_REQUEST,
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java

@ -17,7 +17,8 @@ package org.thingsboard.server.common.data.transport.snmp;
public enum SnmpMethod { public enum SnmpMethod {
GET(-96), GET(-96),
SET(-93); SET(-93),
TRAP(-89);
// codes taken from org.snmp4j.PDU class // codes taken from org.snmp4j.PDU class
private final int code; private final int code;

3
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java

@ -37,7 +37,8 @@ import java.util.List;
@Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"),
@Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"),
@Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"),
@Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST") @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST"),
@Type(value = ToServerRpcRequestSnmpCommunicationConfig.class, name = "TO_SERVER_RPC_REQUEST")
}) })
public interface SnmpCommunicationConfig extends Serializable { public interface SnmpCommunicationConfig extends Serializable {

29
common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java

@ -0,0 +1,29 @@
/**
* 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.common.data.transport.snmp.config;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
public class ToServerRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig {
private static final long serialVersionUID = 4851028734093214L;
@Override
public SnmpCommunicationSpec getSpec() {
return SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST;
}
}

32
application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java → common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java

@ -13,35 +13,53 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.utils; package org.thingsboard.server.common.data.util;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Map;
public class TypeCastUtil { public class TypeCastUtil {
private TypeCastUtil() {} private TypeCastUtil() {}
public static Map.Entry<DataType, Object> castValue(String value) { public static Pair<DataType, Object> castValue(String value) {
if (isNumber(value)) { if (isNumber(value)) {
String formattedValue = value.replace(',', '.'); String formattedValue = value.replace(',', '.');
try { try {
BigDecimal bd = new BigDecimal(formattedValue); BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) { if (bd.scale() <= 16) {
return Map.entry(DataType.DOUBLE, bd.doubleValue()); return Pair.of(DataType.DOUBLE, bd.doubleValue());
} }
} else { } else {
return Map.entry(DataType.LONG, bd.longValueExact()); return Pair.of(DataType.LONG, bd.longValueExact());
} }
} catch (RuntimeException ignored) {} } catch (RuntimeException ignored) {}
} else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value)); return Pair.of(DataType.BOOLEAN, Boolean.parseBoolean(value));
}
return Pair.of(DataType.STRING, value);
}
public static Pair<DataType, Number> castToNumber(String value) {
if (isNumber(value)) {
String formattedValue = value.replace(',', '.');
BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) {
return Pair.of(DataType.DOUBLE, bd.doubleValue());
} else {
return Pair.of(DataType.DOUBLE, bd);
}
} else {
return Pair.of(DataType.LONG, bd.longValueExact());
}
} else {
throw new IllegalArgumentException("'" + value + "' can't be parsed as number");
} }
return Map.entry(DataType.STRING, value);
} }
private static boolean isNumber(String value) { private static boolean isNumber(String value) {

74
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent;
@ -113,19 +114,25 @@ public class SnmpTransportContext extends TransportContext {
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration();
SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
DeviceSessionContext deviceSessionContext; DeviceSessionContext sessionContext;
try { try {
deviceSessionContext = new DeviceSessionContext( sessionContext = DeviceSessionContext.builder()
device, deviceProfile, credentials.getCredentialsId(), .tenantId(deviceProfile.getTenantId())
profileTransportConfiguration, deviceTransportConfiguration, this .device(device)
); .deviceProfile(deviceProfile)
registerSessionMsgListener(deviceSessionContext); .token(credentials.getCredentialsId())
.profileTransportConfiguration(profileTransportConfiguration)
.deviceTransportConfiguration(deviceTransportConfiguration)
.snmpTransportContext(this)
.build();
registerSessionMsgListener(sessionContext);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString()); log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString());
transportService.errorEvent(device.getTenantId(), device.getId(), "sessionEstablishing", e);
return; return;
} }
sessions.put(device.getId(), deviceSessionContext); sessions.put(device.getId(), sessionContext);
snmpTransportService.createQueryingTasks(deviceSessionContext); snmpTransportService.createQueryingTasks(sessionContext);
log.info("Established SNMP device session for device {}", device.getId()); log.info("Established SNMP device session for device {}", device.getId());
} }
@ -148,14 +155,17 @@ public class SnmpTransportContext extends TransportContext {
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.cancelQueryingTasks(sessionContext);
snmpTransportService.createQueryingTasks(sessionContext); snmpTransportService.createQueryingTasks(sessionContext);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null);
} else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) {
sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration);
sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null);
} else { } else {
log.trace("Configuration of the device {} was not updated", device); log.trace("Configuration of the device {} was not updated", device);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage());
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, false, e);
destroyDeviceSession(sessionContext); destroyDeviceSession(sessionContext);
} }
} }
@ -168,43 +178,58 @@ public class SnmpTransportContext extends TransportContext {
transportService.deregisterSession(sessionContext.getSessionInfo()); transportService.deregisterSession(sessionContext.getSessionInfo());
snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.cancelQueryingTasks(sessionContext);
sessions.remove(sessionContext.getDeviceId()); sessions.remove(sessionContext.getDeviceId());
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STOPPED, true, null);
log.trace("Unregistered and removed session"); log.trace("Unregistered and removed session");
} }
private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) { private void registerSessionMsgListener(DeviceSessionContext sessionContext) {
transportService.process(DeviceTransportType.SNMP, transportService.process(DeviceTransportType.SNMP,
TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(), TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(),
new TransportServiceCallback<>() { new TransportServiceCallback<>() {
@Override @Override
public void onSuccess(ValidateDeviceCredentialsResponse msg) { public void onSuccess(ValidateDeviceCredentialsResponse msg) {
if (msg.hasDeviceInfo()) { if (msg.hasDeviceInfo()) {
SessionInfoProto sessionInfo = SessionInfoCreator.create( registerTransportSession(sessionContext, msg);
msg, SnmpTransportContext.this, UUID.randomUUID() sessionContext.setSessionTimeoutHandler(() -> {
); registerTransportSession(sessionContext, msg);
});
transportService.registerAsyncSession(sessionInfo, deviceSessionContext); transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, true, null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY);
deviceSessionContext.setSessionInfo(sessionInfo);
deviceSessionContext.setDeviceInfo(msg.getDeviceInfo());
deviceSessionContext.setConnected(true);
} else { } else {
log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId()); log.warn("[{}] Failed to process device auth", sessionContext.getDeviceId());
} }
} }
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.warn("[{}] Failed to process device auth: {}", deviceSessionContext.getDeviceId(), e); log.warn("[{}] Failed to process device auth: {}", sessionContext.getDeviceId(), e);
transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, false, e);
} }
}); });
} }
private void registerTransportSession(DeviceSessionContext deviceSessionContext, ValidateDeviceCredentialsResponse msg) {
SessionInfoProto sessionInfo = SessionInfoCreator.create(
msg, SnmpTransportContext.this, UUID.randomUUID()
);
log.debug("Registering transport session: {}", sessionInfo);
transportService.registerAsyncSession(sessionInfo, deviceSessionContext);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC)
.build(), TransportServiceCallback.EMPTY);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC)
.build(), TransportServiceCallback.EMPTY);
deviceSessionContext.setSessionInfo(sessionInfo);
deviceSessionContext.setDeviceInfo(msg.getDeviceInfo());
deviceSessionContext.setConnected(true);
}
@EventListener(DeviceUpdatedEvent.class) @EventListener(DeviceUpdatedEvent.class)
public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) { public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) {
Device device = deviceUpdatedEvent.getDevice(); Device device = deviceUpdatedEvent.getDevice();
log.trace("Got creating or updating device event for device {}", device); log.debug("Got creating or updating device event for device {}", device);
DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration()) DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration())
.map(DeviceTransportConfiguration::getType) .map(DeviceTransportConfiguration::getType)
.orElse(null); .orElse(null);
@ -238,6 +263,7 @@ public class SnmpTransportContext extends TransportContext {
} }
public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) { public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) {
log.debug("Handling device profile {} update event for device {}", deviceProfile.getId(), sessionContext.getDeviceId());
updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile); updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile);
} }

97
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java

@ -15,7 +15,9 @@
*/ */
package org.thingsboard.server.transport.snmp.service; package org.thingsboard.server.transport.snmp.service;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU; import org.snmp4j.ScopedPDU;
@ -25,43 +27,61 @@ import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString; import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable; import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding; import org.snmp4j.smi.VariableBinding;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.data.util.TypeCastUtil;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
@TbSnmpTransportComponent @TbSnmpTransportComponent
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class PduService { public class PduService {
public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(communicationConfig.getMethod().getCode()); @Value("${transport.snmp.max_request_oids:100}")
pdu.addAll(communicationConfig.getAllMappings().stream() private int maxRequestOids;
.filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey()))
.map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) @Value("${transport.snmp.response.ignore_type_cast_errors:false}")
.map(value -> { private boolean ignoreTypeCastErrors;
Variable variable = toSnmpVariable(value, mapping.getDataType());
return new VariableBinding(new OID(mapping.getOid()), variable); public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
}) List<PDU> pdus = new ArrayList<>();
.orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
.collect(Collectors.toList()));
for (List<SnmpMapping> mappings : Lists.partition(allMappings, maxRequestOids)) {
PDU pdu = setUpPdu(sessionContext);
pdu.setType(communicationConfig.getMethod().getCode());
pdu.addAll(mappings.stream()
.filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey()))
.map(mapping -> Optional.ofNullable(values.get(mapping.getKey()))
.map(value -> {
Variable variable = toSnmpVariable(value, mapping.getDataType());
return new VariableBinding(new OID(mapping.getOid()), variable);
})
.orElseGet(() -> new VariableBinding(new OID(mapping.getOid()))))
.collect(Collectors.toList()));
if (pdu.size() > 0) {
pdus.add(pdu);
}
}
return pdu; return pdus;
} }
public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) { public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) {
@ -116,8 +136,8 @@ public class PduService {
} }
public JsonObject processPdu(PDU pdu, List<SnmpMapping> responseMappings) { public JsonObject processPdus(List<PDU> pdus, List<SnmpMapping> responseMappings) {
Map<OID, String> values = processPdu(pdu); Map<OID, String> values = processPdus(pdus);
Map<OID, SnmpMapping> mappings = new HashMap<>(); Map<OID, SnmpMapping> mappings = new HashMap<>();
if (responseMappings != null) { if (responseMappings != null) {
@ -143,29 +163,40 @@ public class PduService {
return data; return data;
} }
public Map<OID, String> processPdu(PDU pdu) { public Map<OID, String> processPdus(List<PDU> pdus) {
return IntStream.range(0, pdu.size()) return pdus.stream()
.mapToObj(pdu::get) .flatMap(pdu -> pdu.getVariableBindings().stream())
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null))
.collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString));
} }
public void processValue(String key, DataType dataType, String value, JsonObject result) { public void processValue(String key, DataType dataType, String value, JsonObject result) {
switch (dataType) { try {
case LONG: switch (dataType) {
result.addProperty(key, Long.parseLong(value)); case STRING:
break; case JSON:
case BOOLEAN: result.addProperty(key, value);
result.addProperty(key, Boolean.parseBoolean(value)); break;
break; case LONG:
case DOUBLE: case DOUBLE:
result.addProperty(key, Double.parseDouble(value)); result.addProperty(key, TypeCastUtil.castToNumber(value).getValue());
break; break;
case STRING: case BOOLEAN:
case JSON: if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) {
default: result.addProperty(key, Boolean.parseBoolean(value));
result.addProperty(key, value); } else {
throw new IllegalArgumentException("Can't parse '" + value + "' as boolean");
}
break;
}
} catch (IllegalArgumentException e) {
if (ignoreTypeCastErrors) {
log.debug("Ignoring value '{}' for key '{}' because of data type mismatch ({} required)", value, key, dataType);
} else {
throw e;
}
} }
} }
} }

221
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java

@ -17,10 +17,13 @@ package org.thingsboard.server.transport.snmp.service;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.Snmp; import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping; import org.snmp4j.TransportMapping;
@ -29,10 +32,15 @@ import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM; import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.OctetString; import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -45,15 +53,16 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.SnmpTransportContext;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
@ -71,9 +80,11 @@ import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class SnmpTransportService implements TbTransportService { public class SnmpTransportService implements TbTransportService, CommandResponder {
private final TransportService transportService; private final TransportService transportService;
private final PduService pduService; private final PduService pduService;
@Autowired @Lazy
private SnmpTransportContext transportContext;
@Getter @Getter
private Snmp snmp; private Snmp snmp;
@ -83,6 +94,8 @@ public class SnmpTransportService implements TbTransportService {
private final Map<SnmpCommunicationSpec, ResponseDataMapper> responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map<SnmpCommunicationSpec, ResponseDataMapper> responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class);
private final Map<SnmpCommunicationSpec, ResponseProcessor> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); private final Map<SnmpCommunicationSpec, ResponseProcessor> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class);
@Value("${transport.snmp.bind_port:1620}")
private Integer snmpBindPort;
@Value("${transport.snmp.response_processing.parallelism_level}") @Value("${transport.snmp.response_processing.parallelism_level}")
private Integer responseProcessingParallelismLevel; private Integer responseProcessingParallelismLevel;
@Value("${transport.snmp.underlying_protocol}") @Value("${transport.snmp.underlying_protocol}")
@ -114,15 +127,16 @@ public class SnmpTransportService implements TbTransportService {
TransportMapping<?> transportMapping; TransportMapping<?> transportMapping;
switch (snmpUnderlyingProtocol) { switch (snmpUnderlyingProtocol) {
case "udp": case "udp":
transportMapping = new DefaultUdpTransportMapping(); transportMapping = new DefaultUdpTransportMapping(new UdpAddress(snmpBindPort));
break; break;
case "tcp": case "tcp":
transportMapping = new DefaultTcpTransportMapping(); transportMapping = new DefaultTcpTransportMapping(new TcpAddress(snmpBindPort));
break; break;
default: default:
throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported");
} }
snmp = new Snmp(transportMapping); snmp = new Snmp(transportMapping);
snmp.addNotificationListener(transportMapping, transportMapping.getListenAddress(), this);
snmp.listen(); snmp.listen();
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
@ -143,6 +157,7 @@ public class SnmpTransportService implements TbTransportService {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), config.getSpec().getLabel(), e);
} }
}, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS);
}) })
@ -161,18 +176,24 @@ public class SnmpTransportService implements TbTransportService {
} }
private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) { private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
PDU request = pduService.createPdu(sessionContext, communicationConfig, values); List<PDU> request = pduService.createPdus(sessionContext, communicationConfig, values);
RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings()); RequestContext requestContext = RequestContext.builder()
sendRequest(sessionContext, request, requestInfo); .communicationSpec(communicationConfig.getSpec())
.method(communicationConfig.getMethod())
.responseMappings(communicationConfig.getAllMappings())
.requestSize(request.size())
.build();
sendRequest(sessionContext, request, requestContext);
} }
private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) { private void sendRequest(DeviceSessionContext sessionContext, List<PDU> request, RequestContext requestContext) {
if (request.size() > 0) { for (PDU pdu : request) {
log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); log.debug("Executing SNMP request for device {} with {} variable bindings", sessionContext.getDeviceId(), pdu.size());
try { try {
snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); snmp.send(pdu, sessionContext.getTarget(), requestContext, sessionContext);
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e);
} }
} }
} }
@ -215,51 +236,135 @@ public class SnmpTransportService implements TbTransportService {
DataType dataType = snmpMapping.getDataType(); DataType dataType = snmpMapping.getDataType();
PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType);
RequestInfo requestInfo = new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), communicationConfig.getSpec(), communicationConfig.getAllMappings()); RequestContext requestContext = RequestContext.builder()
sendRequest(sessionContext, request, requestInfo); .requestId(toDeviceRpcRequestMsg.getRequestId())
.communicationSpec(communicationConfig.getSpec())
.method(snmpMethod)
.responseMappings(communicationConfig.getAllMappings())
.requestSize(1)
.build();
sendRequest(sessionContext, List.of(request), requestContext);
} }
public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) {
((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext);
RequestContext requestContext = (RequestContext) event.getUserObject();
if (event.getError() != null) { if (event.getError() != null) {
log.warn("SNMP response error: {}", event.getError().toString()); log.warn("SNMP response error: {}", event.getError().toString());
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException(event.getError()));
return; return;
} }
PDU response = event.getResponse(); PDU responsePdu = event.getResponse();
if (response == null) { if (log.isTraceEnabled()) {
log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); log.trace("Received PDU for device {}: {}", sessionContext.getDeviceId(), responsePdu);
}
List<PDU> response;
if (requestContext.getRequestSize() == 1) {
if (responsePdu == null) {
log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID());
if (requestContext.getMethod() == SnmpMethod.GET) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException("No response from device"));
}
return;
}
response = List.of(responsePdu);
} else {
List<PDU> responseParts = requestContext.getResponseParts();
responseParts.add(responsePdu);
if (responseParts.size() == requestContext.getRequestSize()) {
response = new ArrayList<>();
for (PDU responsePart : responseParts) {
if (responsePart != null) {
response.add(responsePart);
}
}
log.debug("All response parts are collected for request to device {}", sessionContext.getDeviceId());
} else {
log.trace("Awaiting other response parts for request to device {}", sessionContext.getDeviceId());
return;
}
}
responseProcessingExecutor.execute(() -> {
try {
processResponse(sessionContext, response, requestContext);
} catch (Exception e) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e);
}
});
}
/*
* SNMP notifications handler
*
* TODO: add check for host uniqueness when saving device (for backward compatibility - only for the ones using from-device RPC requests)
*
* NOTE: SNMP TRAPs support won't work properly when there is more than one SNMP transport,
* due to load-balancing of requests from devices: session might not be on this instance
* */
@Override
public void processPdu(CommandResponderEvent event) {
Address sourceAddress = event.getPeerAddress();
DeviceSessionContext sessionContext = transportContext.getSessions().stream()
.filter(session -> session.getTarget().getAddress().equals(sourceAddress))
.findFirst().orElse(null);
if (sessionContext == null) {
log.warn("SNMP TRAP processing failed: couldn't find device session for address {}", sourceAddress);
return; return;
} }
RequestInfo requestInfo = (RequestInfo) event.getUserObject(); try {
processIncomingTrap(sessionContext, event);
} catch (Throwable e) {
transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(),
SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST.getLabel(), e);
}
}
private void processIncomingTrap(DeviceSessionContext sessionContext, CommandResponderEvent event) {
PDU pdu = event.getPDU();
if (pdu == null) {
log.warn("Got empty trap from device {}", sessionContext.getDeviceId());
throw new IllegalArgumentException("Received TRAP with no data");
}
log.debug("Processing SNMP trap from device {} (PDU: {}}", sessionContext.getDeviceId(), pdu);
SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream()
.filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST).findFirst()
.orElseThrow(() -> new IllegalArgumentException("No config found for to-server RPC requests"));
RequestContext requestContext = RequestContext.builder()
.communicationSpec(communicationConfig.getSpec())
.responseMappings(communicationConfig.getAllMappings())
.method(SnmpMethod.TRAP)
.build();
responseProcessingExecutor.execute(() -> { responseProcessingExecutor.execute(() -> {
processResponse(sessionContext, response, requestInfo); processResponse(sessionContext, List.of(pdu), requestContext);
}); });
} }
private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { private void processResponse(DeviceSessionContext sessionContext, List<PDU> response, RequestContext requestContext) {
ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec()); ResponseProcessor responseProcessor = responseProcessors.get(requestContext.getCommunicationSpec());
if (responseProcessor == null) return; if (responseProcessor == null) return;
JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo); JsonObject responseData = responseDataMappers.get(requestContext.getCommunicationSpec()).map(response, requestContext);
if (responseData.size() == 0) {
if (responseData.entrySet().isEmpty()) { log.warn("No values in the SNMP response for device {}", sessionContext.getDeviceId());
log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); throw new IllegalArgumentException("No values in the response");
return;
} }
responseProcessor.process(responseData, requestInfo, sessionContext); responseProcessor.process(responseData, requestContext, sessionContext);
reportActivity(sessionContext.getSessionInfo()); reportActivity(sessionContext.getSessionInfo());
} }
private void configureResponseDataMappers() { private void configureResponseDataMappers() {
responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdus, requestContext) -> {
JsonObject responseData = new JsonObject(); JsonObject responseData = new JsonObject();
pduService.processPdu(pdu).forEach((oid, value) -> { pduService.processPdus(pdus).forEach((oid, value) -> {
requestInfo.getResponseMappings().stream() requestContext.getResponseMappings().stream()
.filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString())) .filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString()))
.findFirst() .findFirst()
.ifPresent(snmpMapping -> { .ifPresent(snmpMapping -> {
@ -269,8 +374,8 @@ public class SnmpTransportService implements TbTransportService {
return responseData; return responseData;
}); });
ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> { ResponseDataMapper defaultResponseDataMapper = (pdus, requestContext) -> {
return pduService.processPdu(pdu, requestInfo.getResponseMappings()); return pduService.processPdus(pdus, requestContext.getResponseMappings());
}; };
Arrays.stream(SnmpCommunicationSpec.values()) Arrays.stream(SnmpCommunicationSpec.values())
.forEach(communicationSpec -> { .forEach(communicationSpec -> {
@ -279,34 +384,39 @@ public class SnmpTransportService implements TbTransportService {
} }
private void configureResponseProcessors() { private void configureResponseProcessors() {
responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestContext, sessionContext) -> {
TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData); TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null);
log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
}); });
responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestContext, sessionContext) -> {
TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData); TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData);
transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null);
log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData);
}); });
responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> { responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestContext, sessionContext) -> {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(requestInfo.getRequestId()) .setRequestId(requestContext.getRequestId())
.setPayload(JsonConverter.toJson(responseData)) .setPayload(JsonConverter.toJson(responseData))
.build(); .build();
transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null);
log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId()); log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId());
}); });
responseProcessors.put(SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST, (responseData, requestContext, sessionContext) -> {
TransportProtos.ToServerRpcRequestMsg toServerRpcRequestMsg = TransportProtos.ToServerRpcRequestMsg.newBuilder()
.setRequestId(0)
.setMethodName(requestContext.getMethod().name())
.setParams(JsonConverter.toJson(responseData))
.build();
transportService.process(sessionContext.getSessionInfo(), toServerRpcRequestMsg, null);
});
} }
private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() transportService.reportActivity(sessionInfo);
.setAttributeSubscription(true)
.setRpcSubscription(true)
.setLastActivityTime(System.currentTimeMillis())
.build(), TransportServiceCallback.EMPTY);
} }
@ -335,29 +445,34 @@ public class SnmpTransportService implements TbTransportService {
} }
@Data @Data
private static class RequestInfo { private static class RequestContext {
private Integer requestId; private final Integer requestId;
private SnmpCommunicationSpec communicationSpec; private final SnmpCommunicationSpec communicationSpec;
private List<SnmpMapping> responseMappings; private final SnmpMethod method;
private final List<SnmpMapping> responseMappings;
public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) { private final int requestSize;
this.requestId = requestId; private List<PDU> responseParts;
this.communicationSpec = communicationSpec;
this.responseMappings = responseMappings;
}
public RequestInfo(SnmpCommunicationSpec communicationSpec, List<SnmpMapping> responseMappings) { @Builder
public RequestContext(Integer requestId, SnmpCommunicationSpec communicationSpec, SnmpMethod method, List<SnmpMapping> responseMappings, int requestSize) {
this.requestId = requestId;
this.communicationSpec = communicationSpec; this.communicationSpec = communicationSpec;
this.method = method;
this.responseMappings = responseMappings; this.responseMappings = responseMappings;
this.requestSize = requestSize;
if (requestSize > 1) {
this.responseParts = Collections.synchronizedList(new ArrayList<>());
}
} }
} }
private interface ResponseDataMapper { private interface ResponseDataMapper {
JsonObject map(PDU pdu, RequestInfo requestInfo); JsonObject map(List<PDU> pdus, RequestContext requestContext);
} }
private interface ResponseProcessor { private interface ResponseProcessor {
void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext); void process(JsonObject responseData, RequestContext requestContext, DeviceSessionContext sessionContext);
} }
} }

31
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.snmp.session; package org.thingsboard.server.transport.snmp.session;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -26,9 +27,12 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
@ -57,17 +61,22 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
private SnmpDeviceTransportConfiguration deviceTransportConfiguration; private SnmpDeviceTransportConfiguration deviceTransportConfiguration;
@Getter @Getter
private final Device device; private final Device device;
@Getter
private final TenantId tenantId;
private final SnmpTransportContext snmpTransportContext; private final SnmpTransportContext snmpTransportContext;
private final AtomicInteger msgIdSeq = new AtomicInteger(0); private final AtomicInteger msgIdSeq = new AtomicInteger(0);
@Getter @Getter
private boolean isActive = true; private boolean isActive = true;
@Setter
private Runnable sessionTimeoutHandler;
@Getter @Getter
private final List<ScheduledFuture<?>> queryingTasks = new LinkedList<>(); private final List<ScheduledFuture<?>> queryingTasks = new LinkedList<>();
public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, @Builder
public DeviceSessionContext(TenantId tenantId, Device device, DeviceProfile deviceProfile, String token,
SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration,
SnmpDeviceTransportConfiguration deviceTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration,
SnmpTransportContext snmpTransportContext) throws Exception { SnmpTransportContext snmpTransportContext) throws Exception {
@ -75,6 +84,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
super.setDeviceId(device.getId()); super.setDeviceId(device.getId());
super.setDeviceProfile(deviceProfile); super.setDeviceProfile(deviceProfile);
this.device = device; this.device = device;
this.tenantId = tenantId;
this.token = token; this.token = token;
this.snmpTransportContext = snmpTransportContext; this.snmpTransportContext = snmpTransportContext;
@ -131,19 +141,32 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S
@Override @Override
public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) { public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) {
log.trace("[{}] Received attributes update notification to device", sessionId); log.trace("[{}] Received attributes update notification to device", sessionId);
snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); try {
snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification);
} catch (Exception e) {
snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING.getLabel(), e);
}
} }
@Override @Override
public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) { public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) {
log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
if (sessionCloseNotification.getMessage().equals(DefaultTransportService.SESSION_EXPIRED_MESSAGE)) {
if (sessionTimeoutHandler != null) {
sessionTimeoutHandler.run();
}
}
} }
@Override @Override
public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); try {
snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest);
snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);
} catch (Exception e) {
snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST.getLabel(), e);
}
} }
@Override @Override

23
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.transport.snmp; package org.thingsboard.server.transport.snmp;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget; import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU; import org.snmp4j.PDU;
import org.snmp4j.Snmp; import org.snmp4j.Snmp;
@ -35,7 +34,6 @@ import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.StorageType; import org.snmp4j.agent.mo.snmp.StorageType;
import org.snmp4j.agent.mo.snmp.VacmMIB; import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.security.MutableVACM; import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants; import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityModel;
@ -53,27 +51,11 @@ import org.snmp4j.transport.TransportMappings;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class SnmpDeviceSimulatorV2 extends BaseAgent { public class SnmpDeviceSimulatorV2 extends BaseAgent {
public static class RequestProcessor extends CommandProcessor {
private final Consumer<CommandResponderEvent> processor;
public RequestProcessor(Consumer<CommandResponderEvent> processor) {
super(new OctetString(MPv3.createLocalEngineID()));
this.processor = processor;
}
@Override
public void processPdu(CommandResponderEvent event) {
processor.accept(event);
}
}
private final Target target; private final Target target;
private final Address address; private final Address address;
private Snmp snmp; private Snmp snmp;
@ -81,10 +63,7 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent {
private final String password; private final String password;
public SnmpDeviceSimulatorV2(int port, String password) throws IOException { public SnmpDeviceSimulatorV2(int port, String password) throws IOException {
super(new File("conf.agent"), new File("bootCounter.agent"), new RequestProcessor(event -> { super(new File("conf.agent"), new File("bootCounter.agent"), new CommandProcessor(new OctetString("12312")));
System.out.println("aboba");
((Snmp) event.getSource()).cancel(event.getPDU(), event1 -> System.out.println("canceled"));
}));
CommunityTarget target = new CommunityTarget(); CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(password)); target.setCommunity(new OctetString(password));
this.address = GenericAddress.parse("udp:0.0.0.0/" + port); this.address = GenericAddress.parse("udp:0.0.0.0/" + port);

43
common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java

@ -16,34 +16,41 @@
package org.thingsboard.server.transport.snmp; package org.thingsboard.server.transport.snmp;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
public class SnmpTestV2 { public class SnmpTestV2 {
public static void main(String[] args) throws IOException {
SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public");
device.start(); private static final Scanner scanner = new Scanner(System.in);
device.setUpMappings(Map.of(
".1.3.6.1.2.1.1.1.50", "12",
".1.3.6.1.2.1.2.1.52", "56",
".1.3.6.1.2.1.3.1.54", "yes",
".1.3.6.1.2.1.7.1.58", ""
));
public static void main(String[] args) throws IOException {
SnmpDeviceSimulatorV2 client = new SnmpDeviceSimulatorV2(1610, "public");
// while (true) { client.start();
// new Scanner(System.in).nextLine(); Map<String, String> mappings = new HashMap<>();
// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); // for (int i = 1; i <= 500; i++) {
// System.out.println("sent"); // String oid = String.format(".1.3.6.1.2.1.%s.1.52", i);
// mappings.put(oid, "value_" + i);
// } // }
mappings.put("1.3.6.1.2.1.266.1.52", "****");
// Snmp snmp = new Snmp(device.transportMappings[0]); client.setUpMappings(mappings);
// device.snmp.addCommandResponder(event -> { inputTraps(client);
// System.out.println(event);
// }); scanner.nextLine();
}
new Scanner(System.in).nextLine(); private static void inputTraps(SnmpDeviceSimulatorV2 client) throws IOException {
while (true) {
String data = scanner.nextLine();
if (!data.isEmpty()) {
client.sendTrap("127.0.0.1", 1620, Map.of(
"1.3.6.1.2.1.266.1.52", data + " (266)",
"1.3.6.1.2.1.267.1.52", data + " (267)"
));
}
}
} }
} }

7
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -17,6 +17,9 @@ package org.thingsboard.server.common.transport;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
@ -144,6 +147,10 @@ public interface TransportService {
void reportActivity(SessionInfoProto sessionInfo); void reportActivity(SessionInfoProto sessionInfo);
void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error);
void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error);
void deregisterSession(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo);
void log(SessionInfoProto sessionInfo, String msg); void log(SessionInfoProto sessionInfo, String msg);

49
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -22,6 +22,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -49,6 +50,8 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.limit.LimitedApi;
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
@ -827,6 +830,37 @@ public class DefaultTransportService implements TransportService {
sessionsToRemove.forEach(sessionsActivity::remove); sessionsToRemove.forEach(sessionsActivity::remove);
} }
@Override
public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) {
ToCoreMsg msg = ToCoreMsg.newBuilder()
.setLifecycleEventMsg(TransportProtos.LifecycleEventProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setEntityIdMSB(deviceId.getId().getMostSignificantBits())
.setEntityIdLSB(deviceId.getId().getLeastSignificantBits())
.setServiceId(serviceInfoProvider.getServiceId())
.setLcEventType(eventType.name())
.setSuccess(success)
.setError(error != null ? ExceptionUtils.getStackTrace(error) : ""))
.build();
sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY);
}
@Override
public void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error) {
ToCoreMsg msg = ToCoreMsg.newBuilder()
.setErrorEventMsg(TransportProtos.ErrorEventProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setEntityIdMSB(deviceId.getId().getMostSignificantBits())
.setEntityIdLSB(deviceId.getId().getLeastSignificantBits())
.setServiceId(serviceInfoProvider.getServiceId())
.setMethod(method)
.setError(ExceptionUtils.getStackTrace(error)))
.build();
sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY);
}
@Override @Override
public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) {
SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener);
@ -1118,18 +1152,21 @@ public class DefaultTransportService implements TransportService {
} }
protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) { protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build();
sendToCore(getTenantId(sessionInfo), getDeviceId(sessionInfo), toCoreMsg, getRoutingKey(sessionInfo), callback);
}
private void sendToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, UUID routingKey, TransportServiceCallback<Void> callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, entityId, tpi.getFullTopicName(), msg);
} }
TransportTbQueueCallback transportTbQueueCallback = callback != null ? TransportTbQueueCallback transportTbQueueCallback = callback != null ?
new TransportTbQueueCallback(callback) : null; new TransportTbQueueCallback(callback) : null;
tbCoreProducerStats.incrementTotal(); tbCoreProducerStats.incrementTotal();
StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats); StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats);
tbCoreMsgProducer.send(tpi, tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(routingKey, msg), wrappedCallback);
new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()),
wrappedCallback);
} }
private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {

2
docker/docker-compose.yml

@ -235,6 +235,8 @@ services:
tb-snmp-transport: tb-snmp-transport:
restart: always restart: always
image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
ports:
- "1620:1620/udp"
environment: environment:
TB_SERVICE_ID: tb-snmp-transport TB_SERVICE_ID: tb-snmp-transport
JAVA_OPTS: "${JAVA_OPTS}" JAVA_OPTS: "${JAVA_OPTS}"

1
docker/tb-snmp-transport.env

@ -1,6 +1,7 @@
ZOOKEEPER_ENABLED=true ZOOKEEPER_ENABLED=true
ZOOKEEPER_URL=zookeeper:2181 ZOOKEEPER_URL=zookeeper:2181
SNMP_BIND_PORT=1620
METRICS_ENABLED=true METRICS_ENABLED=true
METRICS_ENDPOINTS_EXPOSE=prometheus METRICS_ENDPOINTS_EXPOSE=prometheus
WEB_APPLICATION_ENABLE=true WEB_APPLICATION_ENABLE=true

4
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -99,11 +99,15 @@ redis:
transport: transport:
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
bind_port: "${SNMP_BIND_PORT:1620}"
response_processing: response_processing:
# parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices
parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}"
# to configure SNMP to work over UDP or TCP # to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"

8
ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts

@ -65,6 +65,10 @@ export enum ImportEntityColumnType {
lwm2mServerSecurityMode = 'LWM2M_SERVER_SECURITY_MODE', lwm2mServerSecurityMode = 'LWM2M_SERVER_SECURITY_MODE',
lwm2mServerClientPublicKeyOrId = 'LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID', lwm2mServerClientPublicKeyOrId = 'LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID',
lwm2mServerClientSecretKey = 'LWM2M_SERVER_CLIENT_SECRET_KEY', lwm2mServerClientSecretKey = 'LWM2M_SERVER_CLIENT_SECRET_KEY',
snmpHost = 'SNMP_HOST',
snmpPort = 'SNMP_PORT',
snmpVersion = 'SNMP_VERSION',
snmpCommunityString = 'SNMP_COMMUNITY_STRING',
isGateway = 'IS_GATEWAY', isGateway = 'IS_GATEWAY',
description = 'DESCRIPTION', description = 'DESCRIPTION',
routingKey = 'ROUTING_KEY', routingKey = 'ROUTING_KEY',
@ -96,6 +100,10 @@ export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType
[ImportEntityColumnType.lwm2mServerSecurityMode, 'import.column-type.lwm2m.lwm2m-server-security-mode'], [ImportEntityColumnType.lwm2mServerSecurityMode, 'import.column-type.lwm2m.lwm2m-server-security-mode'],
[ImportEntityColumnType.lwm2mServerClientPublicKeyOrId, 'import.column-type.lwm2m.lwm2m-server-public-key-id'], [ImportEntityColumnType.lwm2mServerClientPublicKeyOrId, 'import.column-type.lwm2m.lwm2m-server-public-key-id'],
[ImportEntityColumnType.lwm2mServerClientSecretKey, 'import.column-type.lwm2m.lwm2m-server-secret-key'], [ImportEntityColumnType.lwm2mServerClientSecretKey, 'import.column-type.lwm2m.lwm2m-server-secret-key'],
[ImportEntityColumnType.snmpHost, 'import.column-type.snmp.host'],
[ImportEntityColumnType.snmpPort, 'import.column-type.snmp.port'],
[ImportEntityColumnType.snmpVersion, 'import.column-type.snmp.version'],
[ImportEntityColumnType.snmpCommunityString, 'import.column-type.snmp.community-string'],
[ImportEntityColumnType.isGateway, 'import.column-type.isgateway'], [ImportEntityColumnType.isGateway, 'import.column-type.isgateway'],
[ImportEntityColumnType.description, 'import.column-type.description'], [ImportEntityColumnType.description, 'import.column-type.description'],
[ImportEntityColumnType.routingKey, 'import.column-type.routing-key'], [ImportEntityColumnType.routingKey, 'import.column-type.routing-key'],

4
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts

@ -105,6 +105,10 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
{ value: ImportEntityColumnType.lwm2mServerSecurityMode }, { value: ImportEntityColumnType.lwm2mServerSecurityMode },
{ value: ImportEntityColumnType.lwm2mServerClientPublicKeyOrId }, { value: ImportEntityColumnType.lwm2mServerClientPublicKeyOrId },
{ value: ImportEntityColumnType.lwm2mServerClientSecretKey }, { value: ImportEntityColumnType.lwm2mServerClientSecretKey },
{ value: ImportEntityColumnType.snmpHost },
{ value: ImportEntityColumnType.snmpPort },
{ value: ImportEntityColumnType.snmpVersion },
{ value: ImportEntityColumnType.snmpCommunityString },
); );
break; break;
case EntityType.ASSET: case EntityType.ASSET:

12
ui-ngx/src/app/shared/models/device.models.ts

@ -298,14 +298,16 @@ export enum SnmpSpecType {
TELEMETRY_QUERYING = 'TELEMETRY_QUERYING', TELEMETRY_QUERYING = 'TELEMETRY_QUERYING',
CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING', CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING',
SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING', SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING',
TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST' TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST',
TO_SERVER_RPC_REQUEST = 'TO_SERVER_RPC_REQUEST'
} }
export const SnmpSpecTypeTranslationMap = new Map<SnmpSpecType, string>([ export const SnmpSpecTypeTranslationMap = new Map<SnmpSpecType, string>([
[SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry'], [SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry (SNMP GET)'],
[SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes'], [SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes (SNMP GET)'],
[SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes'], [SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes (SNMP SET)'],
[SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'RPC request'] [SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'To-device RPC request (SNMP GET/SET)'],
[SnmpSpecType.TO_SERVER_RPC_REQUEST, 'From-device RPC request (SNMP TRAP)']
]); ]);
export interface SnmpCommunicationConfig { export interface SnmpCommunicationConfig {

6
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2784,6 +2784,12 @@
"lwm2m-server-secret-key": "LwM2M server secret key", "lwm2m-server-secret-key": "LwM2M server secret key",
"lwm2m-server-public-key-id": "LwM2M server public key or id" "lwm2m-server-public-key-id": "LwM2M server public key or id"
}, },
"snmp": {
"host": "SNMP host",
"port": "SNMP port",
"version": "SNMP version (v1, v2c or v3)",
"community-string": "SNMP community string"
},
"isgateway": "Is Gateway", "isgateway": "Is Gateway",
"activity-time-from-gateway-device": "Activity time from gateway device", "activity-time-from-gateway-device": "Activity time from gateway device",
"description": "Description", "description": "Description",

Loading…
Cancel
Save