Merge "JUnit Test - MappingSystemTest"
authorLori Jakab <lorand.jakab@gmail.com>
Thu, 19 May 2016 18:02:47 +0000 (18:02 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 19 May 2016 18:02:47 +0000 (18:02 +0000)
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/LispMappingService.java
mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/LispSouthboundPlugin.java
mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/LispSouthboundHandler.java
mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterCache.java [new file with mode: 0644]
mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterPartialDeserializer.java [new file with mode: 0644]
mappingservice/southbound/src/site/markdown/map-register_cache.md
mappingservice/southbound/src/test/java/org/opendaylight/lispflowmapping/southbound/lisp/LispSouthboundServiceTest.java
mappingservice/southbound/src/test/java/org/opendaylight/lispflowmapping/southbound/lisp/MapRegisterCacheTestUtil.java [new file with mode: 0644]

index 643279ff813d3234e8ba1c0f01df987779ccde99..b870dc43ca9cf7e2e2b275c15aec03ac717dcd6e 100644 (file)
@@ -40,6 +40,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.Od
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.RequestMapping;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.XtrReplyMapping;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.XtrRequestMapping;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadata;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.map.register.cache.metadata.EidLispAddress;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapnotifymessage.MapNotifyBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapreplymessage.MapReplyBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.maprequestmessage.MapRequestBuilder;
@@ -51,6 +54,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.sb.rev150904.SendM
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.sb.rev150904.SendMapReplyInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.sb.rev150904.SendMapRequestInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingOrigin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -240,9 +244,14 @@ public class LispMappingService implements IFlowMapping, BindingAwareProvider, I
 
     @Override
     public void onMappingKeepAlive(MappingKeepAlive notification) {
-        LOG.debug("Received MappingKeepAlive notification, ignoring");
-        // TODO This notification means that SB received a Map-Register which was cached. Will need to update
-        // timestamps in stored mappings
+        final MapRegisterCacheMetadata cacheMetadata = notification.getMapRegisterCacheMetadata();
+        for (EidLispAddress eidLispAddress : cacheMetadata.getEidLispAddress()) {
+            final Eid eid = eidLispAddress.getEid();
+            final Long timestamp = cacheMetadata.getTimestamp();
+            LOG.debug("Update map registration for eid {} with timestamp {}", LispAddressStringifier.getString(eid),
+                    timestamp);
+            mapService.updateMappingRegistration(MappingOrigin.Southbound, eid, timestamp);
+        }
     }
 
     private OdlLispSbService getLispSB() {
index c89cccbfd9cb8555f8456e9729039535dc80fb05..1818f898f21b68bc42b1dc9eed1627a83ae3d65c 100644 (file)
@@ -160,11 +160,16 @@ public class LispSouthboundPlugin implements IConfigLispSouthboundPlugin, AutoCl
     public void handleSerializedLispBuffer(TransportAddress address, ByteBuffer outBuffer,
             final MessageType packetType) {
         InetAddress ip = getInetAddress(address);
-        InetSocketAddress recipient = new InetSocketAddress(ip, address.getPort().getValue());
-        // the wrappedBuffer() method doesn't copy data, so this conversion shouldn't hurt performance
-        ByteBuf data = wrappedBuffer(outBuffer.array());
+        handleSerializedLispBuffer(ip, outBuffer, packetType, address.getPort().getValue());
+    }
+
+    public void handleSerializedLispBuffer(InetAddress address, ByteBuffer outBuffer,
+            final MessageType packetType, final int portNumber) {
+        InetSocketAddress recipient = new InetSocketAddress(address, portNumber);
+        outBuffer.position(0);
+        ByteBuf data = wrappedBuffer(outBuffer);
         DatagramPacket packet = new DatagramPacket(data, recipient);
-        LOG.debug("Sending {} on port {} to address: {}", packetType, address.getPort().getValue(), ip);
+        LOG.debug("Sending {} on port {} to address: {}", packetType, portNumber, address);
         if (LOG.isTraceEnabled()) {
             LOG.trace("Buffer:\n{}", ByteBufUtil.prettyHexDump(data));
         }
index b1728c1b8cc56314f558b407c53331f93fbd5594..347a835ddb7d79d17c4210a14d00111836bb2dbe 100644 (file)
@@ -13,51 +13,82 @@ import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.channel.socket.DatagramPacket;
-
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
-import org.opendaylight.lispflowmapping.southbound.LispSouthboundPlugin;
-import org.opendaylight.lispflowmapping.southbound.LispSouthboundStats;
-import org.opendaylight.lispflowmapping.southbound.util.LispNotificationHelper;
-import org.opendaylight.lispflowmapping.lisp.type.LispMessage;
-import org.opendaylight.lispflowmapping.lisp.util.ByteUtil;
-import org.opendaylight.lispflowmapping.lisp.util.MapRequestUtil;
 import org.opendaylight.lispflowmapping.lisp.serializer.MapNotifySerializer;
 import org.opendaylight.lispflowmapping.lisp.serializer.MapRegisterSerializer;
 import org.opendaylight.lispflowmapping.lisp.serializer.MapReplySerializer;
 import org.opendaylight.lispflowmapping.lisp.serializer.MapRequestSerializer;
+import org.opendaylight.lispflowmapping.lisp.type.LispMessage;
+import org.opendaylight.lispflowmapping.lisp.util.ByteUtil;
+import org.opendaylight.lispflowmapping.lisp.util.LispAddressStringifier;
+import org.opendaylight.lispflowmapping.lisp.util.MapRequestUtil;
+import org.opendaylight.lispflowmapping.southbound.LispSouthboundPlugin;
+import org.opendaylight.lispflowmapping.southbound.LispSouthboundStats;
+import org.opendaylight.lispflowmapping.southbound.lisp.cache.MapRegisterCache;
+import org.opendaylight.lispflowmapping.southbound.lisp.cache.MapRegisterPartialDeserializer;
 import org.opendaylight.lispflowmapping.southbound.lisp.exception.LispMalformedPacketException;
 import org.opendaylight.lispflowmapping.southbound.lisp.network.PacketHeader;
+import org.opendaylight.lispflowmapping.southbound.util.LispNotificationHelper;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.AddMappingBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.GotMapNotifyBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.GotMapReplyBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MessageType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapNotify;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapRegister;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapRequest;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapReply;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapRequest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MappingKeepAlive;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MappingKeepAliveBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MessageType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.RequestMappingBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadata;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadataBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.map.register.cache.metadata.EidLispAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.map.register.cache.metadata.EidLispAddressBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValue;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValueBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.list.MappingRecordItem;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.transport.address.TransportAddressBuilder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
+import org.opendaylight.yangtools.yang.binding.Notification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @ChannelHandler.Sharable
 public class LispSouthboundHandler extends SimpleChannelInboundHandler<DatagramPacket>
         implements ILispSouthboundService {
+    private final MapRegisterCache mapRegisterCache;
+
+    /**
+     * How long is record supposed to be relevant. After this time record isn't valid.
+     *
+     * If you modify this value, please update the LispSouthboundServiceTest class too.
+     */
+    private static final long CACHE_RECORD_TIMEOUT = 90000;
+
     private NotificationPublishService notificationPublishService;
     protected static final Logger LOG = LoggerFactory.getLogger(LispSouthboundHandler.class);
 
     private final LispSouthboundPlugin lispSbPlugin;
     private LispSouthboundStats lispSbStats = null;
 
-    public LispSouthboundHandler(LispSouthboundPlugin lispSbPlugin) {
+    public LispSouthboundHandler(LispSouthboundPlugin lispSbPlugin, final MapRegisterCache mapRegisterCache) {
         this.lispSbPlugin = lispSbPlugin;
         if (lispSbPlugin != null) {
             this.lispSbStats = lispSbPlugin.getStats();
         }
+        this.mapRegisterCache = mapRegisterCache;
+    }
+
+    public LispSouthboundHandler(LispSouthboundPlugin lispSbPlugin) {
+        this(lispSbPlugin, new MapRegisterCache());
     }
 
     public void setNotificationProvider(NotificationPublishService nps) {
@@ -151,19 +182,45 @@ public class LispSouthboundHandler extends SimpleChannelInboundHandler<DatagramP
 
     private void handleMapRegister(ByteBuffer inBuffer, InetAddress sourceAddress, int port) {
         try {
-            MapRegister mapRegister = MapRegisterSerializer.getInstance().deserialize(inBuffer, sourceAddress);
-            AddMappingBuilder addMappingBuilder = new AddMappingBuilder();
-            addMappingBuilder.setMapRegister(LispNotificationHelper.convertMapRegister(mapRegister));
-            TransportAddressBuilder transportAddressBuilder = new TransportAddressBuilder();
-            transportAddressBuilder.setIpAddress(LispNotificationHelper
-                    .getIpAddressBinaryFromInetAddress(sourceAddress));
-            transportAddressBuilder.setPort(new PortNumber(port));
-            addMappingBuilder.setTransportAddress(transportAddressBuilder.build());
-            if (notificationPublishService != null) {
-                notificationPublishService.putNotification(addMappingBuilder.build());
-                LOG.trace("MapRegister was published!");
+            final Map.Entry<MapRegisterCacheKey, byte[]> artificialEntry = MapRegisterPartialDeserializer
+                    .deserializePartially(inBuffer, sourceAddress);
+            final MapRegisterCacheKey cacheKey = artificialEntry == null ? null : artificialEntry.getKey();
+
+            final MapRegisterCacheValue cacheValue = resolveCacheValue(artificialEntry);
+            if (cacheValue != null) {
+                final MapRegisterCacheMetadata mapRegisterValue = cacheValue.getMapRegisterCacheMetadata();
+                LOG.debug("Map register message site-ID: {} xTR-ID: {} from cache.", mapRegisterValue.getSiteId(),
+                        mapRegisterValue.getXtrId());
+                mapRegisterCache.refreshEntry(cacheKey);
+                sendNotificationIfPossible(createMappingKeepAlive(cacheValue));
+                if (mapRegisterValue.isWantMapNotify()) {
+                    sendMapNotifyMsg(inBuffer, sourceAddress, port);
+                }
             } else {
-                LOG.warn("Notification Provider is null!");
+                MapRegister mapRegister = MapRegisterSerializer.getInstance().deserialize(inBuffer, sourceAddress);
+                AddMappingBuilder addMappingBuilder = new AddMappingBuilder();
+                addMappingBuilder.setMapRegister(LispNotificationHelper.convertMapRegister(mapRegister));
+                TransportAddressBuilder transportAddressBuilder = new TransportAddressBuilder();
+                transportAddressBuilder.setIpAddress(LispNotificationHelper.getIpAddressBinaryFromInetAddress(
+                        sourceAddress));
+                transportAddressBuilder.setPort(new PortNumber(port));
+                addMappingBuilder.setTransportAddress(transportAddressBuilder.build());
+                sendNotificationIfPossible(addMappingBuilder.build());
+                if (artificialEntry != null) {
+                    final MapRegisterCacheMetadataBuilder cacheMetadataBldNew = new MapRegisterCacheMetadataBuilder();
+                    cacheMetadataBldNew.setEidLispAddress(provideEidPrefixesFromMessage(mapRegister));
+                    cacheMetadataBldNew.setXtrId(mapRegister.getXtrId());
+                    cacheMetadataBldNew.setSiteId(mapRegister.getSiteId());
+                    cacheMetadataBldNew.setWantMapNotify(mapRegister.isWantMapNotify());
+                    cacheMetadataBldNew.setMergeEnabled(mapRegister.isMergeEnabled());
+                    cacheMetadataBldNew.setTimestamp(System.currentTimeMillis());
+
+                    final MapRegisterCacheValueBuilder cacheValueBldNew = new MapRegisterCacheValueBuilder();
+                    cacheValueBldNew.setPacketData(artificialEntry.getValue());
+                    cacheValueBldNew.setMapRegisterCacheMetadata(cacheMetadataBldNew.build());
+
+                    mapRegisterCache.addEntry(cacheKey, cacheValueBldNew.build());
+                }
             }
         } catch (RuntimeException re) {
             throw new LispMalformedPacketException("Couldn't deserialize Map-Register (len="
@@ -173,6 +230,64 @@ public class LispSouthboundHandler extends SimpleChannelInboundHandler<DatagramP
         }
     }
 
+    private MapRegisterCacheValue resolveCacheValue(Map.Entry<MapRegisterCacheKey, byte[]> entry) {
+        if (entry != null) {
+            final MapRegisterCacheValue mapRegisterCacheValue = mapRegisterCache.getEntry(entry.getKey());
+            if (mapRegisterCacheValue != null) {
+                final long creationTime = mapRegisterCacheValue.getMapRegisterCacheMetadata().getTimestamp();
+                final long currentTime = System.currentTimeMillis();
+                if (currentTime - creationTime > CACHE_RECORD_TIMEOUT) {
+                    mapRegisterCache.removeEntry(entry.getKey());
+                    return null;
+                } else if (Arrays.equals(mapRegisterCacheValue.getPacketData(), entry.getValue())) {
+                    return mapRegisterCacheValue;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void sendNotificationIfPossible(final Notification notification) throws InterruptedException {
+        if (notificationPublishService != null) {
+            notificationPublishService.putNotification(notification);
+            LOG.trace("{} was published.", notification.getClass());
+        } else {
+            LOG.warn("Notification Provider is null!");
+        }
+    }
+
+    private MappingKeepAlive createMappingKeepAlive(final MapRegisterCacheValue value) {
+        MappingKeepAliveBuilder mappingKeepAliveBuilder = new MappingKeepAliveBuilder();
+        mappingKeepAliveBuilder.setMapRegisterCacheMetadata(value.getMapRegisterCacheMetadata());
+        return mappingKeepAliveBuilder.build();
+    }
+
+    private void sendMapNotifyMsg(final ByteBuffer inBuffer, final InetAddress inetAddress, int portNumber) {
+        ByteBuffer outBuffer = transformMapRegisterToMapNotify(inBuffer);
+        outBuffer.position(0);
+        lispSbPlugin.handleSerializedLispBuffer(inetAddress, outBuffer, MessageType.MapNotify, portNumber);
+    }
+
+    private ByteBuffer transformMapRegisterToMapNotify(final ByteBuffer buffer) {
+        buffer.position(0);
+        //TODO: also reset of authentication data is required. other trello card is opened for this task.
+        byte[] byteReplacement = new byte[] {0x04, 0x00, 0x00};
+        buffer.put(byteReplacement);
+        return buffer;
+    }
+
+    private List<EidLispAddress> provideEidPrefixesFromMessage(final MapRegister mapRegister) {
+        List<EidLispAddress> eidsResult = new ArrayList<>();
+        for (MappingRecordItem mappingRecordItem : mapRegister.getMappingRecordItem()) {
+            final EidLispAddressBuilder eidLispAddressBuilder = new EidLispAddressBuilder();
+            final Eid eid = mappingRecordItem.getMappingRecord().getEid();
+            eidLispAddressBuilder.setEidLispAddressId(LispAddressStringifier.getString(eid));
+            eidLispAddressBuilder.setEid(eid);
+            eidsResult.add(eidLispAddressBuilder.build());
+        }
+        return eidsResult;
+    }
+
     private void handleMapNotify(ByteBuffer inBuffer, InetAddress sourceAddress, int port) {
         try {
             MapNotify mapNotify = MapNotifySerializer.getInstance().deserialize(inBuffer);
diff --git a/mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterCache.java b/mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterCache.java
new file mode 100644 (file)
index 0000000..2e7cc09
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.lispflowmapping.southbound.lisp.cache;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadata;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadataBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValue;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValueBuilder;
+
+public class MapRegisterCache {
+
+    protected final Map<MapRegisterCacheKey, MapRegisterCacheValue> cache;
+
+    public MapRegisterCache() {
+        cache = new ConcurrentHashMap<>();
+    }
+
+    public void addEntry(final MapRegisterCacheKey mapRegisterCacheKey, final MapRegisterCacheValue
+            mapRegisterCacheValue) {
+        cache.put(mapRegisterCacheKey, mapRegisterCacheValue);
+    }
+
+    public void addEntry(final Map.Entry<MapRegisterCacheKey, MapRegisterCacheValue>
+                                 mapRegisterCacheEntry) {
+        cache.put(mapRegisterCacheEntry.getKey(), mapRegisterCacheEntry.getValue());
+    }
+
+    public MapRegisterCacheValue getEntry(final MapRegisterCacheKey mapRegisterCacheKey) {
+        if (mapRegisterCacheKey != null) {
+            return cache.get(mapRegisterCacheKey);
+        }
+        return null;
+    }
+
+    public void removeEntry(final MapRegisterCacheKey mapRegisterCacheKey) {
+        if (mapRegisterCacheKey != null) {
+            cache.remove(mapRegisterCacheKey);
+        }
+    }
+
+    public void refreshEntry(final MapRegisterCacheKey mapRegisterCacheKey) {
+        final MapRegisterCacheValue mapRegisterCacheValueOld = cache.get(mapRegisterCacheKey);
+        final MapRegisterCacheMetadata mapRegisterCacheMetadataOld = mapRegisterCacheValueOld
+                .getMapRegisterCacheMetadata();
+
+        final MapRegisterCacheMetadataBuilder mapRegisterCacheMetadataBuilderNew = new MapRegisterCacheMetadataBuilder
+                (mapRegisterCacheMetadataOld);
+        mapRegisterCacheMetadataBuilderNew.setTimestamp(System.currentTimeMillis());
+
+        final MapRegisterCacheValueBuilder mapRegisterCacheValueBuilderNew = new MapRegisterCacheValueBuilder();
+        mapRegisterCacheValueBuilderNew.setPacketData(mapRegisterCacheValueOld.getPacketData());
+        mapRegisterCacheValueBuilderNew.setMapRegisterCacheMetadata(mapRegisterCacheMetadataBuilderNew.build());
+
+        cache.put(mapRegisterCacheKey, mapRegisterCacheValueBuilderNew.build());
+    }
+
+    public int cacheSize() {
+        return cache.size();
+    }
+}
diff --git a/mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterPartialDeserializer.java b/mappingservice/southbound/src/main/java/org/opendaylight/lispflowmapping/southbound/lisp/cache/MapRegisterPartialDeserializer.java
new file mode 100644 (file)
index 0000000..ba3e630
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.lispflowmapping.southbound.lisp.cache;
+
+import com.google.common.base.Optional;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import org.opendaylight.lispflowmapping.lisp.serializer.MapRegisterSerializer;
+import org.opendaylight.lispflowmapping.lisp.util.ByteUtil;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.afn.safi.rev130704.AddressFamily;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKeyBuilder;
+
+public final class MapRegisterPartialDeserializer {
+
+    /**
+     * Consists of:
+     * - 4bits Type,
+     * - 1bit  P,
+     * - 1bit  Reserved,
+     * - 1bit  I,
+     * - 1bit  R,
+     * - 15bit Reserved,
+     * - 1bit  M,
+     * - 8bits RecordCount
+     */
+    private static final int PAYLOAD1_LEN = 4;
+
+    /**
+     * Consists of:
+     * - 64bits  Nonce
+     */
+    private static final int SKIP1_LEN = 8;
+
+    /**
+     * Consists of:
+     * - 16bits - Key ID
+     */
+    private static final int PAYLOAD2_LEN = 2;
+
+    private static final int NUM_OF_BYTES_FROM_START_TO_AUTH_DATA_LENGTH_POS = PAYLOAD1_LEN + SKIP1_LEN + PAYLOAD2_LEN;
+    private static final int NUM_OF_BYTES_AUTHENTICATION_DATA_LENGTH = 2;
+
+    /**
+     * Consists of:
+     * - 32bits  Record TTL
+     * -  8bits  Locator Count
+     * -  8bits  EID mask-len
+     * -  3bits  ACT
+     * -  1bit   A
+     * - 12bits  Reserved
+     * -  4bits  Reserved
+     * - 12bits  Map-Version Number
+     */
+    private static final int NUM_OF_BYTES_FROM_AUTH_DATA_TO_EID_PREFIX_AFI = 10;
+    private static final int NUM_OF_BYTES_EID_PREFIX_AFI = 2;
+    private static final int NUM_OF_BYTES_IPV4 = 4;
+    private static final int NUM_OF_BYTES_IPV6 = 16;
+    private static final int NUM_OF_BYTES_MAC48 = 6;
+    private static final byte XTR_SITE_ID = 0x02;
+    private static final byte MERGE_ENABLED_BIT_POSITION = 0x04;
+    private static final int NUM_OF_BYTES_FROM_EID_PREFIX_AFI_TO_LCAF_LENGTH = 6;
+    private static final int NUM_OF_BYTES_FROM_LCAF_RSVD1_TO_LCAF_LENGTH = 6;
+
+
+    private MapRegisterPartialDeserializer() {
+        throw new UnsupportedOperationException("Trying to instantiate util class.");
+    }
+
+    public static Map.Entry<MapRegisterCacheKey, byte[]> deserializePartially(final ByteBuffer buffer,
+                                                                                      final InetAddress sourceRloc) {
+
+        final byte thirdByte = buffer.get(2);
+        //if merge bit is set to 1, not store to cache.
+        final boolean isMergeBitSet = ByteUtil.extractBit(thirdByte, MERGE_ENABLED_BIT_POSITION);
+        if (isMergeBitSet) {
+            return null;
+        }
+
+        final int authDataLength = ByteUtil.getUnsignedShort(buffer, NUM_OF_BYTES_FROM_START_TO_AUTH_DATA_LENGTH_POS);
+
+        MapRegisterCacheKey mapRegisterCacheKey = createKeyFromBytes(buffer, authDataLength);
+        if (mapRegisterCacheKey == null) {
+            return null;
+        }
+        final byte[] relevantPayloadData = createValueFromBytes(buffer, authDataLength);
+
+        buffer.position(0);
+        return new HashMap.SimpleEntry<>(mapRegisterCacheKey, relevantPayloadData);
+    }
+
+    /**
+     * Extract from buffer the data which doesn't change.
+     *
+     * It means that nonce and authentication items are ommited (skipped).
+     *
+     * payload1 - <Type;RecordCount>
+     * skip1    - Nonce
+     * payload2 - KeyId
+     * skip2    - <Authentication Data Length; Authentication Data>
+     * payload3 - <Record TTL; end of message>
+     *
+     * @param buffer
+     * @return
+     */
+    private static byte[] createValueFromBytes(ByteBuffer buffer, final int authDataLength) {
+        buffer.position(0);
+        final int bufferLength = buffer.remaining();
+
+        final int skip2Length = authDataLength + NUM_OF_BYTES_AUTHENTICATION_DATA_LENGTH;
+        final int totalNewPayloadLength = bufferLength - SKIP1_LEN - skip2Length;
+        final byte[] newPayload = new byte[totalNewPayloadLength];
+
+        buffer.get(newPayload, 0, PAYLOAD1_LEN);
+        buffer.position(PAYLOAD1_LEN + SKIP1_LEN);
+        buffer.get(newPayload, PAYLOAD1_LEN, PAYLOAD2_LEN);
+        buffer.position(PAYLOAD1_LEN + SKIP1_LEN + PAYLOAD2_LEN + skip2Length);
+        int payload3Len = totalNewPayloadLength - PAYLOAD1_LEN - PAYLOAD2_LEN;
+        buffer.get(newPayload, PAYLOAD1_LEN + PAYLOAD2_LEN, payload3Len);
+
+        return newPayload;
+    }
+
+    private static MapRegisterCacheKey createKeyFromBytes(final ByteBuffer buffer, int authDataLength) {
+
+        byte typeAndFlags = buffer.get();
+        buffer.position(0);
+        boolean xtrSiteIdPresent = ByteUtil.extractBit(typeAndFlags, XTR_SITE_ID);
+        if (!xtrSiteIdPresent) {
+            return null;
+        }
+        final MapRegisterCacheKeyBuilder mapRegisterCacheKeyBuilder = new MapRegisterCacheKeyBuilder();
+        final byte[] eidPrefix = extractEidPrefix(buffer, authDataLength);
+        if (eidPrefix == null) {
+            return null;
+        }
+        mapRegisterCacheKeyBuilder.setEidPrefix(eidPrefix);
+        if (xtrSiteIdPresent) {
+            buffer.position(0);
+            final int bufferLength = buffer.remaining();
+            buffer.position(bufferLength - MapRegisterSerializer.Length.XTRID_SIZE - MapRegisterSerializer.Length
+                    .SITEID_SIZE);
+
+            byte[] xtrId  = new byte[MapRegisterSerializer.Length.XTRID_SIZE];
+            buffer.get(xtrId);
+            mapRegisterCacheKeyBuilder.setXtrId(xtrId);
+
+            byte[] siteId = new byte[MapRegisterSerializer.Length.SITEID_SIZE];
+            buffer.get(siteId);
+            mapRegisterCacheKeyBuilder.setSiteId(siteId);
+        }
+        return mapRegisterCacheKeyBuilder.build();
+    }
+
+    private static byte[] extractEidPrefix(final ByteBuffer buffer, int authDataLength) {
+        final int startPositionOfEidPrefixAFI = NUM_OF_BYTES_FROM_START_TO_AUTH_DATA_LENGTH_POS
+                + NUM_OF_BYTES_AUTHENTICATION_DATA_LENGTH + authDataLength +
+                NUM_OF_BYTES_FROM_AUTH_DATA_TO_EID_PREFIX_AFI;
+        buffer.position(startPositionOfEidPrefixAFI);
+        final int eidPrefixAfi = ByteUtil.getUnsignedShort(buffer, startPositionOfEidPrefixAFI);
+        Optional<Integer> eidPrefixLengthOpt = resolveEidPrefixAfi(eidPrefixAfi, buffer);
+        if (eidPrefixLengthOpt.isPresent()) {
+            final byte[] eidPrefix = new byte[eidPrefixLengthOpt.get()];
+            final int startPositionOfEidPrefix = startPositionOfEidPrefixAFI + NUM_OF_BYTES_EID_PREFIX_AFI;
+            buffer.position(startPositionOfEidPrefix);
+            buffer.get(eidPrefix);
+            return eidPrefix;
+        }
+        return null;
+    }
+
+    private static Optional<Integer> resolveEidPrefixAfi(final int eidPrefixAfi, final ByteBuffer buffer) {
+        switch (AddressFamily.forValue(eidPrefixAfi)) {
+            case IpV4:
+                return Optional.of(NUM_OF_BYTES_IPV4);
+            case IpV6:
+                return Optional.of(NUM_OF_BYTES_IPV6);
+            case _48BitMac:
+                return Optional.of(NUM_OF_BYTES_MAC48);
+            case LispCanonicalAddressFormat:
+                buffer.position(buffer.position() + NUM_OF_BYTES_FROM_EID_PREFIX_AFI_TO_LCAF_LENGTH);
+                final short lengthRawValue = buffer.getShort();
+                final int length = (int)(lengthRawValue & 0xFFFF);
+                buffer.position(0);
+                return Optional.of(NUM_OF_BYTES_FROM_LCAF_RSVD1_TO_LCAF_LENGTH + length);
+            default:
+                return Optional.absent();
+        }
+    }
+
+
+}
index c865d6ddaaa34af1d0f6f95b516532f09fcecf1b..f9620691a331ba954c36607c4ef13871fe87e83b 100644 (file)
@@ -1,3 +1,106 @@
-# <a name="map_register_cache">Map-Register Cache</a>
+#Performance - Use a local cache for Map-Registers in byte[] form in SB, and only update map-cache if necessary
+
+##Feature overview
+xTR devices sent periodical updates through map register message which are often the same
+
+Store incomming map register messages in local cache to avoid deserialization of the same message.
+
+##Implementation
+###Hash map
+
+Cache is implemented in class +MapRegisterCache+ as hash map. Key of this hash map is instance of *MapRegisterCacheKey* and value is instance of *MapRegisterCacheValue*.
+
+After receiving map register message it is firstly checked whether this message has been received before. If it so and is still valid (is in cache without refresh less then 90 seconds) then it isn't deserialized. Otherway it comletely deserialized.
+
+To be able to say that received map register message is the same as other previously received there is key (instance of +MapRegisterCacheKey+) which holds following values:
+
+###Key
+
+* **eidPrefix** - array of bytes which comes from first record in map register message. In [RFC 6830](https://tools.ietf.org/html/rfc6830#page-38) it is named *EID-Prefix*. If EID-Prefix-AFI (field just infront of EID-Prefix) has value 16387 then EID-Prefix contains also instance identifier.
+* **xTRId** - array of bytes (16 B) which comes from the end of map register message according to [Lisp-NAT-Traversal](https://tools.ietf.org/html/draft-ermagan-lisp-nat-traversal-10#page-10) draft,
+* **siteId** - arrays of bytes (8 B) which comes from the end of map register message according to [Lisp-NAT-Traversal](https://tools.ietf.org/html/draft-ermagan-lisp-nat-traversal-10#page-10) draft.
+
+Combination of this 3 values should be enough to uniquely identify each map register message.
+
+###Value
+
+Value part of hash map record is created in two phases:
+
+1. during partional deserialization:
+ * **val** - byte array which contains map register in the same state as was received
+ * **wantMapNotify** - boolean value which is set according to value of M bib (24th bit of map register message)
+ * **mergeBit** - boolean value which is set according to value of 22th bit in map register message
+1. during full deserialization:
+ * **list of EIDs** - all EIDS (transformed value of EID-Prefix) for all records,
+ * **xTRId** - instance of +XtrId+ class,
+ * **siteId** - instance of +SiteId+ class,
+ * **timestamp** - when was this value object created o last refreshed.
+
+##New handling of map register message
+1. partial deserialization is done - result of this operation is map entry object which consists of pair - key and value
+1. if map entry key is in cache and is still valid
+ * new type of notification mapping-keep-alive is sent (it is caught in +LispMappingService+)
+ * if want-map-notify bit is set then also map-notify message is sent
+ * map entry value is refreshed - timestamp is set to current time
+ * done
+1. if map entry key is in cache but isn't valid - entry is removed from cache and continue with following step
+1. if map entry isn't in cache
+ * message is processed as before (complete deserialization)
+ * some data from deserialization are stored to map entry value
+ * timestamp for map entry value is set to current time
+ * done
+
+##Consideration
+* It isn't necessary to store mergeBit in value part of hash map record because only records which have merge bit set to value 0 are stored to hash map
+
+##Results comparison
+Performance test 010_Southbound_MapRequest.robot from integration-test/csit/suites/lispflowmapping/performance was
+ran 2 times with master build and 3 times with patch build. Results below:
+<table border="true">
+<tr>
+    <th>type of patch</th>
+    <th>run order</th>
+    <th>replies/s</th>
+    <th>notifies/s</th>
+    <th>store/s</th>
+</tr>
+<tr>
+    <td rowspan="3">master</td>
+    <td>1</td>
+    <td>25528.03</td>
+    <td>3207.33792662</td>
+    <td>335.244225418</td>
+</tr>
+<tr>
+    <td>2</td>
+    <td>26270.7</td>
+    <td>3427.32</td>
+    <td>323.070461668</td>
+</tr>
+<tr>
+    <td>3</td>
+    <td>26151.48</td>
+    <td>3373.95</td>
+    <td>321.998969603</td>
+</tr>
+<tr>
+    <td rowspan="3">patch</td>
+    <td>1</td>
+    <td>31635.4</td>
+    <td>24079.1</td>
+    <td>291.5026964</td>
+</tr>
+<tr>
+    <td>2</td>
+    <td>30541.91</td>
+    <td>25235.59</td>
+    <td>251.889168766</td>
+</tr>
+<tr>
+    <td>3</td>
+    <td>35747.1</td>
+    <td>24249.39</td>
+    <td>272.464715819</td>
+</tr>
+</table>
 
-## <a name="replace_this_heading_anchor">Heading 2</a>
index 69b8fadadc85da999c27185f8476b6924c20f1b9..cb81185e3d27b1947c55c486d447e47ace979736 100644 (file)
@@ -9,32 +9,46 @@
 package org.opendaylight.lispflowmapping.southbound.lisp;
 
 import static io.netty.buffer.Unpooled.wrappedBuffer;
-import io.netty.channel.socket.DatagramPacket;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.authenticationData;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.data1;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.data2;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.data3;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.joinArrays;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.keyId;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.nonce;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.siteId;
+import static org.opendaylight.lispflowmapping.southbound.lisp.MapRegisterCacheTestUtil.xTRId;
 
+import io.netty.channel.socket.DatagramPacket;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-
 import junitx.framework.ArrayAssert;
-
 import org.apache.commons.lang3.ArrayUtils;
 import org.jmock.api.Invocation;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
+import org.opendaylight.lispflowmapping.lisp.serializer.MapNotifySerializer;
+import org.opendaylight.lispflowmapping.lisp.serializer.MapReplySerializer;
 import org.opendaylight.lispflowmapping.lisp.type.LispMessage;
 import org.opendaylight.lispflowmapping.lisp.util.ByteUtil;
 import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil;
 import org.opendaylight.lispflowmapping.lisp.util.MapNotifyBuilderHelper;
 import org.opendaylight.lispflowmapping.lisp.util.MaskUtil;
-import org.opendaylight.lispflowmapping.lisp.serializer.MapNotifySerializer;
-import org.opendaylight.lispflowmapping.lisp.serializer.MapReplySerializer;
+import org.opendaylight.lispflowmapping.southbound.LispSouthboundPlugin;
+import org.opendaylight.lispflowmapping.southbound.lisp.cache.MapRegisterCache;
 import org.opendaylight.lispflowmapping.southbound.lisp.exception.LispMalformedPacketException;
 import org.opendaylight.lispflowmapping.tools.junit.BaseTestCase;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.afn.safi.rev130704.AddressFamily;
@@ -51,6 +65,11 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.ei
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.list.EidItem;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecord;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKeyBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.metadata.container.MapRegisterCacheMetadataBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValue;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValueBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapnotifymessage.MapNotifyBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecord;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecord.Action;
@@ -72,6 +91,10 @@ public class LispSouthboundServiceTest extends BaseTestCase {
     private MapNotifyBuilder mapNotifyBuilder;
     private MapReplyBuilder mapReplyBuilder;
     private MappingRecordBuilder mappingRecordBuilder;
+    private MapRegisterCache mapRegisterCache;
+    private LispSouthboundPlugin mockLispSouthboundPlugin;
+    private static final long CACHE_RECORD_TIMEOUT = 90000;
+
 
     private interface MapReplyIpv4SingleLocatorPos {
         int RECORD_COUNT = 3;
@@ -98,7 +121,9 @@ public class LispSouthboundServiceTest extends BaseTestCase {
         super.before();
         // mapResolver = context.mock(IMapResolver.class);
         // mapServer = context.mock(IMapServer.class);
-        testedLispService = new LispSouthboundHandler(null);
+        mapRegisterCache = new MapRegisterCache();
+        mockLispSouthboundPlugin = mock(LispSouthboundPlugin.class);
+        testedLispService = new LispSouthboundHandler(mockLispSouthboundPlugin, mapRegisterCache);
         nps = context.mock(NotificationPublishService.class);
         testedLispService.setNotificationProvider(nps);
         lispNotificationSaver = new ValueSaverAction<Notification>();
@@ -353,6 +378,197 @@ public class LispSouthboundServiceTest extends BaseTestCase {
         assertTrue(lastMapRegister().isWantMapNotify());
     }
 
+    /**
+     * Tests whether handling of map-register message will generate mapping-keep-alive notification
+     */
+    @Test
+    public void mapRegister_isMappingKeepAliveAndMapNotifyGenerated() throws InterruptedException,
+            UnknownHostException {
+        byte[] eidPrefixAfi = new byte[] {
+                0x00, 0x01                  //eid-prefix-afi
+        };
+
+        byte[] eidPrefix = new byte[] {
+                0x0a, 0x0a, 0x0a, 0x0a     //ipv4 address
+        };
+
+        NotificationPublishService notifServiceMock = MapRegisterCacheTestUtil.resetMockForNotificationProvider
+                (testedLispService);
+
+        //send stream of byte -> map register message
+        final MapRegisterCacheKey cacheKey = MapRegisterCacheTestUtil.createMapRegisterCacheKey(eidPrefix);
+        MapRegisterCacheTestUtil.beforeMapRegisterInvocationValidation(cacheKey, mapRegisterCache);
+        mapRegisterInvocationForCacheTest(eidPrefixAfi, eidPrefix);
+        MapRegisterCacheTestUtil.afterMapRegisterInvocationValidation(notifServiceMock,
+                cacheKey, mapRegisterCache, eidPrefixAfi, eidPrefix);
+
+        //sending the same byte stream -> map register second time
+        notifServiceMock = MapRegisterCacheTestUtil.resetMockForNotificationProvider(testedLispService);
+        mapRegisterInvocationForCacheTest(eidPrefixAfi, eidPrefix);
+
+        //mapping-keep-alive message should be generated
+        MapRegisterCacheTestUtil.afterSecondMapRegisterInvocationValidation(notifServiceMock,
+                mockLispSouthboundPlugin, eidPrefixAfi, eidPrefix);
+    }
+
+    void mapRegisterInvocationForCacheTest(byte[] eidPrefixAfi, byte[] eidPrefix) {
+        final byte[] mapRegisterMessage = MapRegisterCacheTestUtil.joinArrays(data1, nonce, keyId,
+                authenticationData, data2, eidPrefixAfi, eidPrefix, data3, xTRId, siteId);
+        handlePacket(mapRegisterMessage);
+    }
+
+    /**
+     * It tests whether map register message is stored to local cache with Ipv4 EidPrefix
+     */
+    @Test
+    public void mapRegister_cacheWithEidPrefixIpv4Test() throws InterruptedException {
+        byte[] eidPrefixAfi = new byte[] {
+                0x00, 0x01                  //eid-prefix-afi
+        };
+
+        byte[] eidPrefix = new byte[] {
+                0x0a, 0x0a, 0x0a, 0x0a     //ipv4 address
+        };
+        cacheTest(eidPrefixAfi, eidPrefix);
+    }
+
+    /**
+     * It tests whether map register message is stored to local cache with Ipv6 EidPrefix
+     */
+    @Test
+    public void mapRegister_cacheWithEidPrefixIpv6Test() throws InterruptedException {
+        byte[] eidPrefixAfi = new byte[] {
+                0x00, 0x02                  //eid-prefix-afi
+        };
+
+        byte[] eidPrefix = new byte[] {
+                0x0f, 0x0f, 0x0f, 0x0f     //ipv6 address
+                ,0x0f, 0x0f, 0x0f, 0x0f     //ipv6 address
+                ,0x0f, 0x0f, 0x0f, 0x0f     //ipv6 address
+                ,0x0f, 0x0f, 0x0f, 0x0f     //ipv6 address
+        };
+        cacheTest(eidPrefixAfi, eidPrefix);
+    }
+
+    /**
+     * It tests whether map register message is stored to local cache with Mac 48bits EidPrefix
+     */
+    @Test
+    public void mapRegister_cacheWithEidPrefixMac48Test() throws InterruptedException {
+        byte[] eidPrefixAfi = new byte[] {
+                0x40, 0x05                  //eid-prefix-afi
+        };
+
+        byte[] eidPrefix = new byte[] {
+                0x0a, 0x0b, 0x0c, 0x0d     //mac address
+                ,0x0e, 0x0f                 //mac address
+        };
+        cacheTest(eidPrefixAfi, eidPrefix);
+    }
+
+    /**
+     * It tests whether map register message is stored to local cache with Lcaf EidPrefix (inside Ipv4)
+     */
+    @Test
+    public void mapRegister_cacheWithEidPrefixLcafTest() throws InterruptedException {
+        byte[] eidPrefixAfi = new byte[] {
+                0x40, 0x03                  //eid-prefix-afi
+        };
+
+        //following lcaf prefixed variables are defined according to https://tools.ietf
+        // .org/html/draft-ietf-lisp-lcaf-12#section-4.1
+        byte[] lcafRsvd1 = new byte[]{0x00};
+        byte[] lcafFlags = new byte[]{0x00};
+        byte[] lcafType = new byte[]{0x02};
+        byte[] lcafIIDMaskLength = new byte[]{0x20};
+        byte[] lcafLength = new byte[] {0x00, 0x0a};
+        byte[] lcafInstanceId = new byte[]{0x00, 0x00, 0x00, 0x15};
+        byte[] lcafAfi = new byte[] {0x00, 0x01};
+        byte[] lcafAddress = new byte[] {0x7d, 0x7c, 0x7b, 0x7a};
+
+        byte[] eidPrefix = joinArrays(lcafRsvd1, lcafFlags, lcafType, lcafIIDMaskLength, lcafLength, lcafInstanceId,
+                lcafAfi, lcafAddress);
+        cacheTest(eidPrefixAfi, eidPrefix);
+    }
+
+    /**
+     * It tests whether map register message is stored to local cache
+     * @param eidPrefixAfi
+     * @param eidPrefix
+     */
+    public void cacheTest(byte[] eidPrefixAfi, byte[] eidPrefix) throws InterruptedException {
+        final MapRegisterCacheKey mapRegisterCacheKey = MapRegisterCacheTestUtil.createMapRegisterCacheKey(eidPrefix);
+
+        final NotificationPublishService mockedNotificationProvider = mock(NotificationPublishService.class);
+        testedLispService.setNotificationProvider(mockedNotificationProvider);
+
+        MapRegisterCacheTestUtil.beforeMapRegisterInvocationValidation(mapRegisterCacheKey, mapRegisterCache);
+        mapRegisterInvocationForCacheTest(eidPrefixAfi, eidPrefix);
+        MapRegisterCacheTestUtil.afterMapRegisterInvocationValidation(mockedNotificationProvider,
+                mapRegisterCacheKey, mapRegisterCache, eidPrefixAfi, eidPrefix);
+    }
+
+
+    /*
+        Checks whether old record from cache will expire and is replaced with new record.
+
+        add to empty cache record with timestamp older then 90 second - one object
+        add the same entry through calling of handleMapRegister
+        check that removeEntry() and addEntry() (or refreshEntry() was called on mocked object for mapRegisterCache
+    */
+    @Test
+    public void mapRegister_cacheRecordExpirationTest() throws InterruptedException {
+        //tests handling of map register message when next message comes:
+
+        //after cache entry timeout
+        cacheRecordExpirationTest(true);
+
+        //before cache entry timout
+        cacheRecordExpirationTest(false);
+    }
+
+    private void cacheRecordExpirationTest(boolean cacheRecordTimeouted) {
+        mapRegisterCache = mock(MapRegisterCache.class);
+        testedLispService = new LispSouthboundHandler(mockLispSouthboundPlugin, mapRegisterCache);
+
+        byte[] eidPrefixAfi = new byte[] {0x00, 0x01};
+
+        byte[] eidPrefix = new byte[] {0x0a, 0x0a, 0x0a, 0x0a};
+
+
+        MapRegisterCacheKeyBuilder cacheKeyBld = new MapRegisterCacheKeyBuilder();
+        MapRegisterCacheValueBuilder cacheValueBld = new MapRegisterCacheValueBuilder();
+        cacheKeyBld.setXtrId(xTRId);
+        cacheKeyBld.setEidPrefix(eidPrefix);
+        cacheKeyBld.setSiteId(siteId);
+
+        MapRegisterCacheMetadataBuilder cacheMetadataBld = new MapRegisterCacheMetadataBuilder();
+        cacheMetadataBld.setTimestamp(System.currentTimeMillis() - (cacheRecordTimeouted ? CACHE_RECORD_TIMEOUT : 0L));
+        cacheMetadataBld.setWantMapNotify(false);
+        cacheValueBld.setMapRegisterCacheMetadata(cacheMetadataBld.build());
+        cacheValueBld.setPacketData(MapRegisterCacheTestUtil.joinArrays(data1, keyId, data2, eidPrefixAfi,
+                eidPrefix, data3, xTRId, siteId));
+
+
+        final MapRegisterCacheKey cacheKey = cacheKeyBld.build();
+        final MapRegisterCacheValue cacheValue = cacheValueBld.build();
+
+        Mockito.when(mapRegisterCache.getEntry(Mockito.eq(cacheKey))).thenReturn(cacheRecordTimeouted ? null :
+                cacheValue);
+
+        mapRegisterInvocationForCacheTest(eidPrefixAfi, eidPrefix);
+
+        InOrder inOrder = Mockito.inOrder(mapRegisterCache);
+            inOrder.verify(mapRegisterCache).getEntry(Mockito.eq(cacheKey));
+
+        if (cacheRecordTimeouted) {
+            inOrder.verify(mapRegisterCache).addEntry(Mockito.eq(cacheKey), AdditionalMatchers.not(Mockito.eq
+                    (cacheValue)));
+        } else {
+            inOrder.verify(mapRegisterCache).refreshEntry(Mockito.eq(cacheKey));
+        }
+    }
+
     @Test
     @Ignore
     public void mapRegisterAndNotify__ValidExtraDataParsedSuccessfully() throws Exception {
diff --git a/mappingservice/southbound/src/test/java/org/opendaylight/lispflowmapping/southbound/lisp/MapRegisterCacheTestUtil.java b/mappingservice/southbound/src/test/java/org/opendaylight/lispflowmapping/southbound/lisp/MapRegisterCacheTestUtil.java
new file mode 100644 (file)
index 0000000..bc960c6
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.lispflowmapping.southbound.lisp;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import org.apache.commons.lang3.ArrayUtils;
+import org.junit.Assert;
+import org.mockito.Mockito;
+import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
+import org.opendaylight.lispflowmapping.southbound.LispSouthboundPlugin;
+import org.opendaylight.lispflowmapping.southbound.lisp.cache.MapRegisterCache;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.AddMapping;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MappingKeepAlive;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MessageType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.key.container.MapRegisterCacheKeyBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.map.register.cache.value.grouping.MapRegisterCacheValue;
+
+final class MapRegisterCacheTestUtil {
+    final static byte[] data1 = new byte[] {
+            0x33, 0x00, 0x01, 0x01     //Type, P, I, R, Reserved, M, Record Count
+    };
+
+    final static byte[] nonce = new byte[] {
+            0x00, 0x00, 0x00, 0x00     //nonce
+            ,0x00, 0x00, 0x00, 0x00    //nonce
+    };
+
+    final static byte[] keyId = new byte[] {
+            0x10, 0x10                 //key ID
+    };
+
+    final static byte[] authenticationData = new byte[]{
+            0x00, 0x0c                 //authentication data length
+            ,0x7f, 0x7f, 0x7f, 0x7f    //auth data
+            ,0x7f, 0x7f, 0x7f, 0x7f    //auth data
+            ,0x7f, 0x7f, 0x7f, 0x7f    //auth data
+    };
+
+    final static byte[] data2 = new byte[]{
+            0x00, 0x00, 0x00, 0x6f    //TTL
+            ,0x01, 0x08, 0x00, 0x00
+            ,0x00, 0x01                //Rsvd, Map-Version Number
+    };
+
+    final static byte[] data3 = new byte[] {
+            0x01, 0x0c, 0x0d, 0x0b     //Priority, Weight, M Priority, M Weight
+            ,0x00, 0x00, 0x00, 0x01    //Unused Flags, L, p, R, Loc-Afi
+            ,0x0c, 0x0c, 0x0c, 0x0c    //Locator
+    };
+
+    final static byte[] xTRId = new byte[]{
+            0x53, 0x61, 0x6e, 0x20     //xTR id
+            ,0x4a, 0x6f, 0x73, 0x65    //xTR id
+            ,0x2c, 0x20, 0x32, 0x66    //xTR id
+            ,0x33, 0x72, 0x32, 0x64    //xTR id
+    };
+
+    final static byte[] siteId = new byte[]{
+            0x33, 0x2c, 0x31, 0x34     //site id
+            ,0x31, 0x35, 0x39, 0x32    //site id
+    };
+
+
+    private MapRegisterCacheTestUtil() {
+        throw new UnsupportedOperationException();
+    }
+
+    static byte[] joinArrays(byte[] firstArray, byte[] ... arrays) {
+        byte[] result = firstArray;
+        for(byte[] array : arrays) {
+            result = ArrayUtils.addAll(result, array);
+        }
+        return result;
+    }
+
+    static NotificationPublishService resetMockForNotificationProvider(final LispSouthboundHandler testedLispService) {
+        NotificationPublishService mockedNotificationProvider = mock(NotificationPublishService.class);
+        testedLispService.setNotificationProvider(mockedNotificationProvider);
+        return mockedNotificationProvider;
+    }
+
+    static void afterSecondMapRegisterInvocationValidation(final NotificationPublishService
+                                                                    mockedNotificationProvider,
+                                                            final LispSouthboundPlugin mockLispSouthboundPlugin,
+                                                            byte[] eidPrefixAfi, byte[] eidPrefix) throws
+            InterruptedException, UnknownHostException {
+        verify(mockedNotificationProvider).putNotification(Mockito.any(MappingKeepAlive.class));
+
+        final byte[] resetForMapNotify = new byte[]{
+                0x04, 0x00, 0x00, 0x01
+        };
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(joinArrays(resetForMapNotify, nonce, keyId, authenticationData,
+                data2, eidPrefixAfi, eidPrefix, data3, xTRId, siteId));
+
+        verify(mockLispSouthboundPlugin).handleSerializedLispBuffer(
+                Mockito.eq(Inet4Address.getByAddress("0.0.0.0", new byte[]{0, 0, 0, 0}))
+                , Mockito.eq(byteBuffer)
+                , Mockito.eq(MessageType.MapNotify)
+                , Mockito.eq(0));
+    }
+
+    static void afterMapRegisterInvocationValidation(final NotificationPublishService mockedNotificationProvider, final
+    MapRegisterCacheKey mapRegisterCacheKey, final MapRegisterCache mapRegisterCache, final byte[] eidPrefixAfi,
+                                                     byte[] eidPrefix) throws InterruptedException {
+        Mockito.verify(mockedNotificationProvider).putNotification(Mockito.any(AddMapping.class));
+
+        Assert.assertEquals(1, mapRegisterCache.cacheSize());
+        final MapRegisterCacheValue currentMapRegisterCacheValue = mapRegisterCache.getEntry(mapRegisterCacheKey);
+        Assert.assertNotNull(currentMapRegisterCacheValue);
+        final byte[] currentMapRegisterMsg = currentMapRegisterCacheValue.getPacketData();
+        final byte[] expectedMapRegisterMsg = joinArrays(data1, keyId, data2, eidPrefixAfi, eidPrefix, data3, xTRId,
+                siteId);
+        Assert.assertArrayEquals(expectedMapRegisterMsg, currentMapRegisterMsg);
+    }
+
+    static MapRegisterCacheKey createMapRegisterCacheKey(final byte[] eidPrefix) {
+        final MapRegisterCacheKeyBuilder mapRegisterCacheKeyBuilder = new MapRegisterCacheKeyBuilder();
+        mapRegisterCacheKeyBuilder.setXtrId(xTRId);
+        mapRegisterCacheKeyBuilder.setSiteId(siteId);
+        mapRegisterCacheKeyBuilder.setEidPrefix(eidPrefix);
+        return mapRegisterCacheKeyBuilder.build();
+    }
+
+    static void beforeMapRegisterInvocationValidation(final MapRegisterCacheKey mapRegisterCacheKey,
+                                                       final MapRegisterCache mapRegisterCache) {
+        Assert.assertEquals(0, mapRegisterCache.cacheSize());
+        Assert.assertNull(mapRegisterCache.getEntry(mapRegisterCacheKey));
+    }
+
+
+
+
+}