added entity usage

This commit is contained in:
YevhenBondarenko 2023-04-03 10:07:16 +02:00
parent 8edc55d48e
commit 8481b71b7e
6 changed files with 331 additions and 0 deletions

View File

@ -0,0 +1,45 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.dao.usage.UsageInfoService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class UsageInfoController extends BaseController {
@Autowired
private UsageInfoService usageInfoService;
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/usageInfo", method = RequestMethod.GET)
@ResponseBody
public UsageInfo getTenantUsageInfo() throws ThingsboardException {
return checkNotNull(usageInfoService.getUsageInfo(getCurrentUser().getTenantId()));
}
}

View File

@ -26,11 +26,13 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.FeaturesInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.TenantId;
@ -49,7 +51,9 @@ import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.stats.TbApiUsageStateClient;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
@ -69,6 +73,9 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
@Autowired
private TbApiUsageStateClient apiUsageStateClient;
@Autowired
private TbTenantProfileCache tenantProfileCache;
//For system administrator
@Test
public void testTenantsCountWsCmd() throws Exception {
@ -329,6 +336,99 @@ public abstract class BaseHomePageApiTest extends AbstractControllerTest {
Assert.assertTrue(featuresInfo.isOauthEnabled());
}
@Test
public void testUsageInfo() throws Exception {
loginTenantAdmin();
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
Assert.assertNotNull(tenantProfile);
DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
UsageInfo usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertNotNull(usageInfo);
Assert.assertEquals(0, usageInfo.getDevices());
Assert.assertEquals(configuration.getMaxDevices(), usageInfo.getMaxDevices());
Assert.assertEquals(0, usageInfo.getAssets());
Assert.assertEquals(configuration.getMaxAssets(), usageInfo.getMaxAssets());
Assert.assertEquals(1, usageInfo.getCustomers());
Assert.assertEquals(configuration.getMaxCustomers(), usageInfo.getMaxCustomers());
Assert.assertEquals(2, usageInfo.getUsers());
Assert.assertEquals(configuration.getMaxUsers(), usageInfo.getMaxUsers());
Assert.assertEquals(0, usageInfo.getDashboards());
Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards());
Assert.assertEquals(0, usageInfo.getTransportMessages());
Assert.assertEquals(configuration.getMaxTransportMessages(), usageInfo.getMaxTransportMessages());
Assert.assertEquals(0, usageInfo.getJsExecutions());
Assert.assertEquals(configuration.getMaxJSExecutions(), usageInfo.getMaxJsExecutions());
Assert.assertEquals(0, usageInfo.getEmails());
Assert.assertEquals(configuration.getMaxEmails(), usageInfo.getMaxEmails());
Assert.assertEquals(0, usageInfo.getSms());
Assert.assertEquals(configuration.getMaxSms(), usageInfo.getMaxSms());
Assert.assertEquals(0, usageInfo.getAlarms());
Assert.assertEquals(configuration.getMaxCreatedAlarms(), usageInfo.getMaxAlarms());
List<Device> devices = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setName("device" + i);
devices.add(doPost("/api/device", device, Device.class));
}
usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertEquals(devices.size(), usageInfo.getDevices());
List<Asset> assets = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Asset asset = new Asset();
asset.setName("asset" + i);
assets.add(doPost("/api/asset", asset, Asset.class));
}
usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertEquals(assets.size(), usageInfo.getAssets());
List<Customer> customers = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Customer customer = new Customer();
customer.setTitle("customer" + i);
customers.add(doPost("/api/customer", customer, Customer.class));
}
usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertEquals(customers.size() + 1, usageInfo.getCustomers());
List<User> users = new ArrayList<>();
for (int i = 0; i < 97; i++) {
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail(i + "user@thingsboard.org");
users.add(doPost("/api/user", user, User.class));
}
usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertEquals(users.size() + 2, usageInfo.getUsers());
List<Dashboard> dashboards = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("dashboard" + i);
dashboards.add(doPost("/api/dashboard", dashboard, Dashboard.class));
}
usageInfo = doGet("/api/tenant/usageInfo", UsageInfo.class);
Assert.assertEquals(dashboards.size(), usageInfo.getDashboards());
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList(

View File

@ -0,0 +1,25 @@
/**
* 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.dao.usage;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.id.TenantId;
public interface UsageInfoService {
UsageInfo getUsageInfo(TenantId tenantId);
}

View File

@ -0,0 +1,43 @@
/**
* 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;
import lombok.Data;
@Data
public class UsageInfo {
private long devices;
private long maxDevices;
private long assets;
private long maxAssets;
private long customers;
private long maxCustomers;
private long users;
private long maxUsers;
private long dashboards;
private long maxDashboards;
private long transportMessages;
private long maxTransportMessages;
private long jsExecutions;
private long maxJsExecutions;
private long emails;
private long maxEmails;
private long sms;
private long maxSms;
private long alarms;
private long maxAlarms;
}

View File

@ -0,0 +1,109 @@
/**
* 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.dao.usage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserDao;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@Service
@Slf4j
@RequiredArgsConstructor
public class BasicUsageInfoService implements UsageInfoService {
private final DeviceDao deviceDao;
private final AssetDao assetDao;
private final CustomerDao customerDao;
private final UserDao userDao;
private final DashboardDao dashboardDao;
private final ApiUsageStateService apiUsageStateService;
private final TimeseriesService tsService;
@Lazy
private final TbTenantProfileCache tenantProfileCache;
@Override
public UsageInfo getUsageInfo(TenantId tenantId) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
UsageInfo usageInfo = new UsageInfo();
usageInfo.setDevices(deviceDao.countByTenantId(tenantId));
usageInfo.setMaxDevices(profileConfiguration.getMaxDevices());
usageInfo.setAssets(assetDao.countByTenantId(tenantId));
usageInfo.setMaxAssets(profileConfiguration.getMaxAssets());
usageInfo.setCustomers(customerDao.countByTenantId(tenantId));
usageInfo.setMaxCustomers(profileConfiguration.getMaxCustomers());
usageInfo.setUsers(userDao.countByTenantId(tenantId));
usageInfo.setMaxUsers(profileConfiguration.getMaxUsers());
usageInfo.setDashboards(dashboardDao.countByTenantId(tenantId));
usageInfo.setMaxDashboards(profileConfiguration.getMaxDashboards());
usageInfo.setMaxAlarms(profileConfiguration.getMaxCreatedAlarms());
usageInfo.setMaxTransportMessages(profileConfiguration.getMaxTransportMessages());
usageInfo.setMaxJsExecutions(profileConfiguration.getMaxJSExecutions());
usageInfo.setMaxEmails(profileConfiguration.getMaxEmails());
usageInfo.setMaxSms(profileConfiguration.getMaxSms());
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId);
if (apiUsageState != null) {
Collection<String> keys = Arrays.asList(
ApiUsageRecordKey.TRANSPORT_MSG_COUNT.getApiCountKey(),
ApiUsageRecordKey.JS_EXEC_COUNT.getApiCountKey(),
ApiUsageRecordKey.EMAIL_EXEC_COUNT.getApiCountKey(),
ApiUsageRecordKey.SMS_EXEC_COUNT.getApiCountKey(),
ApiUsageRecordKey.CREATED_ALARMS_COUNT.getApiCountKey());
try {
List<TsKvEntry> entries = tsService.findLatest(tenantId, apiUsageState.getId(), keys).get();
usageInfo.setTransportMessages(getLongValueFromTsEntries(entries, ApiUsageRecordKey.TRANSPORT_MSG_COUNT.getApiCountKey()));
usageInfo.setJsExecutions(getLongValueFromTsEntries(entries, ApiUsageRecordKey.JS_EXEC_COUNT.getApiCountKey()));
usageInfo.setEmails(getLongValueFromTsEntries(entries, ApiUsageRecordKey.EMAIL_EXEC_COUNT.getApiCountKey()));
usageInfo.setSms(getLongValueFromTsEntries(entries, ApiUsageRecordKey.SMS_EXEC_COUNT.getApiCountKey()));
usageInfo.setAlarms(getLongValueFromTsEntries(entries, ApiUsageRecordKey.CREATED_ALARMS_COUNT.getApiCountKey()));
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException("Failed to fetch api usage values from timeseries!");
}
}
return usageInfo;
}
private long getLongValueFromTsEntries(List<TsKvEntry> entries, String key) {
Optional<TsKvEntry> entryOpt = entries.stream().filter(e -> e.getKey().equals(key)).findFirst();
if (entryOpt.isPresent() && entryOpt.get().getLongValue().isPresent()) {
return entryOpt.get().getLongValue().get();
} else {
return 0;
}
}
}

View File

@ -63,6 +63,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.UsageInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
@ -2467,6 +2468,14 @@ public class RestClient implements Closeable {
}, params).getBody();
}
public UsageInfo getUsageInfo() {
return restTemplate.exchange(
baseURL + "/api/tenant/usageInfo",
HttpMethod.GET,
HttpEntity.EMPTY,
UsageInfo.class).getBody();
}
public Optional<TenantProfile> getTenantProfileById(TenantProfileId tenantProfileId) {
try {
ResponseEntity<TenantProfile> tenantProfile = restTemplate.getForEntity(baseURL + "/api/tenantProfile/{tenantProfileId}", TenantProfile.class, tenantProfileId);