23 changed files with 990 additions and 70 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,67 @@ |
|||
/** |
|||
* 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.PostConstruct; |
|||
import java.util.Map; |
|||
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; |
|||
|
|||
public HostRequestIntervalRegistry(@Value("${quota.host.intervalMs}") long intervalDurationMs, |
|||
@Value("${quota.host.ttlMs}") long ttlMs) { |
|||
this.intervalDurationMs = intervalDurationMs; |
|||
this.ttlMs = ttlMs; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
if (ttlMs < intervalDurationMs) { |
|||
log.warn("TTL for IntervalRegistry [{}] smaller than interval duration [{}]", ttlMs, intervalDurationMs); |
|||
} |
|||
} |
|||
|
|||
public long tick(String clientHostId) { |
|||
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,88 @@ |
|||
/** |
|||
* 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 javax.annotation.PostConstruct; |
|||
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> top = getTopElements(intervalRegistry.getContent()); |
|||
log(top); |
|||
} |
|||
|
|||
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) { |
|||
StringBuilder builder = new StringBuilder("Quota Statistic : "); |
|||
for (Map.Entry<String, Long> host : top.entrySet()) { |
|||
builder.append(host.getKey()).append(" : ").append(host.getValue()); |
|||
} |
|||
|
|||
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,64 @@ |
|||
/** |
|||
* 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 hostQuotaValidated() { |
|||
when(requestRegistry.tick("key")).thenReturn(10L); |
|||
when(requestsPolicy.isValid(10L)).thenReturn(true); |
|||
|
|||
assertTrue(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,57 @@ |
|||
/** |
|||
* 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.Before; |
|||
import org.junit.Test; |
|||
|
|||
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); |
|||
} |
|||
|
|||
@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")); |
|||
} |
|||
} |
|||
@ -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