committed by
GitHub
23 changed files with 1062 additions and 69 deletions
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public final class Clock { |
||||
|
|
||||
|
private static long time = 0L; |
||||
|
|
||||
|
private Clock() { |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static long millis() { |
||||
|
return time == 0 ? System.currentTimeMillis() : time; |
||||
|
} |
||||
|
|
||||
|
public static void setMillis(long millis) { |
||||
|
time = millis; |
||||
|
} |
||||
|
|
||||
|
public static void shift(long delta) { |
||||
|
time += delta; |
||||
|
} |
||||
|
|
||||
|
public static void reset() { |
||||
|
time = 0; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class HostRequestLimitPolicy { |
||||
|
|
||||
|
private final long limit; |
||||
|
|
||||
|
public HostRequestLimitPolicy(@Value("${quota.host.limit}") long limit) { |
||||
|
this.limit = limit; |
||||
|
} |
||||
|
|
||||
|
public boolean isValid(long currentValue) { |
||||
|
return currentValue <= limit; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.HostRequestIntervalRegistry; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class HostRequestsQuotaService implements QuotaService { |
||||
|
|
||||
|
private final HostRequestIntervalRegistry requestRegistry; |
||||
|
private final HostRequestLimitPolicy requestsPolicy; |
||||
|
private final IntervalRegistryCleaner registryCleaner; |
||||
|
private final IntervalRegistryLogger registryLogger; |
||||
|
private final boolean enabled; |
||||
|
|
||||
|
public HostRequestsQuotaService(HostRequestIntervalRegistry requestRegistry, HostRequestLimitPolicy requestsPolicy, |
||||
|
IntervalRegistryCleaner registryCleaner, IntervalRegistryLogger registryLogger, |
||||
|
@Value("${quota.host.enabled}") boolean enabled) { |
||||
|
this.requestRegistry = requestRegistry; |
||||
|
this.requestsPolicy = requestsPolicy; |
||||
|
this.registryCleaner = registryCleaner; |
||||
|
this.registryLogger = registryLogger; |
||||
|
this.enabled = enabled; |
||||
|
} |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
if (enabled) { |
||||
|
registryCleaner.schedule(); |
||||
|
registryLogger.schedule(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
public void close() { |
||||
|
if (enabled) { |
||||
|
registryCleaner.stop(); |
||||
|
registryLogger.stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isQuotaExceeded(String key) { |
||||
|
if (enabled) { |
||||
|
long count = requestRegistry.tick(key); |
||||
|
return !requestsPolicy.isValid(count); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public interface QuotaService { |
||||
|
|
||||
|
boolean isQuotaExceeded(String key); |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import com.google.common.collect.Sets; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class HostRequestIntervalRegistry { |
||||
|
|
||||
|
private final Map<String, IntervalCount> hostCounts = new ConcurrentHashMap<>(); |
||||
|
private final long intervalDurationMs; |
||||
|
private final long ttlMs; |
||||
|
private final Set<String> whiteList; |
||||
|
private final Set<String> blackList; |
||||
|
|
||||
|
public HostRequestIntervalRegistry(@Value("${quota.host.intervalMs}") long intervalDurationMs, |
||||
|
@Value("${quota.host.ttlMs}") long ttlMs, |
||||
|
@Value("${quota.host.whitelist}") String whiteList, |
||||
|
@Value("${quota.host.blacklist}") String blackList) { |
||||
|
this.intervalDurationMs = intervalDurationMs; |
||||
|
this.ttlMs = ttlMs; |
||||
|
this.whiteList = Sets.newHashSet(StringUtils.split(whiteList, ',')); |
||||
|
this.blackList = Sets.newHashSet(StringUtils.split(blackList, ',')); |
||||
|
} |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
if (ttlMs < intervalDurationMs) { |
||||
|
log.warn("TTL for IntervalRegistry [{}] smaller than interval duration [{}]", ttlMs, intervalDurationMs); |
||||
|
} |
||||
|
log.info("Start Host Quota Service with whitelist {}", whiteList); |
||||
|
log.info("Start Host Quota Service with blacklist {}", blackList); |
||||
|
} |
||||
|
|
||||
|
public long tick(String clientHostId) { |
||||
|
if (whiteList.contains(clientHostId)) { |
||||
|
return 0; |
||||
|
} else if (blackList.contains(clientHostId)) { |
||||
|
return Long.MAX_VALUE; |
||||
|
} |
||||
|
IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs)); |
||||
|
return intervalCount.resetIfExpiredAndTick(); |
||||
|
} |
||||
|
|
||||
|
public void clean() { |
||||
|
hostCounts.entrySet().removeIf(entry -> entry.getValue().silenceDuration() > ttlMs); |
||||
|
} |
||||
|
|
||||
|
public Map<String, Long> getContent() { |
||||
|
return hostCounts.entrySet().stream() |
||||
|
.collect(Collectors.toMap( |
||||
|
Map.Entry::getKey, |
||||
|
interval -> interval.getValue().getCount())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
|
||||
|
import org.thingsboard.server.common.transport.quota.Clock; |
||||
|
|
||||
|
import java.util.concurrent.atomic.LongAdder; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class IntervalCount { |
||||
|
|
||||
|
private final LongAdder adder = new LongAdder(); |
||||
|
private final long intervalDurationMs; |
||||
|
private volatile long startTime; |
||||
|
private volatile long lastTickTime; |
||||
|
|
||||
|
public IntervalCount(long intervalDurationMs) { |
||||
|
this.intervalDurationMs = intervalDurationMs; |
||||
|
startTime = Clock.millis(); |
||||
|
} |
||||
|
|
||||
|
public long resetIfExpiredAndTick() { |
||||
|
if (isExpired()) { |
||||
|
reset(); |
||||
|
} |
||||
|
tick(); |
||||
|
return adder.sum(); |
||||
|
} |
||||
|
|
||||
|
public long silenceDuration() { |
||||
|
return Clock.millis() - lastTickTime; |
||||
|
} |
||||
|
|
||||
|
public long getCount() { |
||||
|
return adder.sum(); |
||||
|
} |
||||
|
|
||||
|
private void tick() { |
||||
|
adder.add(1); |
||||
|
lastTickTime = Clock.millis(); |
||||
|
} |
||||
|
|
||||
|
private void reset() { |
||||
|
adder.reset(); |
||||
|
startTime = Clock.millis(); |
||||
|
} |
||||
|
|
||||
|
private boolean isExpired() { |
||||
|
return (Clock.millis() - startTime) > intervalDurationMs; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PreDestroy; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class IntervalRegistryCleaner { |
||||
|
|
||||
|
private final HostRequestIntervalRegistry intervalRegistry; |
||||
|
private final long cleanPeriodMs; |
||||
|
private ScheduledExecutorService executor; |
||||
|
|
||||
|
public IntervalRegistryCleaner(HostRequestIntervalRegistry intervalRegistry, @Value("${quota.host.cleanPeriodMs}") long cleanPeriodMs) { |
||||
|
this.intervalRegistry = intervalRegistry; |
||||
|
this.cleanPeriodMs = cleanPeriodMs; |
||||
|
} |
||||
|
|
||||
|
public void schedule() { |
||||
|
if (executor != null) { |
||||
|
throw new IllegalStateException("Registry Cleaner already scheduled"); |
||||
|
} |
||||
|
executor = Executors.newSingleThreadScheduledExecutor(); |
||||
|
executor.scheduleAtFixedRate(this::clean, cleanPeriodMs, cleanPeriodMs, TimeUnit.MILLISECONDS); |
||||
|
} |
||||
|
|
||||
|
public void stop() { |
||||
|
if (executor != null) { |
||||
|
executor.shutdown(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void clean() { |
||||
|
try { |
||||
|
intervalRegistry.clean(); |
||||
|
} catch (RuntimeException ex) { |
||||
|
log.error("Could not clear Interval Registry", ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import com.google.common.collect.MinMaxPriorityQueue; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Comparator; |
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.function.Function; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class IntervalRegistryLogger { |
||||
|
|
||||
|
private final int topSize; |
||||
|
private final HostRequestIntervalRegistry intervalRegistry; |
||||
|
private final long logIntervalMin; |
||||
|
private ScheduledExecutorService executor; |
||||
|
|
||||
|
public IntervalRegistryLogger(@Value("${quota.log.topSize}") int topSize, @Value("${quota.log.intervalMin}") long logIntervalMin, |
||||
|
HostRequestIntervalRegistry intervalRegistry) { |
||||
|
this.topSize = topSize; |
||||
|
this.logIntervalMin = logIntervalMin; |
||||
|
this.intervalRegistry = intervalRegistry; |
||||
|
} |
||||
|
|
||||
|
public void schedule() { |
||||
|
if (executor != null) { |
||||
|
throw new IllegalStateException("Registry Cleaner already scheduled"); |
||||
|
} |
||||
|
executor = Executors.newSingleThreadScheduledExecutor(); |
||||
|
executor.scheduleAtFixedRate(this::logStatistic, logIntervalMin, logIntervalMin, TimeUnit.MINUTES); |
||||
|
} |
||||
|
|
||||
|
public void stop() { |
||||
|
if (executor != null) { |
||||
|
executor.shutdown(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void logStatistic() { |
||||
|
Map<String, Long> registryContent = intervalRegistry.getContent(); |
||||
|
int uniqHosts = registryContent.size(); |
||||
|
long requestsCount = registryContent.values().stream().mapToLong(i -> i).sum(); |
||||
|
Map<String, Long> top = getTopElements(registryContent); |
||||
|
log(top, uniqHosts, requestsCount); |
||||
|
} |
||||
|
|
||||
|
protected Map<String, Long> getTopElements(Map<String, Long> countMap) { |
||||
|
MinMaxPriorityQueue<Map.Entry<String, Long>> topQueue = MinMaxPriorityQueue |
||||
|
.orderedBy(Comparator.comparing((Function<Map.Entry<String, Long>, Long>) Map.Entry::getValue).reversed()) |
||||
|
.maximumSize(topSize) |
||||
|
.create(countMap.entrySet()); |
||||
|
|
||||
|
return topQueue.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
|
} |
||||
|
|
||||
|
private void log(Map<String, Long> top, int uniqHosts, long requestsCount) { |
||||
|
long rps = requestsCount / TimeUnit.MINUTES.toSeconds(logIntervalMin); |
||||
|
StringBuilder builder = new StringBuilder("Quota Statistic : "); |
||||
|
builder.append("uniqHosts : ").append(uniqHosts).append("; "); |
||||
|
builder.append("requestsCount : ").append(requestsCount).append("; "); |
||||
|
builder.append("RPS : ").append(rps).append(" "); |
||||
|
builder.append("top -> "); |
||||
|
for (Map.Entry<String, Long> host : top.entrySet()) { |
||||
|
builder.append(host.getKey()).append(" : ").append(host.getValue()).append("; "); |
||||
|
} |
||||
|
|
||||
|
log.info(builder.toString()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
import org.junit.After; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
|
||||
|
import static org.junit.Assert.*; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class ClockTest { |
||||
|
|
||||
|
@Before |
||||
|
public void init() { |
||||
|
Clock.reset(); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void clear() { |
||||
|
Clock.reset(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void defaultClockUseSystemTime() { |
||||
|
assertFalse(Clock.millis() > System.currentTimeMillis()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void timeCanBeSet() { |
||||
|
Clock.setMillis(100L); |
||||
|
assertEquals(100L, Clock.millis()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void clockCanBeReseted() { |
||||
|
Clock.setMillis(100L); |
||||
|
assertEquals(100L, Clock.millis()); |
||||
|
Clock.reset(); |
||||
|
assertFalse(Clock.millis() > System.currentTimeMillis()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void timeIsShifted() { |
||||
|
Clock.setMillis(100L); |
||||
|
Clock.shift(50L); |
||||
|
assertEquals(150L, Clock.millis()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
import org.junit.Test; |
||||
|
|
||||
|
import static org.junit.Assert.assertFalse; |
||||
|
import static org.junit.Assert.assertTrue; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class HostRequestLimitPolicyTest { |
||||
|
|
||||
|
private HostRequestLimitPolicy limitPolicy = new HostRequestLimitPolicy(10L); |
||||
|
|
||||
|
@Test |
||||
|
public void ifCurrentValueLessThenLimitItIsValid() { |
||||
|
assertTrue(limitPolicy.isValid(9)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void ifCurrentValueEqualsToLimitItIsValid() { |
||||
|
assertTrue(limitPolicy.isValid(10)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void ifCurrentValueGreaterThenLimitItIsValid() { |
||||
|
assertFalse(limitPolicy.isValid(11)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.HostRequestIntervalRegistry; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner; |
||||
|
import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger; |
||||
|
|
||||
|
import static org.junit.Assert.assertFalse; |
||||
|
import static org.junit.Assert.assertTrue; |
||||
|
import static org.mockito.Mockito.*; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class HostRequestsQuotaServiceTest { |
||||
|
|
||||
|
private HostRequestsQuotaService quotaService; |
||||
|
|
||||
|
private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class); |
||||
|
private HostRequestLimitPolicy requestsPolicy = mock(HostRequestLimitPolicy.class); |
||||
|
private IntervalRegistryCleaner registryCleaner = mock(IntervalRegistryCleaner.class); |
||||
|
private IntervalRegistryLogger registryLogger = mock(IntervalRegistryLogger.class); |
||||
|
|
||||
|
@Before |
||||
|
public void init() { |
||||
|
quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, true); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void quotaExceededIfRequestCountBiggerThanAllowed() { |
||||
|
when(requestRegistry.tick("key")).thenReturn(10L); |
||||
|
when(requestsPolicy.isValid(10L)).thenReturn(false); |
||||
|
|
||||
|
assertTrue(quotaService.isQuotaExceeded("key")); |
||||
|
|
||||
|
verify(requestRegistry).tick("key"); |
||||
|
verify(requestsPolicy).isValid(10L); |
||||
|
verifyNoMoreInteractions(requestRegistry, requestsPolicy); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void quotaNotExceededIfRequestCountLessThanAllowed() { |
||||
|
when(requestRegistry.tick("key")).thenReturn(10L); |
||||
|
when(requestsPolicy.isValid(10L)).thenReturn(true); |
||||
|
|
||||
|
assertFalse(quotaService.isQuotaExceeded("key")); |
||||
|
|
||||
|
verify(requestRegistry).tick("key"); |
||||
|
verify(requestsPolicy).isValid(10L); |
||||
|
verifyNoMoreInteractions(requestRegistry, requestsPolicy); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void serviceCanBeDisabled() { |
||||
|
quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, false); |
||||
|
assertFalse(quotaService.isQuotaExceeded("key")); |
||||
|
verifyNoMoreInteractions(requestRegistry, requestsPolicy); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import com.google.common.collect.Sets; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
|
||||
|
import static org.junit.Assert.assertEquals; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class HostRequestIntervalRegistryTest { |
||||
|
|
||||
|
private HostRequestIntervalRegistry registry; |
||||
|
|
||||
|
@Before |
||||
|
public void init() { |
||||
|
registry = new HostRequestIntervalRegistry(10000L, 100L,"g1,g2", "b1"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void newHostCreateNewInterval() { |
||||
|
assertEquals(1L, registry.tick("host1")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void existingHostUpdated() { |
||||
|
registry.tick("aaa"); |
||||
|
assertEquals(1L, registry.tick("bbb")); |
||||
|
assertEquals(2L, registry.tick("aaa")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void expiredIntervalsCleaned() throws InterruptedException { |
||||
|
registry.tick("aaa"); |
||||
|
Thread.sleep(150L); |
||||
|
registry.tick("bbb"); |
||||
|
registry.clean(); |
||||
|
assertEquals(1L, registry.tick("aaa")); |
||||
|
assertEquals(2L, registry.tick("bbb")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void domainFromWhitelistNotCounted(){ |
||||
|
assertEquals(0L, registry.tick("g1")); |
||||
|
assertEquals(0L, registry.tick("g1")); |
||||
|
assertEquals(0L, registry.tick("g2")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void domainFromBlackListReturnMaxValue(){ |
||||
|
assertEquals(Long.MAX_VALUE, registry.tick("b1")); |
||||
|
assertEquals(Long.MAX_VALUE, registry.tick("b1")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void emptyWhitelistParsedOk(){ |
||||
|
registry = new HostRequestIntervalRegistry(10000L, 100L,"", "b1"); |
||||
|
assertEquals(1L, registry.tick("aaa")); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void emptyBlacklistParsedOk(){ |
||||
|
registry = new HostRequestIntervalRegistry(10000L, 100L,"", ""); |
||||
|
assertEquals(1L, registry.tick("aaa")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import org.junit.After; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.transport.quota.Clock; |
||||
|
|
||||
|
import static org.junit.Assert.assertEquals; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class IntervalCountTest { |
||||
|
|
||||
|
@Before |
||||
|
public void init() { |
||||
|
Clock.setMillis(1000L); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void clear() { |
||||
|
Clock.reset(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void ticksInSameIntervalAreSummed() { |
||||
|
IntervalCount intervalCount = new IntervalCount(100L); |
||||
|
assertEquals(1L, intervalCount.resetIfExpiredAndTick()); |
||||
|
Clock.shift(100); |
||||
|
assertEquals(2L, intervalCount.resetIfExpiredAndTick()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void oldDataCleanedWhenIntervalExpired() { |
||||
|
IntervalCount intervalCount = new IntervalCount(100L); |
||||
|
assertEquals(1L, intervalCount.resetIfExpiredAndTick()); |
||||
|
Clock.shift(101); |
||||
|
assertEquals(1L, intervalCount.resetIfExpiredAndTick()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void silenceDurationCalculatedFromLastTick() { |
||||
|
IntervalCount intervalCount = new IntervalCount(100L); |
||||
|
assertEquals(1L, intervalCount.resetIfExpiredAndTick()); |
||||
|
Clock.shift(10L); |
||||
|
assertEquals(10L, intervalCount.silenceDuration()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.transport.quota.inmemory; |
||||
|
|
||||
|
import com.google.common.collect.ImmutableMap; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
import static org.junit.Assert.assertEquals; |
||||
|
import static org.mockito.Mockito.mock; |
||||
|
|
||||
|
/** |
||||
|
* @author Vitaliy Paromskiy |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
public class IntervalRegistryLoggerTest { |
||||
|
|
||||
|
private IntervalRegistryLogger logger; |
||||
|
|
||||
|
private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class); |
||||
|
|
||||
|
@Before |
||||
|
public void init() { |
||||
|
logger = new IntervalRegistryLogger(3, 10, requestRegistry); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void onlyMaxHostsCollected() { |
||||
|
Map<String, Long> map = ImmutableMap.of("a", 8L, "b", 3L, "c", 1L, "d", 3L); |
||||
|
Map<String, Long> actual = logger.getTopElements(map); |
||||
|
Map<String, Long> expected = ImmutableMap.of("a", 8L, "b", 3L, "d", 3L); |
||||
|
|
||||
|
assertEquals(expected, actual); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void emptyMapProcessedCorrectly() { |
||||
|
Map<String, Long> map = Collections.emptyMap(); |
||||
|
Map<String, Long> actual = logger.getTopElements(map); |
||||
|
Map<String, Long> expected = Collections.emptyMap(); |
||||
|
|
||||
|
assertEquals(expected, actual); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue