Time Bucket DS for handeling SB mapping timeout 13/46013/19
authorShakib Ahmed <sheikahm@cisco.com>
Thu, 1 Dec 2016 19:19:28 +0000 (11:19 -0800)
committerShakib Ahmed <sheikahm@cisco.com>
Thu, 16 Feb 2017 21:17:24 +0000 (13:17 -0800)
Mapping records in map caches have expiration time. When
mapping records surpass their expiration time, they are
supposed to be expired, meaning, removed from the map cache.

Right now the mapping records are expired in a lazy manner,
meaning, we query and handle the validity of the mapping only
when the mapping are beign asked for which introduces the
possibility of mapping records being in map cache indefinitly
if they are not requested for.

Time Bucket data structure maintains mapping records and
delete them upon expiration in a non-lazy manner.

Change-Id: I664249cc5c5d14cb54759369c6f982aef89a0ca3
Signed-off-by: Shakib Ahmed <sheikahm@cisco.com>
mappingservice/api/src/main/java/org/opendaylight/lispflowmapping/interfaces/dao/SubKeys.java
mappingservice/config/src/main/java/org/opendaylight/lispflowmapping/config/ConfigIni.java
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/MappingSystem.java
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucket.java [new file with mode: 0644]
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucketWheel.java [new file with mode: 0644]
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/implementation/TimeBucketMappingTimeoutService.java [new file with mode: 0644]
mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/interfaces/ISouthBoundMappingTimeoutService.java [new file with mode: 0644]
mappingservice/implementation/src/test/java/org/opendaylight/lispflowmapping/implementation/timebucket/TimeBucketWheelUnitTest.java [new file with mode: 0644]
mappingservice/mapcache/src/main/java/org/opendaylight/lispflowmapping/mapcache/SimpleMapCache.java
mappingservice/mapcache/src/test/java/org/opendaylight/lispflowmapping/mapcache/SimpleMapCacheTest.java

index fadc017ef42f70e1e95b3aa4126e0d3ff2b92bdf..5c0241a3e9cc202d4ff02b6ae8880cc46e8c1409 100644 (file)
@@ -23,6 +23,7 @@ public interface SubKeys {
     String SRC_RLOCS = "src_rlocs";
     String VNI = "vni";
     String LCAF_SRCDST = "lcaf_srcdst";
+    String TIME_BUCKET_ID = "time_bucket_id";
 
     String UNKOWN = "-1";
 }
index 943990b612fed22bbab4d188e13cb95b230780d4..6f42a390f6924727a38b5d4c89450782e5d55013 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.lispflowmapping.config;
 
+import java.util.concurrent.TimeUnit;
+
 import org.opendaylight.lispflowmapping.interfaces.mappingservice.IMappingService;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -24,6 +26,7 @@ public final class ConfigIni {
     private long registrationValiditySb;
     private long smrTimeout;
     private int smrRetryCount;
+    private int numberOfBucketsInTimeBucketWheel;
 
     /*
      * XXX  When configuration options are added or removed, they should also be added/removed in the karaf
@@ -47,6 +50,8 @@ public final class ConfigIni {
     private static final long MIN_REGISTRATION_VALIDITY_SB = 200000L;
     private static final long DEFAULT_SMR_TIMEOUT = 3000L;
     private static final int DEFAULT_SMR_RETRY_COUNT = 5;
+    private static final int MIN_NUMBER_OF_BUCKETS_IN_TIME_BUCKET_WHEEL = 2;
+    private static final int TIMEOUT_TOLERANCE_MULTIPLIER_IN_TIME_BUCKET_WHEEL = 2;
 
     private static final ConfigIni INSTANCE = new ConfigIni();
 
@@ -64,6 +69,7 @@ public final class ConfigIni {
         initRegisterValiditySb(context);
         initSmrRetryCount(context);
         initSmrTimeout(context);
+        initBucketNumber();
     }
 
     private void initRegisterValiditySb(BundleContext context) {
@@ -263,6 +269,14 @@ public final class ConfigIni {
         }
     }
 
+    //one bucket should contain mapping of approximate 1 min time frame
+    private void initBucketNumber() {
+        numberOfBucketsInTimeBucketWheel = (int) (TimeUnit.MILLISECONDS.toMinutes(getRegistrationValiditySb()) + 1);
+
+        numberOfBucketsInTimeBucketWheel = Math.max(numberOfBucketsInTimeBucketWheel,
+                MIN_NUMBER_OF_BUCKETS_IN_TIME_BUCKET_WHEEL);
+    }
+
     public boolean mappingMergeIsSet() {
         return mappingMerge;
     }
@@ -322,6 +336,14 @@ public final class ConfigIni {
         return this.smrTimeout;
     }
 
+    public int getNumberOfBucketsInTimeBucketWheel() {
+        return numberOfBucketsInTimeBucketWheel;
+    }
+
+    public long  getMaximumTimeoutTolerance() {
+        return TIMEOUT_TOLERANCE_MULTIPLIER_IN_TIME_BUCKET_WHEEL * getRegistrationValiditySb();
+    }
+
     public static ConfigIni getInstance() {
         return INSTANCE;
     }
index 988ba781a202d3766083577b823767bdb17de650..fae45a32a68e5abd9526d6448fa6f2fb65a34f35 100644 (file)
@@ -18,9 +18,12 @@ import java.util.Set;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.lispflowmapping.config.ConfigIni;
 import org.opendaylight.lispflowmapping.dsbackend.DataStoreBackEnd;
+import org.opendaylight.lispflowmapping.implementation.timebucket.implementation.TimeBucketMappingTimeoutService;
+import org.opendaylight.lispflowmapping.implementation.timebucket.interfaces.ISouthBoundMappingTimeoutService;
 import org.opendaylight.lispflowmapping.implementation.util.DSBEInputUtil;
 import org.opendaylight.lispflowmapping.implementation.util.MappingMergeUtil;
 import org.opendaylight.lispflowmapping.interfaces.dao.ILispDAO;
+import org.opendaylight.lispflowmapping.interfaces.dao.SubKeys;
 import org.opendaylight.lispflowmapping.interfaces.mapcache.IAuthKeyDb;
 import org.opendaylight.lispflowmapping.interfaces.mapcache.ILispMapCache;
 import org.opendaylight.lispflowmapping.interfaces.mapcache.IMapCache;
@@ -73,11 +76,17 @@ public class MappingSystem implements IMappingSystem {
     private DataStoreBackEnd dsbe;
     private boolean isMaster = false;
 
+    private ISouthBoundMappingTimeoutService sbMappingTimeoutService;
+
     public MappingSystem(ILispDAO dao, boolean iterateMask, boolean notifications, boolean mappingMerge) {
         this.dao = dao;
         this.notificationService = notifications;
         this.mappingMerge = mappingMerge;
         buildMapCaches();
+
+        sbMappingTimeoutService = new TimeBucketMappingTimeoutService(ConfigIni.getInstance()
+                .getNumberOfBucketsInTimeBucketWheel(), ConfigIni.getInstance().getRegistrationValiditySb(),
+                this);
     }
 
     public void setDataStoreBackEnd(DataStoreBackEnd dsbe) {
@@ -116,6 +125,9 @@ public class MappingSystem implements IMappingSystem {
     }
 
     public void addMapping(MappingOrigin origin, Eid key, MappingData mappingData) {
+
+        sbMappingTimeoutService.removeExpiredMappings();
+
         if (mappingData == null) {
             LOG.warn("addMapping() called with null mapping, ignoring");
             return;
@@ -137,6 +149,7 @@ public class MappingSystem implements IMappingSystem {
                     smc.addMapping(key, xtrId, mappingData);
                 }
             }
+            addOrRefreshMappingInTimeoutService(key, mappingData);
         }
 
         tableMap.get(origin).addMapping(key, mappingData);
@@ -154,17 +167,35 @@ public class MappingSystem implements IMappingSystem {
         }
     }
 
+    private void addOrRefreshMappingInTimeoutService(Eid key, MappingData mappingData) {
+        Integer oldBucketId = (Integer) smc.getData(key, SubKeys.TIME_BUCKET_ID);
+        Integer updatedBucketId;
+
+        if (oldBucketId != null) {
+            //refresh mapping
+            updatedBucketId = sbMappingTimeoutService.refreshMapping(key, mappingData, oldBucketId);
+        } else {
+            updatedBucketId = sbMappingTimeoutService.addMapping(key, mappingData);
+        }
+
+        smc.addData(key, SubKeys.TIME_BUCKET_ID, updatedBucketId);
+    }
+
     /*
      * Since this method is only called when there is a hit in the southbound Map-Register cache, and that cache is
      * not used when merge is on, it's OK to ignore the effects of timestamp changes on merging for now.
      */
     public void refreshMappingRegistration(Eid key, XtrId xtrId, Long timestamp) {
+
+        sbMappingTimeoutService.removeExpiredMappings();
+
         if (timestamp == null) {
             timestamp = System.currentTimeMillis();
         }
         MappingData mappingData = (MappingData) smc.getMapping(null, key);
         if (mappingData != null) {
             mappingData.setTimestamp(new Date(timestamp));
+            addOrRefreshMappingInTimeoutService(key, mappingData);
         } else {
             LOG.warn("Could not update timestamp for EID {}, no mapping found", LispAddressStringifier.getString(key));
         }
@@ -233,6 +264,7 @@ public class MappingSystem implements IMappingSystem {
         if (mergedMappingData != null) {
             smc.addMapping(key, mergedMappingData, sourceRlocs);
             dsbe.addMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key, mergedMappingData));
+            addOrRefreshMappingInTimeoutService(key, mergedMappingData);
         } else {
             removeSbMapping(key, mergedMappingData);
         }
@@ -319,7 +351,7 @@ public class MappingSystem implements IMappingSystem {
         }
     }
 
-    private MappingData handleSbExpiredMapping(Eid key, XtrId xtrId, MappingData mappingData) {
+    public MappingData handleSbExpiredMapping(Eid key, XtrId xtrId, MappingData mappingData) {
         if (mappingMerge && mappingData.isMergeEnabled()) {
             return handleMergedMapping(key);
         }
@@ -329,7 +361,6 @@ public class MappingSystem implements IMappingSystem {
         } else {
             removeSbMapping(key, mappingData);
         }
-
         return null;
     }
 
@@ -342,11 +373,18 @@ public class MappingSystem implements IMappingSystem {
         if (mappingData != null && mappingData.getXtrId() != null) {
             removeSbXtrIdSpecificMapping(key, mappingData.getXtrId(), mappingData);
         }
-
+        removeFromSbTimeoutService(key);
         smc.removeMapping(key);
         dsbe.removeMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key, mappingData));
     }
 
+    private void removeFromSbTimeoutService(Eid key) {
+        Integer bucketId = (Integer) smc.getData(key, SubKeys.TIME_BUCKET_ID);
+        if (bucketId != null) {
+            sbMappingTimeoutService.removeMappingFromTimeoutService(key, bucketId);
+        }
+    }
+
     @Override
     public Eid getWidestNegativePrefix(Eid key) {
         Eid nbPrefix = pmc.getWidestNegativeMapping(key);
@@ -369,6 +407,9 @@ public class MappingSystem implements IMappingSystem {
 
     @Override
     public void removeMapping(MappingOrigin origin, Eid key) {
+        if (origin == MappingOrigin.Southbound) {
+            removeFromSbTimeoutService(key);
+        }
         tableMap.get(origin).removeMapping(key);
         if (notificationService) {
             // TODO
diff --git a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucket.java b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucket.java
new file mode 100644 (file)
index 0000000..3a08687
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc.  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.implementation.timebucket.containers;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.opendaylight.lispflowmapping.implementation.MappingSystem;
+import org.opendaylight.lispflowmapping.lisp.type.MappingData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by Shakib Ahmed on 12/1/16.
+ */
+public class TimeBucket {
+    private static final Logger LOG = LoggerFactory.getLogger(TimeBucket.class);
+
+    private ConcurrentHashMap<Eid, MappingData> bucketElements;
+
+    private MappingSystem mappingSystem;
+
+    public TimeBucket(MappingSystem mappingSystem) {
+        bucketElements = new ConcurrentHashMap<>();
+        this.mappingSystem = mappingSystem;
+    }
+
+    public void add(Eid key, MappingData mappingData) {
+        bucketElements.put(key, mappingData);
+    }
+
+    public void removeFromBucketOnly(Eid key) {
+        MappingData mappingData = bucketElements.get(key);
+
+        if (mappingData != null) {
+            bucketElements.remove(key);
+        }
+    }
+
+    public void clearBucket() {
+        bucketElements.forEach((key, mappingData) -> {
+            mappingSystem.handleSbExpiredMapping(key, null, mappingData);
+        });
+    }
+}
\ No newline at end of file
diff --git a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucketWheel.java b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/containers/TimeBucketWheel.java
new file mode 100644 (file)
index 0000000..8cb174c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc.  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.implementation.timebucket.containers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opendaylight.lispflowmapping.implementation.MappingSystem;
+import org.opendaylight.lispflowmapping.lisp.type.MappingData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by Shakib Ahmed on 12/1/16.
+ */
+public class TimeBucketWheel {
+    private static final Logger LOG = LoggerFactory.getLogger(TimeBucketWheel.class);
+
+    private int currentBucketId;
+    private int numberOfBuckets;
+    private long lastRotationTimestamp;
+
+    private List<TimeBucket> bucketList;
+
+    private long timeFrame;
+
+    public TimeBucketWheel(int numberOfBuckets, long mappingRecordValidityInMilis, MappingSystem mappingSystem) {
+
+        if (numberOfBuckets <= 1) {
+            throw new IllegalArgumentException("Expected number of buckets "
+                    + "in TimeBucketMappingContainer to be more 1");
+        }
+
+        this.numberOfBuckets = numberOfBuckets;
+
+        initializeBucketList(mappingSystem);
+        timeFrame = (long) Math.ceil(1.0 * mappingRecordValidityInMilis / (numberOfBuckets - 1));
+        lastRotationTimestamp = System.currentTimeMillis();
+        currentBucketId = 0;
+    }
+
+    private void initializeBucketList(MappingSystem mappingSystem) {
+        bucketList = new ArrayList<>();
+        for (int i = 0; i < numberOfBuckets; i++) {
+            bucketList.add(new TimeBucket(mappingSystem));
+        }
+    }
+
+    public int add(Eid key, MappingData mappingData, long timestamp) {
+        clearExpiredMappingAndRotate(timestamp);
+        int timeBucketId = getProperBucketId(timestamp);
+
+        TimeBucket properTimeBucket = getBucket(timeBucketId);
+        properTimeBucket.add(key, mappingData);
+        return timeBucketId;
+    }
+
+    public int refreshMappping(Eid key, MappingData newMappingData, long timestamp, int bucketId) {
+        TimeBucket timeBucket = getBucket(bucketId);
+        timeBucket.removeFromBucketOnly(key);
+        return add(key, newMappingData, timestamp);
+    }
+
+    public void removeMapping(Eid key, int bucketId) {
+        TimeBucket timeBucket = getBucket(bucketId);
+        timeBucket.removeFromBucketOnly(key);
+    }
+
+    private int getLastBucketId() {
+        return (currentBucketId - 1 + numberOfBuckets) % numberOfBuckets;
+    }
+
+    private int getProperBucketId(long timestamp) {
+        if (timestamp > lastRotationTimestamp) {
+            //after rotation we are at current
+            return currentBucketId;
+        }
+
+        int relativeBucketId = (int) ((lastRotationTimestamp - timestamp) / timeFrame);
+        if (relativeBucketId >= numberOfBuckets) {
+            //too old scenario
+            LOG.error("The mapping that is being added is too old! This should not happen.");
+            return getLastBucketId();
+        }
+
+        return (this.currentBucketId + relativeBucketId) % numberOfBuckets;
+    }
+
+    private TimeBucket getBucket(int bucketId) {
+        return bucketList.get(bucketId);
+    }
+
+    public void clearExpiredMappingAndRotate() {
+        clearExpiredMappingAndRotate(System.currentTimeMillis());
+    }
+
+    public void clearExpiredMappingAndRotate(long currentStamp) {
+        int numberOfRotationToPerform = getNumberOfRotationsToPerform(currentStamp);
+
+        long timeForwarded = 0;
+
+        while (numberOfRotationToPerform > 0) {
+            clearExpiredBucket();
+            rotate();
+            numberOfRotationToPerform--;
+            timeForwarded += timeFrame;
+        }
+
+        lastRotationTimestamp = lastRotationTimestamp + timeForwarded;
+    }
+
+    private int getNumberOfRotationsToPerform(long currentStamp) {
+        if (currentStamp < lastRotationTimestamp) {
+            return 0;
+        }
+
+        long durationInMillis = (currentStamp - lastRotationTimestamp);
+
+        int numberOfRotationToPerform = (int) (1.0 * durationInMillis / timeFrame);
+        numberOfRotationToPerform = Math.min(numberOfRotationToPerform, numberOfBuckets);
+
+        return numberOfRotationToPerform;
+    }
+
+    private void clearExpiredBucket() {
+        int timeoutBucketId = getLastBucketId();
+        clearSpecificBucket(timeoutBucketId);
+    }
+
+    private void clearSpecificBucket(int bucketId) {
+        TimeBucket bucket = getBucket(bucketId);
+        bucket.clearBucket();
+    }
+
+    private void rotate() {
+        currentBucketId = getLastBucketId();
+    }
+}
diff --git a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/implementation/TimeBucketMappingTimeoutService.java b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/implementation/TimeBucketMappingTimeoutService.java
new file mode 100644 (file)
index 0000000..b9b1b29
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc.  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.implementation.timebucket.implementation;
+
+import org.opendaylight.lispflowmapping.implementation.MappingSystem;
+import org.opendaylight.lispflowmapping.implementation.timebucket.containers.TimeBucketWheel;
+import org.opendaylight.lispflowmapping.implementation.timebucket.interfaces.ISouthBoundMappingTimeoutService;
+import org.opendaylight.lispflowmapping.lisp.type.MappingData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by Shakib Ahmed on 12/1/16.
+ */
+public class TimeBucketMappingTimeoutService implements ISouthBoundMappingTimeoutService {
+    private static final Logger LOG = LoggerFactory.getLogger(TimeBucketWheel.class);
+
+    TimeBucketWheel timeBucketWheel;
+
+    public TimeBucketMappingTimeoutService(int numberOfBucket, long mappingRecordValidityInMillis,
+                                           MappingSystem mappingSystem) {
+        timeBucketWheel = new TimeBucketWheel(numberOfBucket, mappingRecordValidityInMillis, mappingSystem);
+    }
+
+    @Override
+    public int addMapping(Eid key, MappingData mappingData) {
+        long timestamp = System.currentTimeMillis();
+        if (mappingData.getTimestamp() != null) {
+            timestamp = mappingData.getTimestamp().getTime();
+        }
+        return timeBucketWheel.add(key, mappingData, timestamp);
+    }
+
+    @Override
+    public int refreshMapping(Eid key, MappingData newMappingData, int presentBucketId) {
+        long timestamp = System.currentTimeMillis();
+        if (newMappingData.getTimestamp() != null) {
+            timestamp = newMappingData.getTimestamp().getTime();
+        }
+        return timeBucketWheel.refreshMappping(key, newMappingData, timestamp, presentBucketId);
+    }
+
+    @Override
+    public void removeMappingFromTimeoutService(Eid key, int presentBucketId) {
+        timeBucketWheel.removeMapping(key, presentBucketId);
+    }
+
+    @Override
+    public void removeExpiredMappings() {
+        timeBucketWheel.clearExpiredMappingAndRotate();
+    }
+}
diff --git a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/interfaces/ISouthBoundMappingTimeoutService.java b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/timebucket/interfaces/ISouthBoundMappingTimeoutService.java
new file mode 100644 (file)
index 0000000..c48064b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc.  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.implementation.timebucket.interfaces;
+
+import org.opendaylight.lispflowmapping.lisp.type.MappingData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
+
+/**
+ * Created by Shakib Ahmed on 12/1/16.
+ */
+public interface ISouthBoundMappingTimeoutService {
+
+    /**
+     * Add mapping in Southbound Mapping Timeout Manager
+     * which is currently Time Bucket Wheel.
+     *
+     * @param key
+     *            The key for the mapping
+     * @param mappingData
+     *            Mapping to be stored
+     * @return The id of the bucket the mapping was added to
+     */
+    int addMapping(Eid key, MappingData mappingData);
+
+    /**
+     * Refresh mapping in southbound manager. Remove old mapping
+     * from Time Bucket Wheel and add the mapping in proper time bucket.
+     * This is either because mapping re-registration or new merged
+     * mapping and refresh mapping request in MS/MR.
+     *
+     * @param key
+     *            The key for the mapping
+     * @param newMappingData
+     *            New Mapping Data for the key
+     * @param presentBucketId
+     *            The id of the bucket the previous mapping is in
+     * @return The new id of the bucket the mapping was added to
+     */
+    int refreshMapping(Eid key, MappingData newMappingData, int presentBucketId);
+
+    /**
+     * Remove mapping from Southbound manager.
+     *
+     * @param key
+     *            The key for the mapping
+     * @param presentBucketId
+     *            The id of the present bucket the key is in
+     */
+    void removeMappingFromTimeoutService(Eid key, int presentBucketId);
+
+    /**
+     * Remove the expired mappings from the Time Bucket Wheel. This
+     * should remove mapping from both SimpleMapCache and DataStoreBackEnd.
+     */
+    void removeExpiredMappings();
+}
diff --git a/mappingservice/implementation/src/test/java/org/opendaylight/lispflowmapping/implementation/timebucket/TimeBucketWheelUnitTest.java b/mappingservice/implementation/src/test/java/org/opendaylight/lispflowmapping/implementation/timebucket/TimeBucketWheelUnitTest.java
new file mode 100644 (file)
index 0000000..f549a0a
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc.  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.implementation.timebucket;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.lispflowmapping.config.ConfigIni;
+import org.opendaylight.lispflowmapping.implementation.MappingSystem;
+import org.opendaylight.lispflowmapping.implementation.timebucket.containers.TimeBucket;
+import org.opendaylight.lispflowmapping.implementation.timebucket.containers.TimeBucketWheel;
+import org.opendaylight.lispflowmapping.interfaces.dao.ILispDAO;
+import org.opendaylight.lispflowmapping.lisp.type.MappingData;
+import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.inet.binary.types.rev160303.IpAddressBinary;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.inet.binary.types.rev160303.Ipv4AddressBinary;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.SiteId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.XtrId;
+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.mapping.record.container.MappingRecord;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecordBuilder;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.powermock.reflect.Whitebox;
+
+/**
+ * Created by Shakib Ahmed on 12/13/16.
+ */
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(MockitoJUnitRunner.class)
+@PrepareForTest(TimeBucketWheel.class)
+public class TimeBucketWheelUnitTest {
+
+    private static final String IPV4_STRING_1 =         "1.2.3.0";
+    private static final String IPV4_STRING_2 =         "1.2.4.0";
+    private static final String IPV4_STRING_3 =         "1.2.5.0";
+    private static final String IPV4_STRING_4 =         "1.2.6.0";
+    private static final String IPV4_STRING_5 =         "1.2.7.0";
+    private static final Eid IPV4_EID_1 = LispAddressUtil.asIpv4Eid(IPV4_STRING_1);
+    private static final Eid IPV4_EID_2 = LispAddressUtil.asIpv4Eid(IPV4_STRING_2);
+    private static final Eid IPV4_EID_3 = LispAddressUtil.asIpv4Eid(IPV4_STRING_3);
+    private static final Eid IPV4_EID_4 = LispAddressUtil.asIpv4Eid(IPV4_STRING_4);
+    private static final Eid IPV4_EID_5 = LispAddressUtil.asIpv4Eid(IPV4_STRING_5);
+    private static final IpAddressBinary IPV4_SOURCE_RLOC_1 = new IpAddressBinary(
+            new Ipv4AddressBinary(new byte[] {1, 1, 1, 1}));
+
+    private static final int NUMBER_OF_BUCKETS = 4;
+
+    private static final XtrId XTR_ID_1 = new XtrId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1});
+
+    private static final SiteId SITE_ID_1 = new SiteId(new byte[]{1, 1, 1, 1, 1, 1, 1, 1});
+
+    private static final long REGISTRATION_VALIDITY = ConfigIni.getInstance().getRegistrationValiditySb();
+
+    private static ILispDAO daoMock = Mockito.mock(ILispDAO.class);
+    private static MappingSystem mappingSystem = Mockito.mock(MappingSystem.class);
+
+    /**
+     * Tests {@link TimeBucketWheel#add(Eid, MappingData, long)} method for general case.
+     */
+    @Test
+    public void mappingAddedInTheProperBucketGeneralTest() {
+        PowerMockito.mockStatic(System.class);
+
+        long frozenTimeStamp = System.currentTimeMillis();
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        TimeBucketWheel timeBucketWheel = getDefaultTimeBucketWheel();
+
+        final int bucketId1 = timeBucketWheel.add(IPV4_EID_1, getDefaultMappingData(IPV4_EID_1),
+                System.currentTimeMillis());
+
+        frozenTimeStamp += 1000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        final int bucketId2 = timeBucketWheel.add(IPV4_EID_2, getDefaultMappingData(IPV4_EID_2),
+                System.currentTimeMillis());
+
+        frozenTimeStamp += 1000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        final int bucketId3 = timeBucketWheel.add(IPV4_EID_3, getDefaultMappingData(IPV4_EID_3),
+                System.currentTimeMillis());
+
+        frozenTimeStamp += 1000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        final int bucketId4 = timeBucketWheel.add(IPV4_EID_4, getDefaultMappingData(IPV4_EID_4),
+                System.currentTimeMillis());
+
+        frozenTimeStamp += 1000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        final int bucketId5 = timeBucketWheel.add(IPV4_EID_5, getDefaultMappingData(IPV4_EID_5),
+                System.currentTimeMillis());
+
+        Assert.assertEquals((bucketId1 - 1 + NUMBER_OF_BUCKETS) % NUMBER_OF_BUCKETS, bucketId2);
+        Assert.assertEquals((bucketId2 - 1 + NUMBER_OF_BUCKETS) % NUMBER_OF_BUCKETS, bucketId3);
+        Assert.assertEquals((bucketId3 - 1 + NUMBER_OF_BUCKETS) % NUMBER_OF_BUCKETS, bucketId4);
+        Assert.assertEquals((bucketId4 - 1 + NUMBER_OF_BUCKETS) % NUMBER_OF_BUCKETS, bucketId5);
+    }
+
+    /**
+     * Tests {@link TimeBucketWheel#add(Eid, MappingData, long)} method for add in some bucket in the middle.
+     */
+    @Test
+    public void mappingAddedInTheProperBucketAddInMiddleTest() throws Exception {
+        PowerMockito.mockStatic(System.class);
+
+        long frozenTimeStamp = System.currentTimeMillis();
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        TimeBucketWheel timeBucketWheel = getDefaultTimeBucketWheel();
+
+        checkTooOldMappingCase(timeBucketWheel);
+
+        checkOlderThanCurrentCase(timeBucketWheel);
+
+    }
+
+    private void checkTooOldMappingCase(TimeBucketWheel timeBucketWheel) throws Exception {
+        long frozenTimeStamp = System.currentTimeMillis() - 10000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        int bucketId = timeBucketWheel.add(IPV4_EID_1, getDefaultMappingData(IPV4_EID_1),
+                System.currentTimeMillis());
+
+        int idOfLastBucketInBucketWheel = Whitebox.invokeMethod(timeBucketWheel,
+                "getLastBucketId");
+
+        Assert.assertEquals(idOfLastBucketInBucketWheel, bucketId);
+
+        //Time resetting to old frozen time
+        frozenTimeStamp = System.currentTimeMillis() + 10000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+    }
+
+    private void checkOlderThanCurrentCase(TimeBucketWheel timeBucketWheel) {
+        long frozenTimeStamp = System.currentTimeMillis();
+
+        int bucketId1 = timeBucketWheel.add(IPV4_EID_1, getDefaultMappingData(IPV4_EID_1),
+                frozenTimeStamp);
+
+        MappingData toBeExpiredMappingData = getDefaultMappingData(IPV4_EID_2);
+
+        int bucketId2 = timeBucketWheel.add(IPV4_EID_2, toBeExpiredMappingData,
+                frozenTimeStamp - 1000);
+
+        Assert.assertEquals((bucketId1 + 1) % NUMBER_OF_BUCKETS, bucketId2);
+
+        //expired at proper time
+        frozenTimeStamp = frozenTimeStamp + 3000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        timeBucketWheel.clearExpiredMappingAndRotate();
+
+        Mockito.verify(mappingSystem).handleSbExpiredMapping(IPV4_EID_2, null, toBeExpiredMappingData);
+    }
+
+
+    /**
+     * Tests {@link TimeBucketWheel#refreshMappping(Eid, MappingData, long, int)} method.
+     * {@link ClassCastException} can be thrown.
+     */
+    @Test
+    public void mappingRefreshedProperlyTest() {
+        PowerMockito.mockStatic(System.class);
+
+        long frozenTimeStamp = System.currentTimeMillis();
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        TimeBucketWheel timeBucketWheel = getDefaultTimeBucketWheel();
+
+        final int bucketId1 = timeBucketWheel.add(IPV4_EID_1, getDefaultMappingData(IPV4_EID_1),
+                System.currentTimeMillis());
+
+        frozenTimeStamp += 2000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        MappingData newMappingData = getDefaultMappingData(IPV4_EID_1);
+
+        int currentBucketId = timeBucketWheel.refreshMappping(IPV4_EID_1, newMappingData,
+                System.currentTimeMillis(), bucketId1);
+
+        List<TimeBucket> bucketList = extractBucketList(timeBucketWheel);
+
+        TimeBucket pastTimeBucket = bucketList.get(bucketId1);
+
+        MappingData oldStoredMappingData = getMappingDataFromTimeBucket(pastTimeBucket, IPV4_EID_1);
+
+        Assert.assertNull(oldStoredMappingData);
+
+        TimeBucket presentTimeBucket = bucketList.get(currentBucketId);
+
+        MappingData newStoredMappingData = getMappingDataFromTimeBucket(presentTimeBucket, IPV4_EID_1);
+
+        Assert.assertEquals(newMappingData, newStoredMappingData);
+    }
+
+    private List<TimeBucket> extractBucketList(TimeBucketWheel timeBucketWheel) {
+        List<TimeBucket> bucketList;
+        try {
+            bucketList = (List<TimeBucket>) Whitebox.getInternalState(timeBucketWheel, "bucketList");
+        } catch (ClassCastException e) {
+            throw e;
+        }
+        return bucketList;
+    }
+
+    private MappingData getMappingDataFromTimeBucket(TimeBucket timeBucket, Eid eid) {
+        ConcurrentHashMap<Eid, MappingData> bucketElements;
+
+        try {
+            bucketElements = (ConcurrentHashMap<Eid, MappingData>) Whitebox.getInternalState(timeBucket,
+                    "bucketElements");
+        } catch (ClassCastException e) {
+            throw e;
+        }
+
+        return bucketElements.get(eid);
+    }
+
+    /**
+     * Tests {@link TimeBucketWheel#clearExpiredMappingAndRotate(long)} method.
+     * {@link ClassCastException} can be thrown.
+     */
+    @Test
+    public void expiredMappingClearedProperlyTest() {
+        PowerMockito.mockStatic(System.class);
+
+        long frozenTimeStamp = System.currentTimeMillis();
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        TimeBucketWheel timeBucketWheel = getDefaultTimeBucketWheel();
+
+        MappingData mappingData = getDefaultMappingData(IPV4_EID_1);
+        timeBucketWheel.add(IPV4_EID_1, mappingData,
+                System.currentTimeMillis());
+
+        frozenTimeStamp = System.currentTimeMillis() + 4000;
+        PowerMockito.when(System.currentTimeMillis()).thenReturn(frozenTimeStamp);
+
+        timeBucketWheel.clearExpiredMappingAndRotate(frozenTimeStamp);
+
+        Mockito.verify(mappingSystem).handleSbExpiredMapping(IPV4_EID_1, null, mappingData);
+    }
+
+
+    private MappingData getDefaultMappingDataWithProperTimestamp(Eid eid, long timeStamp) {
+        MappingData mappingData = getDefaultMappingData(eid);
+        Date date = new Date();
+        date.setTime(timeStamp);
+        mappingData.setTimestamp(date);
+        return mappingData;
+    }
+
+    private static TimeBucketWheel getDefaultTimeBucketWheel() {
+        return new TimeBucketWheel(4, 3000, mappingSystem);
+    }
+
+    private static MappingData getDefaultMappingData(Eid eid) {
+        return getDefaultMappingData(eid,null);
+    }
+
+    private static MappingData getDefaultMappingData(Eid eid, MappingRecord mappingRecord) {
+        if (mappingRecord == null) {
+            mappingRecord = getDefaultMappingRecordBuilder(eid).build();
+        }
+        return new MappingData(mappingRecord, System.currentTimeMillis());
+    }
+
+    private static MappingRecordBuilder getDefaultMappingRecordBuilder(Eid eid) {
+        return new MappingRecordBuilder()
+                .setEid(eid)
+                .setLocatorRecord(new ArrayList<>())
+                .setRecordTtl(2)
+                .setAction(MappingRecord.Action.NativelyForward)
+                .setAuthoritative(true)
+                .setMapVersion((short) 1)
+                .setSiteId(SITE_ID_1)
+                .setSourceRloc(IPV4_SOURCE_RLOC_1)
+                .setTimestamp(new Date().getTime())
+                .setXtrId(XTR_ID_1);
+    }
+}
\ No newline at end of file
index 3b1aa291536a85ffdc58d54d4f50f365e2329b81..764104a1146df2547efb32b3c436060aa3299b4d 100644 (file)
@@ -191,6 +191,7 @@ public class SimpleMapCache implements ILispMapCache {
         table.removeSpecific(key, SubKeys.RECORD);
         table.removeSpecific(key, SubKeys.SRC_RLOCS);
         table.removeSpecific(key, SubKeys.XTRID_RECORDS);
+        table.removeSpecific(key, SubKeys.TIME_BUCKET_ID);
     }
 
     @Override
index 1a2418a7bdca3eee5a2f666ebb17830c5727811a..4329667445f01ad106553f0e60410749267c6f44 100644 (file)
@@ -111,6 +111,7 @@ public class SimpleMapCacheTest {
         Mockito.verify(tableMock).removeSpecific(MaskUtil.normalize(EID_IPV4), SubKeys.RECORD);
         Mockito.verify(tableMock).removeSpecific(MaskUtil.normalize(EID_IPV4), SubKeys.SRC_RLOCS);
         Mockito.verify(tableMock).removeSpecific(MaskUtil.normalize(EID_IPV4), SubKeys.XTRID_RECORDS);
+        Mockito.verify(tableMock).removeSpecific(MaskUtil.normalize(EID_IPV4), SubKeys.TIME_BUCKET_ID);
         Mockito.verifyNoMoreInteractions(tableMock);
     }