Ray Milkey
Committed by Gerrit Code Review

Unit test for the gossip intent store

This change also implements a unit test harness for the
EventuallyConsistentMap that is intended to be reusable
to test other stores.

Change-Id: I2257da9b19412b97a3aa0f127be7263a7732b852
......@@ -18,19 +18,25 @@ package org.onosproject.cluster;
import java.util.Set;
import org.joda.time.DateTime;
import org.onlab.packet.IpAddress;
import com.google.common.collect.ImmutableSet;
/**
* Test adapter for the cluster service.
*/
public class ClusterServiceAdapter implements ClusterService {
ControllerNode local = new DefaultControllerNode(new NodeId("local"),
IpAddress.valueOf("127.0.0.1"));
@Override
public ControllerNode getLocalNode() {
return null;
return local;
}
@Override
public Set<ControllerNode> getNodes() {
return null;
return ImmutableSet.of(local);
}
@Override
......
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.net.intent;
import org.onosproject.cluster.NodeId;
import static org.junit.Assert.*;
/**
* Testing adapter for the partition service.
*/
public class PartitionServiceAdapter implements PartitionService {
@Override
public boolean isMine(Key intentKey) {
return true;
}
@Override
public NodeId getLeader(Key intentKey) {
return null;
}
@Override
public void addListener(PartitionEventListener listener) {
}
@Override
public void removeListener(PartitionEventListener listener) {
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.store.service;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
/**
* Testing adapter for EventuallyConsistentMap.
*/
public class EventuallyConsistentMapAdapter<K, V> implements EventuallyConsistentMap<K, V> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean containsKey(K key) {
return false;
}
@Override
public boolean containsValue(V value) {
return false;
}
@Override
public V get(K key) {
return null;
}
@Override
public void put(K key, V value) {
}
@Override
public V remove(K key) {
return null;
}
@Override
public void remove(K key, V value) {
}
@Override
public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
return null;
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
}
@Override
public void clear() {
}
@Override
public Set<K> keySet() {
return null;
}
@Override
public Collection<V> values() {
return null;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return null;
}
@Override
public void addListener(EventuallyConsistentMapListener<K, V> listener) {
}
@Override
public void removeListener(EventuallyConsistentMapListener<K, V> listener) {
}
@Override
public void destroy() {
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.store.service;
/**
* Adapter for the storage service.
*/
public class StorageServiceAdapter implements StorageService {
@Override
public <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder() {
return null;
}
@Override
public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
return null;
}
@Override
public <E> DistributedSetBuilder<E> setBuilder() {
return null;
}
@Override
public <E> DistributedQueueBuilder<E> queueBuilder() {
return null;
}
@Override
public AtomicCounterBuilder atomicCounterBuilder() {
return null;
}
@Override
public <V> AtomicValueBuilder<V> atomicValueBuilder() {
return null;
}
@Override
public TransactionContextBuilder transactionContextBuilder() {
return null;
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.store.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import org.onlab.util.KryoNamespace;
import org.onosproject.cluster.NodeId;
import org.onosproject.store.Timestamp;
import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.*;
/**
* Testing version of an Eventually Consistent Map.
*/
public final class TestEventuallyConsistentMap<K, V> extends EventuallyConsistentMapAdapter<K, V> {
private final HashMap<K, V> map;
private final String mapName;
private final List<EventuallyConsistentMapListener<K, V>> listeners;
private final BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
private TestEventuallyConsistentMap(String mapName,
BiFunction<K, V, Collection<NodeId>> peerUpdateFunction) {
map = new HashMap<>();
listeners = new LinkedList<>();
this.mapName = mapName;
this.peerUpdateFunction = peerUpdateFunction;
}
/**
* Notify all listeners of an event.
*/
private void notifyListeners(EventuallyConsistentMapEvent<K, V> event) {
listeners.forEach(
listener -> listener.event(event)
);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(K key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(V value) {
return map.containsValue(value);
}
@Override
public V get(K key) {
return map.get(key);
}
@Override
public void put(K key, V value) {
map.put(key, value);
EventuallyConsistentMapEvent<K, V> addEvent =
new EventuallyConsistentMapEvent<>(mapName, PUT, key, value);
notifyListeners(addEvent);
peerUpdateFunction.apply(key, value);
}
@Override
public V remove(K key) {
V result = map.remove(key);
if (result != null) {
EventuallyConsistentMapEvent<K, V> removeEvent =
new EventuallyConsistentMapEvent<>(mapName, REMOVE,
key, map.get(key));
notifyListeners(removeEvent);
}
return result;
}
@Override
public void remove(K key, V value) {
boolean removed = map.remove(key, value);
if (removed) {
EventuallyConsistentMapEvent<K, V> removeEvent =
new EventuallyConsistentMapEvent<>(mapName, REMOVE, key, value);
notifyListeners(removeEvent);
}
}
@Override
public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
return map.compute(key, recomputeFunction);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
map.putAll(m);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<K> keySet() {
return map.keySet();
}
@Override
public Collection<V> values() {
return map.values();
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return map.entrySet();
}
public static <K, V> Builder<K, V> builder() {
return new Builder<>();
}
@Override
public void addListener(EventuallyConsistentMapListener<K, V> listener) {
listeners.add(listener);
}
@Override
public void removeListener(EventuallyConsistentMapListener<K, V> listener) {
listeners.remove(listener);
}
public static class Builder<K, V> implements EventuallyConsistentMapBuilder<K, V> {
private String name;
private BiFunction<K, V, Collection<NodeId>> peerUpdateFunction;
@Override
public EventuallyConsistentMapBuilder<K, V> withName(String name) {
this.name = name;
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withSerializer(KryoNamespace.Builder serializerBuilder) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V>
withTimestampProvider(BiFunction<K, V, Timestamp> timestampProvider) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withEventExecutor(ExecutorService executor) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withCommunicationExecutor(ExecutorService executor) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withBackgroundExecutor(ScheduledExecutorService executor) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V>
withPeerUpdateFunction(BiFunction<K, V, Collection<NodeId>> peerUpdateFunction) {
this.peerUpdateFunction = peerUpdateFunction;
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withTombstonesDisabled() {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withAntiEntropyPeriod(long period, TimeUnit unit) {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withFasterConvergence() {
return this;
}
@Override
public EventuallyConsistentMapBuilder<K, V> withPersistence() {
return this;
}
@Override
public EventuallyConsistentMap<K, V> build() {
if (name == null) {
name = "test";
}
return new TestEventuallyConsistentMap<>(name, peerUpdateFunction);
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.store.service;
public class TestStorageService extends StorageServiceAdapter {
@Override
public <K, V> EventuallyConsistentMapBuilder<K, V> eventuallyConsistentMapBuilder() {
return TestEventuallyConsistentMap.builder();
}
@Override
public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
throw new UnsupportedOperationException("consistentMapBuilder");
}
@Override
public <E> DistributedSetBuilder<E> setBuilder() {
throw new UnsupportedOperationException("setBuilder");
}
@Override
public <E> DistributedQueueBuilder<E> queueBuilder() {
throw new UnsupportedOperationException("queueBuilder");
}
@Override
public AtomicCounterBuilder atomicCounterBuilder() {
throw new UnsupportedOperationException("atomicCounterBuilder");
}
@Override
public <V> AtomicValueBuilder<V> atomicValueBuilder() {
throw new UnsupportedOperationException("atomicValueBuilder");
}
@Override
public TransactionContextBuilder transactionContextBuilder() {
throw new UnsupportedOperationException("transactionContextBuilder");
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.onosproject.store.intent.impl;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onosproject.cluster.ClusterServiceAdapter;
import org.onosproject.core.IdGenerator;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentData;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.IntentTestsMocks;
import org.onosproject.net.intent.MockIdGenerator;
import org.onosproject.net.intent.PartitionServiceAdapter;
import org.onosproject.store.service.TestStorageService;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.onosproject.net.NetTestTools.APP_ID;
import static org.onosproject.net.NetTestTools.hid;
/**
* Gossip Intent Store test using database adapter.
*/
public class GossipIntentStoreTest {
private GossipIntentStore intentStore;
private IdGenerator idGenerator;
private HostToHostIntent.Builder builder1;
@Before
public void setUp() {
intentStore = new GossipIntentStore();
intentStore.storageService = new TestStorageService();
intentStore.partitionService = new PartitionServiceAdapter();
intentStore.clusterService = new ClusterServiceAdapter();
idGenerator = new MockIdGenerator();
Intent.bindIdGenerator(idGenerator);
builder1 = HostToHostIntent
.builder()
.one(hid("12:34:56:78:91:ab/1"))
.two(hid("12:34:56:78:91:ac/1"))
.appId(APP_ID);
intentStore.activate();
}
@After
public void cleanUp() {
intentStore.deactivate();
Intent.unbindIdGenerator(idGenerator);
}
/**
* Generates a list of test intent data.
*
* @param count how many intent data objects are needed
* @return list of intent data
*/
private List<IntentData> generateIntentList(int count) {
LinkedList<IntentData> intents = new LinkedList<>();
IntStream.rangeClosed(1, count)
.forEach(i ->
intents.add(
new IntentData(
builder1
.priority(i)
.build(),
IntentState.INSTALLED,
new IntentTestsMocks.MockTimestamp(12))));
return intents;
}
/**
* Tests the intent count APIs.
*/
@Test
public void testGetIntentCount() {
assertThat(intentStore.getIntentCount(), is(0L));
generateIntentList(5).forEach(intentStore::write);
assertThat(intentStore.getIntentCount(), is(5L));
}
/**
* Tests the batch add API.
*/
@Test
public void testBatchAdd() {
assertThat(intentStore.getIntentCount(), is(0L));
List<IntentData> intents = generateIntentList(5);
intentStore.batchWrite(intents);
assertThat(intentStore.getIntentCount(), is(5L));
}
/**
* Tests adding and withdrawing an Intent.
*/
@Test
public void testAddAndWithdrawIntent() {
// build and install one intent
Intent intent = builder1.build();
IntentData installed = new IntentData(
intent,
IntentState.INSTALLED,
new IntentTestsMocks.MockTimestamp(12));
intentStore.write(installed);
// check that the intent count includes the new one
assertThat(intentStore.getIntentCount(), is(1L));
// check that the getIntents() API returns the new intent
intentStore.getIntents()
.forEach(item -> assertThat(item, is(intent)));
// check that the getInstallableIntents() API returns the new intent
intentStore.getInstallableIntents(intent.key())
.forEach(item -> assertThat(item, is(intent)));
// check that the getIntent() API can find the new intent
Intent queried = intentStore.getIntent(intent.key());
assertThat(queried, is(intent));
// check that the state of the new intent is correct
IntentState state = intentStore.getIntentState(intent.key());
assertThat(state, is(IntentState.INSTALLED));
// check that the getIntentData() API returns the proper value for the
// new intent
IntentData dataByQuery = intentStore.getIntentData(intent.key());
assertThat(dataByQuery, is(installed));
// check that the getIntentData() API returns the new intent when given
// a time stamp to look for
Iterable<IntentData> dataIteratorByTime = intentStore.getIntentData(true, 10L);
assertThat(dataIteratorByTime.iterator().hasNext(), is(true));
dataIteratorByTime.forEach(
data -> assertThat(data, is(installed))
);
// check that the getIntentData() API returns the new intent when asked to
// find all intents
Iterable<IntentData> dataIteratorAll = intentStore.getIntentData(false, 0L);
assertThat(dataIteratorAll.iterator().hasNext(), is(true));
dataIteratorAll.forEach(
data -> assertThat(data, is(installed))
);
// now purge the intent that was created
IntentData purge = new IntentData(
intent,
IntentState.PURGE_REQ,
new IntentTestsMocks.MockTimestamp(12));
intentStore.write(purge);
// check that no intents are left
assertThat(intentStore.getIntentCount(), is(0L));
// check that a getIntent() operation on the key of the purged intent
// returns null
Intent queriedAfterWithdrawal = intentStore.getIntent(intent.key());
assertThat(queriedAfterWithdrawal, nullValue());
}
/**
* Tests the operation of the APIs for the pending map.
*/
@Test
public void testPending() {
// crete a new intent and add it as pending
Intent intent = builder1.build();
IntentData installed = new IntentData(
intent,
IntentState.INSTALLED,
new IntentTestsMocks.MockTimestamp(11));
intentStore.addPending(installed);
// check that the getPending() API returns the new pending intent
Iterable<Intent> pendingIntentIteratorAll = intentStore.getPending();
assertThat(pendingIntentIteratorAll.iterator().hasNext(), is(true));
pendingIntentIteratorAll.forEach(
data -> assertThat(data, is(intent))
);
// check that the getPendingData() API returns the IntentData for the
// new pending intent
Iterable<IntentData> pendingDataIteratorAll = intentStore.getPendingData();
assertThat(pendingDataIteratorAll.iterator().hasNext(), is(true));
pendingDataIteratorAll.forEach(
data -> assertThat(data, is(installed))
);
// check that the new pending intent is returned by the getPendingData()
// API when a time stamp is provided
Iterable<IntentData> pendingDataIteratorSelected =
intentStore.getPendingData(true, 10L);
assertThat(pendingDataIteratorSelected.iterator().hasNext(), is(true));
pendingDataIteratorSelected.forEach(
data -> assertThat(data, is(installed))
);
// check that the new pending intent is returned by the getPendingData()
// API when a time stamp is provided
Iterable<IntentData> pendingDataIteratorAllFromTimestamp =
intentStore.getPendingData(false, 0L);
assertThat(pendingDataIteratorAllFromTimestamp.iterator().hasNext(), is(true));
pendingDataIteratorSelected.forEach(
data -> assertThat(data, is(installed))
);
}
}