initial code for the mac-learning app
Change-Id: I04f3b17c453e502f20477e83cb7cdc796b4160ae
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
new file mode 100644
index 0000000..1e05637
--- /dev/null
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.opencord.maclearner.app.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Set of tests of the Mac Learner ONOS application component.
+ */
+public class MacLearnerManagerTest extends TestBaseMacLearner {
+
+ @Before
+ public void setUp() {
+ setUpApp();
+ }
+
+ @After
+ public void tearDown() {
+ this.macLearnerManager.deactivate();
+ }
+
+ private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
+ private static final VlanId CLIENT_QINQ_VLAN = VlanId.vlanId("200");
+ private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
+
+ @Test
+ public void testSingleTagDhcpPacket() {
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ VlanId.NONE,
+ CLIENT_CP));
+ Optional<MacAddress> macAddress = macLearnerManager.getMacMapping(CLIENT_CP.deviceId(),
+ CLIENT_CP.port(), CLIENT_VLAN);
+ assertTrue(macAddress.isPresent());
+ assertEquals(CLIENT_MAC, macAddress.get());
+ }
+
+ @Test
+ public void testDoubleTagDhcpPacket() {
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_QINQ_VLAN,
+ CLIENT_CP));
+ Optional<MacAddress> macAddress = macLearnerManager.getMacMapping(CLIENT_CP.deviceId(),
+ CLIENT_CP.port(), CLIENT_QINQ_VLAN);
+ assertTrue(macAddress.isPresent());
+ assertEquals(CLIENT_MAC, macAddress.get());
+ }
+
+}
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
new file mode 100644
index 0000000..c9d4af5
--- /dev/null
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.opencord.maclearner.app.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketContextAdapter;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.store.service.AsyncConsistentMap;
+import org.onosproject.store.service.AsyncDistributedSet;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapAdapter;
+import org.onosproject.store.service.ConsistentMapBuilder;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.DistributedSetAdapter;
+import org.onosproject.store.service.DistributedSetBuilder;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.StorageServiceAdapter;
+import org.onosproject.store.service.Versioned;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Mac Learner mock services class.
+ */
+public abstract class TestBaseMacLearner {
+
+ private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
+ private static final Ip4Address INTERFACE_IP = Ip4Address.valueOf("10.0.3.254");
+
+ protected MacLearnerManager macLearnerManager;
+ protected ObjectMapper mapper;
+ protected ApplicationId subject;
+
+ protected ComponentConfigService componentConfigService = new MockComponentConfigService();
+ protected MockCoreService coreService = new MockCoreService();
+ protected MockStorageService storageService = new MockStorageService();
+ protected MockPacketService packetService = new MockPacketService();
+
+ public void setUpApp() {
+ macLearnerManager = new MacLearnerManager();
+ macLearnerManager.componentConfigService = this.componentConfigService;
+ macLearnerManager.coreService = this.coreService;
+ macLearnerManager.storageService = this.storageService;
+ macLearnerManager.packetService = this.packetService;
+ injectEventDispatcher(macLearnerManager, new MockEventDispatcher());
+ mapper = new ObjectMapper();
+ subject = macLearnerManager.coreService.registerApplication("org.opencord.maclearner");
+
+ macLearnerManager.activate();
+ }
+
+ /**
+ * Mocks an instance of {@link ApplicationId} so that the application
+ * component under test can query and use its application ID.
+ */
+ private static final class MockApplicationId implements ApplicationId {
+
+ private final short id;
+ private final String name;
+
+ public MockApplicationId(short id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ @Override
+ public short id() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+ }
+
+ private static final class MockComponentConfigService extends ComponentConfigAdapter {
+
+ }
+
+ /**
+ * Mocks the core services of ONOS so that the application under test can
+ * register and query application IDs.
+ */
+ private static final class MockCoreService extends CoreServiceAdapter {
+
+ private List<ApplicationId> idList = Lists.newArrayList();
+ private Map<String, ApplicationId> idMap = Maps.newHashMap();
+
+ @Override
+ public ApplicationId getAppId(Short id) {
+ if (id >= idList.size()) {
+ return null;
+ }
+ return idList.get(id);
+ }
+
+ @Override
+ public ApplicationId getAppId(String name) {
+ return idMap.get(name);
+ }
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ ApplicationId appId = idMap.get(name);
+ if (appId == null) {
+ appId = new MockApplicationId((short) idList.size(), name);
+ idList.add(appId);
+ idMap.put(name, appId);
+ }
+ return appId;
+ }
+
+ }
+
+ /**
+ * Mocks the storage service of ONOS so that the application under test can
+ * use consistent maps.
+ */
+ private static class MockStorageService extends StorageServiceAdapter {
+
+ @Override
+ public <K, V> ConsistentMapBuilder<K, V> consistentMapBuilder() {
+ ConsistentMapBuilder<K, V> builder = new ConsistentMapBuilder<K, V>() {
+ @Override
+ public AsyncConsistentMap<K, V> buildAsyncMap() {
+ return null;
+ }
+
+ @Override
+ public ConsistentMap<K, V> build() {
+ return new TestConsistentMap<>();
+ }
+ };
+
+ return builder;
+ }
+
+ @Override
+ public <E> DistributedSetBuilder<E> setBuilder() {
+ DistributedSetBuilder<E> builder = new DistributedSetBuilder<E>() {
+ @Override
+ public AsyncDistributedSet<E> build() {
+ return new DistributedSetAdapter<E>() {
+ @Override
+ public DistributedSet<E> asDistributedSet() {
+ return new TestDistributedSet<>();
+ }
+ };
+ }
+ };
+
+ return builder;
+ }
+
+ @Override
+ public AtomicCounter getAtomicCounter(String name) {
+ return new MockAtomicCounter();
+ }
+
+ // Mock ConsistentMap that behaves as a HashMap
+ class TestConsistentMap<K, V> extends ConsistentMapAdapter<K, V> {
+ private Map<K, Versioned<V>> map = new HashMap<>();
+ private Map<MapEventListener<K, V>, Executor> listeners = new HashMap<>();
+
+ public void notifyListeners(MapEvent<K, V> event) {
+ listeners.forEach((c, e) -> e.execute(() -> c.event(event)));
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public Versioned<V> put(K key, V value) {
+ Versioned<V> oldValue = map.get(key);
+ Versioned<V> newValue = new Versioned<>(value, oldValue == null ? 0 : oldValue.version() + 1);
+ map.put(key, newValue);
+ notifyListeners(new MapEvent<>(name(), key, newValue, oldValue));
+ return newValue;
+ }
+
+ @Override
+ public Versioned<V> get(K key) {
+ return map.get(key);
+ }
+
+ @Override
+ public Versioned<V> remove(K key) {
+ Versioned<V> oldValue = map.remove(key);
+ notifyListeners(new MapEvent<>(name(), key, oldValue, null));
+ return oldValue;
+ }
+
+ @Override
+ public Versioned<V> computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Versioned<V> oldValue = map.get(key);
+ Versioned<V> newValue = new Versioned<>(remappingFunction.apply(key, oldValue.value()),
+ oldValue == null ? 0 : oldValue.version() + 1);
+ map.put(key, newValue);
+ notifyListeners(new MapEvent<>(name(), key, newValue, oldValue));
+ return newValue;
+ }
+
+ @Override
+ public Set<Map.Entry<K, Versioned<V>>> entrySet() {
+ return map.entrySet();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<Versioned<V>> values() {
+ return map.values();
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public void addListener(MapEventListener<K, V> listener, Executor executor) {
+ listeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeListener(MapEventListener<K, V> listener) {
+ listeners.remove(listener);
+ }
+ }
+
+ // Mock DistributedSet that behaves as a HashSet
+ class TestDistributedSet<E> extends HashSet<E> implements DistributedSet<E> {
+
+ @Override
+ public void addListener(SetEventListener<E> listener) {
+ }
+
+ @Override
+ public void removeListener(SetEventListener<E> listener) {
+ }
+
+ @Override
+ public String name() {
+ return null;
+ }
+
+ @Override
+ public Type primitiveType() {
+ return null;
+ }
+ }
+ }
+
+ private static class MockAtomicCounter implements AtomicCounter {
+ long id = 0;
+
+ @Override
+ public long incrementAndGet() {
+ return ++id;
+ }
+
+ @Override
+ public long getAndIncrement() {
+ return id++;
+ }
+
+ @Override
+ public long getAndAdd(long delta) {
+ long oldId = id;
+ id += delta;
+ return oldId;
+ }
+
+ @Override
+ public long addAndGet(long delta) {
+ id += delta;
+ return id;
+ }
+
+ @Override
+ public void set(long value) {
+ id = value;
+ }
+
+ @Override
+ public boolean compareAndSet(long expectedValue, long updateValue) {
+ if (id == expectedValue) {
+ id = updateValue;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public long get() {
+ return id;
+ }
+
+ @Override
+ public String name() {
+ return "MockAtomicCounter";
+ }
+ }
+
+ /**
+ * Mocks the packet service of ONOS so that the application under test can
+ * observe network packets.
+ */
+ public static class MockPacketService extends PacketServiceAdapter {
+ Set<PacketProcessor> packetProcessors = Sets.newHashSet();
+ OutboundPacket emittedPacket;
+
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ packetProcessors.add(processor);
+ }
+
+ public void processPacket(PacketContext packetContext) {
+ packetProcessors.forEach(p -> p.process(packetContext));
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ this.emittedPacket = packet;
+ }
+ }
+
+ /**
+ * Implements event delivery system that delivers events synchronously, or
+ * in-line with the post method invocation.
+ */
+ public static class MockEventDispatcher extends DefaultEventSinkRegistry
+ implements EventDeliveryService {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public synchronized void post(Event event) {
+ EventSink sink = getSink(event.getClass());
+ checkState(sink != null, "No sink for event %s", event);
+ sink.process(event);
+ }
+
+ @Override
+ public void setDispatchTimeLimit(long millis) {
+ }
+
+ @Override
+ public long getDispatchTimeLimit() {
+ return 0;
+ }
+ }
+
+ public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+ Class mc = manager.getClass();
+ Field[] var3 = mc.getSuperclass().getDeclaredFields();
+
+ for (Field f : var3) {
+ if (f.getType().equals(EventDeliveryService.class)) {
+ try {
+ TestUtils.setField(manager, f.getName(), svc);
+ break;
+ } catch (TestUtils.TestUtilsException var8) {
+ throw new IllegalArgumentException("Unable to inject reference", var8);
+ }
+ }
+ }
+ }
+
+ /**
+ * Generates DHCP REQUEST packet.
+ */
+ protected static class TestDhcpRequestPacketContext extends PacketContextAdapter {
+
+ private InboundPacket inPacket;
+
+ public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId,
+ VlanId qinqQVid,
+ ConnectPoint clientCp) {
+ super(0, null, null, false);
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(SERVER_IP.toInt());
+ ipv4.setSourceAddress(INTERFACE_IP.toInt());
+
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setQinQVID(qinqQVid.toShort())
+ .setSourceMACAddress(clientMac)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+
+ this.inPacket = new DefaultInboundPacket(clientCp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+}