Claiming devices using claimData attribute (#2105)

* Claiming devices using claimData attribute

* Fixed license header
This commit is contained in:
Andrew Shvayka 2019-10-18 08:30:53 +03:00 committed by GitHub
parent 25e36583f8
commit 2b7df84ae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 13 deletions

View File

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2019 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.device;
import lombok.Data;
import org.thingsboard.server.dao.device.claim.ClaimData;
import java.util.List;
@Data
public class ClaimDataInfo {
private final boolean fromCache;
private final List<Object> key;
private final ClaimData data;
}

View File

@ -23,12 +23,13 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import java.util.List;
import java.util.concurrent.ExecutionException;
public interface ClaimDevicesService {
ListenableFuture<Void> registerClaimingInfo(TenantId tenantId, DeviceId deviceId, String secretKey, long durationMs);
ListenableFuture<ClaimResult> claimDevice(Device device, CustomerId customerId, String secretKey);
ListenableFuture<ClaimResult> claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException;
ListenableFuture<List<Void>> reClaimDevice(TenantId tenantId, Device device);

View File

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId;
@ -37,9 +39,12 @@ import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.model.ModelConstants;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CACHE;
@ -48,6 +53,8 @@ import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CA
public class ClaimDevicesServiceImpl implements ClaimDevicesService {
private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed";
private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData";
private static final ObjectMapper mapper = new ObjectMapper();
@Autowired
private DeviceService deviceService;
@ -95,24 +102,45 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
});
}
@Override
public ListenableFuture<ClaimResult> claimDevice(Device device, CustomerId customerId, String secretKey) {
private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException {
List<Object> key = constructCacheKey(device.getId());
ClaimData claimDataFromCache = cache.get(key, ClaimData.class);
if (claimDataFromCache != null) {
return new ClaimDataInfo(true, key, claimDataFromCache);
} else {
Optional<AttributeKvEntry> claimDataAttr = attributesService.find(device.getTenantId(), device.getId(),
DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME).get();
if (claimDataAttr.isPresent()) {
try {
ClaimData claimDataFromAttribute = mapper.readValue(claimDataAttr.get().getValueAsString(), ClaimData.class);
return new ClaimDataInfo(false, key, claimDataFromAttribute);
} catch (IOException e) {
log.warn("Failed to read Claim Data [{}] from attribute!", claimDataAttr, e);
}
}
}
return null;
}
@Override
public ListenableFuture<ClaimResult> claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException {
Cache cache = cacheManager.getCache(CLAIM_DEVICES_CACHE);
ClaimData claimData = cache.get(key, ClaimData.class);
ClaimDataInfo claimData = getClaimData(cache, device);
if (claimData != null) {
long currTs = System.currentTimeMillis();
if (currTs > claimData.getExpirationTime() || !secretKey.equals(claimData.getSecretKey())) {
if (currTs > claimData.getData().getExpirationTime() || !secretKeyIsEmptyOrEqual(secretKey, claimData.getData().getSecretKey())) {
log.warn("The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]", device.getName());
cache.evict(key);
if (claimData.isFromCache()) {
cache.evict(claimData.getKey());
}
return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE));
} else {
if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
device.setCustomerId(customerId);
Device savedDevice = deviceService.saveDevice(device);
return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS));
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS));
}
return Futures.transform(removeClaimingSavedData(cache, key, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED));
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED));
}
} else {
log.warn("Failed to find the device's claiming message![{}]", device.getName());
@ -124,6 +152,10 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
}
}
private boolean secretKeyIsEmptyOrEqual(String secretKeyA, String secretKeyB) {
return (StringUtils.isEmpty(secretKeyA) && StringUtils.isEmpty(secretKeyB)) || secretKeyA.equals(secretKeyB);
}
@Override
public ListenableFuture<List<Void>> reClaimDevice(TenantId tenantId, Device device) {
if (!device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
@ -159,13 +191,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return systemDurationMs;
}
private ListenableFuture<List<Void>> removeClaimingSavedData(Cache cache, List<Object> key, Device device) {
cache.evict(key);
if (isAllowedClaimingByDefault) {
return Futures.immediateFuture(null);
private ListenableFuture<List<Void>> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) {
if (data.isFromCache()) {
cache.evict(data.getKey());
}
return attributesService.removeAll(device.getTenantId(),
device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(CLAIM_ATTRIBUTE_NAME));
device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME));
}
private void cacheEviction(DeviceId deviceId) {