Browse Source

Fix race condition on tenant creation; refactoring

pull/10472/head
ViacheslavKlimov 2 years ago
parent
commit
9d1f751f3a
  1. 89
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  2. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
  3. 16
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  4. 16
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  5. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  6. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  7. 31
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

89
application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java

@ -69,80 +69,71 @@ public class EntityStateSourcingListener {
@TransactionalEventListener(fallbackExecution = true) @TransactionalEventListener(fallbackExecution = true)
public void handleEvent(SaveEntityEvent<?> event) { public void handleEvent(SaveEntityEvent<?> event) {
log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event);
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId(); EntityId entityId = event.getEntityId();
EntityType entityType = entityId.getEntityType(); EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity save event: {}", tenantId, entityType, entityId, event);
boolean isCreated = event.getCreated() != null && event.getCreated(); boolean isCreated = event.getCreated() != null && event.getCreated();
ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED; ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED;
switch (entityType) { switch (entityType) {
case ASSET: case ASSET, ASSET_PROFILE, ENTITY_VIEW, NOTIFICATION_RULE -> {
case ASSET_PROFILE:
case ENTITY_VIEW:
case NOTIFICATION_RULE:
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent);
break; }
case RULE_CHAIN: case RULE_CHAIN -> {
RuleChain ruleChain = (RuleChain) event.getEntity(); RuleChain ruleChain = (RuleChain) event.getEntity();
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), lifecycleEvent); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), lifecycleEvent);
} }
break; }
case TENANT: case TENANT -> {
Tenant tenant = (Tenant) event.getEntity(); Tenant tenant = (Tenant) event.getEntity();
onTenantUpdate(tenant, lifecycleEvent); onTenantUpdate(tenant, lifecycleEvent);
break; }
case TENANT_PROFILE: case TENANT_PROFILE -> {
TenantProfile tenantProfile = (TenantProfile) event.getEntity(); TenantProfile tenantProfile = (TenantProfile) event.getEntity();
onTenantProfileUpdate(tenantProfile, lifecycleEvent); onTenantProfileUpdate(tenantProfile, lifecycleEvent);
break; }
case DEVICE: case DEVICE -> {
onDeviceUpdate(event.getEntity(), event.getOldEntity()); onDeviceUpdate(event.getEntity(), event.getOldEntity());
break; }
case DEVICE_PROFILE: case DEVICE_PROFILE -> {
DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); DeviceProfile deviceProfile = (DeviceProfile) event.getEntity();
onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated); onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated);
break; }
case EDGE: case EDGE -> {
handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent); handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent);
break; }
case TB_RESOURCE: case TB_RESOURCE -> {
TbResource tbResource = (TbResource) event.getEntity(); TbResource tbResource = (TbResource) event.getEntity();
tbClusterService.onResourceChange(tbResource, null); tbClusterService.onResourceChange(tbResource, null);
break; }
case API_USAGE_STATE: case API_USAGE_STATE -> {
ApiUsageState apiUsageState = (ApiUsageState) event.getEntity(); ApiUsageState apiUsageState = (ApiUsageState) event.getEntity();
tbClusterService.onApiStateChange(apiUsageState, null); tbClusterService.onApiStateChange(apiUsageState, null);
break; }
default: default -> {}
break;
} }
} }
@TransactionalEventListener(fallbackExecution = true) @TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) { public void handleEvent(DeleteEntityEvent<?> event) {
log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId(); EntityId entityId = event.getEntityId();
EntityType entityType = entityId.getEntityType(); EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event);
switch (entityType) { switch (entityType) {
case ASSET: case ASSET, ASSET_PROFILE, ENTITY_VIEW, CUSTOMER, EDGE, NOTIFICATION_RULE -> {
case ASSET_PROFILE:
case ENTITY_VIEW:
case CUSTOMER:
case EDGE:
case NOTIFICATION_RULE:
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED);
break; }
case NOTIFICATION_REQUEST: case NOTIFICATION_REQUEST -> {
NotificationRequest request = (NotificationRequest) event.getEntity(); NotificationRequest request = (NotificationRequest) event.getEntity();
if (request.isScheduled()) { if (request.isScheduled()) {
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED);
} }
break; }
case RULE_CHAIN: case RULE_CHAIN -> {
RuleChain ruleChain = (RuleChain) event.getEntity(); RuleChain ruleChain = (RuleChain) event.getEntity();
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
Set<RuleChainId> referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {}); Set<RuleChainId> referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {});
@ -152,29 +143,28 @@ public class EntityStateSourcingListener {
} }
tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED);
} }
break; }
case TENANT: case TENANT -> {
Tenant tenant = (Tenant) event.getEntity(); Tenant tenant = (Tenant) event.getEntity();
onTenantDeleted(tenant); onTenantDeleted(tenant);
break; }
case TENANT_PROFILE: case TENANT_PROFILE -> {
TenantProfile tenantProfile = (TenantProfile) event.getEntity(); TenantProfile tenantProfile = (TenantProfile) event.getEntity();
tbClusterService.onTenantProfileDelete(tenantProfile, null); tbClusterService.onTenantProfileDelete(tenantProfile, null);
break; }
case DEVICE: case DEVICE -> {
Device device = (Device) event.getEntity(); Device device = (Device) event.getEntity();
tbClusterService.onDeviceDeleted(tenantId, device, null); tbClusterService.onDeviceDeleted(tenantId, device, null);
break; }
case DEVICE_PROFILE: case DEVICE_PROFILE -> {
DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); DeviceProfile deviceProfile = (DeviceProfile) event.getEntity();
onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile); onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile);
break; }
case TB_RESOURCE: case TB_RESOURCE -> {
TbResourceInfo tbResource = (TbResourceInfo) event.getEntity(); TbResourceInfo tbResource = (TbResourceInfo) event.getEntity();
tbClusterService.onResourceDeleted(tbResource, null); tbClusterService.onResourceDeleted(tbResource, null);
break; }
default: default -> {}
break;
} }
} }
@ -186,8 +176,7 @@ public class EntityStateSourcingListener {
&& event.getEntity() instanceof DeviceCredentials) { && event.getEntity() instanceof DeviceCredentials) {
tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(event.getTenantId(), tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(event.getTenantId(),
(DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null); (DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null);
} else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device) { } else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device device) {
Device device = (Device) event.getEntity();
Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class); Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class);
if (tenant != null) { if (tenant != null) {
tbClusterService.onDeviceAssignedToTenant(tenant.getId(), device); tbClusterService.onDeviceAssignedToTenant(tenant.getId(), device);

18
application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java

@ -16,12 +16,10 @@
package org.thingsboard.server.service.entitiy.tenant; package org.thingsboard.server.service.entitiy.tenant;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -45,25 +43,19 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
private final TbQueueService tbQueueService; private final TbQueueService tbQueueService;
private final TenantProfileService tenantProfileService; private final TenantProfileService tenantProfileService;
private final EntitiesVersionControlService versionControlService; private final EntitiesVersionControlService versionControlService;
private final ApplicationEventPublisher eventPublisher;
@Override @Override
public Tenant save(Tenant tenant) throws Exception { public Tenant save(Tenant tenant) throws Exception {
boolean created = tenant.getId() == null; boolean created = tenant.getId() == null;
Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null;
Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant, !created)); Tenant savedTenant = tenantService.saveTenant(tenant, tenantId -> {
if (created) { installScripts.createDefaultRuleChains(tenantId);
installScripts.createDefaultRuleChains(savedTenant.getId()); installScripts.createDefaultEdgeRuleChains(tenantId);
installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); installScripts.createDefaultTenantDashboards(tenantId, null);
installScripts.createDefaultTenantDashboards(savedTenant.getId(), null); });
}
tenantProfileCache.evict(savedTenant.getId()); tenantProfileCache.evict(savedTenant.getId());
if (created) {
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(savedTenant.getId()).entity(savedTenant).created(true).build());
}
TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null;
TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId());
tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile);

16
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -151,17 +151,18 @@ public class InstallScripts {
} }
} }
public void createDefaultRuleChains(TenantId tenantId) throws IOException { public void createDefaultRuleChains(TenantId tenantId) {
Path tenantChainsDir = getTenantRuleChainsDir(); Path tenantChainsDir = getTenantRuleChainsDir();
loadRuleChainsFromPath(tenantId, tenantChainsDir); loadRuleChainsFromPath(tenantId, tenantChainsDir);
} }
public void createDefaultEdgeRuleChains(TenantId tenantId) throws IOException { public void createDefaultEdgeRuleChains(TenantId tenantId) {
Path edgeChainsDir = getEdgeRuleChainsDir(); Path edgeChainsDir = getEdgeRuleChainsDir();
loadRuleChainsFromPath(tenantId, edgeChainsDir); loadRuleChainsFromPath(tenantId, edgeChainsDir);
} }
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException { @SneakyThrows
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) {
findRuleChainsFromPath(ruleChainsPath).forEach(path -> { findRuleChainsFromPath(ruleChainsPath).forEach(path -> {
try { try {
createRuleChainFromFile(tenantId, path, null); createRuleChainFromFile(tenantId, path, null);
@ -329,17 +330,18 @@ public class InstallScripts {
} }
} }
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { public void loadDashboards(TenantId tenantId, CustomerId customerId) {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir); loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
} }
public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) throws Exception { public void createDefaultTenantDashboards(TenantId tenantId, CustomerId customerId) {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR); Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir); loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
} }
private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) throws IOException { @SneakyThrows
private void loadDashboardsFromDir(TenantId tenantId, CustomerId customerId, Path dashboardsDir) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach( dirStream.forEach(
path -> { path -> {
@ -414,7 +416,7 @@ public class InstallScripts {
} }
); );
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load resources lwm2m object model from file: [{}]", resourceLwm2mPath.toString()); log.error("Unable to load resources lwm2m object model from file: [{}]", resourceLwm2mPath);
throw new RuntimeException("resource lwm2m object model from file", e); throw new RuntimeException("resource lwm2m object model from file", e);
} }
} }

16
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -663,7 +663,7 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class); savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.until(() -> { .until(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId);
return !tpi.getTenantId().get().isSysTenantId(); return !tpi.getTenantId().get().isSysTenantId();
@ -677,7 +677,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile.setIsolatedTbRuleEngine(false); tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList()); tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
await().atMost(10, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId) .until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId)
.getTenantId().get().isSysTenantId()); .getTenantId().get().isSysTenantId());
@ -689,11 +689,11 @@ public class TenantControllerTest extends AbstractControllerTest {
submittedMsgs.add(tbMsg.getId()); submittedMsgs.add(tbMsg.getId());
Thread.sleep(timeLeft / msgs); Thread.sleep(timeLeft / msgs);
} }
await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic)); verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic));
}); });
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
for (UUID msgId : submittedMsgs) { for (UUID msgId : submittedMsgs) {
verify(actorContext).tell(argThat(msg -> { verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(msgId); return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(msgId);
@ -718,13 +718,13 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class); savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull(); assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();
}); });
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId);
assertThat(tpi.getTenantId()).hasValue(tenantId); assertThat(tpi.getTenantId()).hasValue(tenantId);
TbMsg tbMsg = publishTbMsg(tenantId, tpi); TbMsg tbMsg = publishTbMsg(tenantId, tpi);
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(actorContext).tell(argThat(msg -> { verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId()); return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
})); }));
@ -732,7 +732,7 @@ public class TenantControllerTest extends AbstractControllerTest {
deleteDifferentTenant(); deleteDifferentTenant();
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull(); assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull();
assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId)) assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId))
.isInstanceOf(TenantNotFoundException.class); .isInstanceOf(TenantNotFoundException.class);
@ -752,7 +752,7 @@ public class TenantControllerTest extends AbstractControllerTest {
} }
private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) { private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) {
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId);
tpiAssert.accept(tpi); tpiAssert.accept(tpi);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public interface TenantService extends EntityDaoService { public interface TenantService extends EntityDaoService {
@ -36,7 +37,7 @@ public interface TenantService extends EntityDaoService {
Tenant saveTenant(Tenant tenant); Tenant saveTenant(Tenant tenant);
Tenant saveTenant(Tenant tenant, boolean publishSaveEvent); Tenant saveTenant(Tenant tenant, Consumer<TenantId> defaultEntitiesCreator);
boolean tenantExists(TenantId tenantId); boolean tenantExists(TenantId tenantId);

3
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
@ -157,7 +156,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
return new UserNotificationSettings(prefs); return new UserNotificationSettings(prefs);
} }
@Transactional(propagation = Propagation.NOT_SUPPORTED) // so that parent transaction is not aborted on method failure @Transactional
@Override @Override
public void createDefaultNotificationConfigs(TenantId tenantId) { public void createDefaultNotificationConfigs(TenantId tenantId) {
NotificationTarget allUsers = createTarget(tenantId, "All users", new AllUsersFilter(), NotificationTarget allUsers = createTarget(tenantId, "All users", new AllUsersFilter(),

31
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -63,6 +63,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -187,12 +188,12 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Override @Override
@Transactional @Transactional
public Tenant saveTenant(Tenant tenant) { public Tenant saveTenant(Tenant tenant) {
return saveTenant(tenant, true); return saveTenant(tenant, null);
} }
@Override @Override
@Transactional @Transactional
public Tenant saveTenant(Tenant tenant, boolean publishSaveEvent) { public Tenant saveTenant(Tenant tenant, Consumer<TenantId> defaultEntitiesCreator) {
log.trace("Executing saveTenant [{}]", tenant); log.trace("Executing saveTenant [{}]", tenant);
tenant.setRegion(DEFAULT_TENANT_REGION); tenant.setRegion(DEFAULT_TENANT_REGION);
if (tenant.getTenantProfileId() == null) { if (tenant.getTenantProfileId() == null) {
@ -201,20 +202,20 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
} }
tenantValidator.validate(tenant, Tenant::getId); tenantValidator.validate(tenant, Tenant::getId);
boolean create = tenant.getId() == null; boolean create = tenant.getId() == null;
Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create)); TenantId tenantId = savedTenant.getId();
if (publishSaveEvent) { publishEvictEvent(new TenantEvictEvent(tenantId, create));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedTenant.getId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(savedTenant.getId()).entity(savedTenant).created(create).build()); .entityId(tenantId).entity(savedTenant).created(create).build());
}
if (tenant.getId() == null) { if (create) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); deviceProfileService.createDefaultDeviceProfile(tenantId);
assetProfileService.createDefaultAssetProfile(savedTenant.getId()); assetProfileService.createDefaultAssetProfile(tenantId);
apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null); apiUsageStateService.createDefaultApiUsageState(tenantId, null);
try { notificationSettingsService.createDefaultNotificationConfigs(tenantId);
notificationSettingsService.createDefaultNotificationConfigs(savedTenant.getId()); if (defaultEntitiesCreator != null) {
} catch (Throwable e) { defaultEntitiesCreator.accept(tenantId);
log.error("Failed to create default notification configs for tenant {}", savedTenant.getId(), e);
} }
} }
return savedTenant; return savedTenant;

Loading…
Cancel
Save