Bug 7947: Move subscribers to a separate cache
[lispflowmapping.git] / mappingservice / implementation / src / main / java / org / opendaylight / lispflowmapping / implementation / MappingSystem.java
1 /*
2  * Copyright (c) 2015, 2017 Cisco Systems, Inc.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.lispflowmapping.implementation;
10
11 import com.google.common.collect.Sets;
12 import java.util.ArrayList;
13 import java.util.Date;
14 import java.util.EnumMap;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.concurrent.ConcurrentHashMap;
21 import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.lispflowmapping.config.ConfigIni;
24 import org.opendaylight.lispflowmapping.dsbackend.DataStoreBackEnd;
25 import org.opendaylight.lispflowmapping.implementation.timebucket.implementation.TimeBucketMappingTimeoutService;
26 import org.opendaylight.lispflowmapping.implementation.timebucket.interfaces.ISouthBoundMappingTimeoutService;
27 import org.opendaylight.lispflowmapping.implementation.util.DSBEInputUtil;
28 import org.opendaylight.lispflowmapping.implementation.util.LoggingUtil;
29 import org.opendaylight.lispflowmapping.implementation.util.MSNotificationInputUtil;
30 import org.opendaylight.lispflowmapping.implementation.util.MappingMergeUtil;
31 import org.opendaylight.lispflowmapping.interfaces.dao.ILispDAO;
32 import org.opendaylight.lispflowmapping.interfaces.dao.SubKeys;
33 import org.opendaylight.lispflowmapping.interfaces.dao.Subscriber;
34 import org.opendaylight.lispflowmapping.interfaces.mapcache.IAuthKeyDb;
35 import org.opendaylight.lispflowmapping.interfaces.mapcache.ILispMapCache;
36 import org.opendaylight.lispflowmapping.interfaces.mapcache.IMapCache;
37 import org.opendaylight.lispflowmapping.interfaces.mapcache.IMappingSystem;
38 import org.opendaylight.lispflowmapping.interfaces.mappingservice.IMappingService;
39 import org.opendaylight.lispflowmapping.lisp.type.LispMessage;
40 import org.opendaylight.lispflowmapping.lisp.type.MappingData;
41 import org.opendaylight.lispflowmapping.lisp.util.LispAddressStringifier;
42 import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil;
43 import org.opendaylight.lispflowmapping.lisp.util.MaskUtil;
44 import org.opendaylight.lispflowmapping.lisp.util.SourceDestKeyHelper;
45 import org.opendaylight.lispflowmapping.mapcache.AuthKeyDb;
46 import org.opendaylight.lispflowmapping.mapcache.MultiTableMapCache;
47 import org.opendaylight.lispflowmapping.mapcache.SimpleMapCache;
48 import org.opendaylight.lispflowmapping.mapcache.lisp.LispMapCacheStringifier;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.SimpleAddress;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.ExplicitLocatorPath;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.Ipv4;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.Ipv6;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.ServicePath;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.SourceDestKey;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.explicit.locator.path.explicit.locator.path.Hop;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.inet.binary.types.rev160303.IpAddressBinary;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.Ipv4PrefixBinaryAfi;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.Ipv6PrefixBinaryAfi;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv4PrefixBinary;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv6PrefixBinary;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.XtrId;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecord;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecordBuilder;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.authkey.container.MappingAuthkey;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecord;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecordBuilder;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.rloc.container.Rloc;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChange;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChanged;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingOrigin;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.db.instance.AuthenticationKey;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.db.instance.Mapping;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 /**
78  * The Mapping System coordinates caching of md-sal stored mappings and if so configured enables longest prefix match
79  * mapping lookups.
80  *
81  * @author Florin Coras
82  * @author Lorand Jakab
83  *
84  */
85 public class MappingSystem implements IMappingSystem {
86     private static final Logger LOG = LoggerFactory.getLogger(MappingSystem.class);
87     private static final String AUTH_KEY_TABLE = "authentication";
88     //private static final int TTL_RLOC_TIMED_OUT = 1;
89     private static final int TTL_NO_RLOC_KNOWN = ConfigIni.getInstance().getNegativeMappingTTL();
90     private NotificationPublishService notificationPublishService;
91     private boolean mappingMerge;
92     private ILispDAO dao;
93     private ILispDAO sdao;
94     private ILispMapCache smc;
95     private IMapCache pmc;
96     private ConcurrentHashMap<Eid, Set<Subscriber>> subscriberdb = new ConcurrentHashMap<>();
97     private IAuthKeyDb akdb;
98     private final EnumMap<MappingOrigin, IMapCache> tableMap = new EnumMap<>(MappingOrigin.class);
99     private DataStoreBackEnd dsbe;
100     private boolean isMaster = false;
101
102     private ISouthBoundMappingTimeoutService sbMappingTimeoutService;
103
104     public MappingSystem(ILispDAO dao, boolean iterateMask, NotificationPublishService nps, boolean mappingMerge) {
105         this.dao = dao;
106         this.notificationPublishService = nps;
107         this.mappingMerge = mappingMerge;
108         buildMapCaches();
109
110         sbMappingTimeoutService = new TimeBucketMappingTimeoutService(ConfigIni.getInstance()
111                 .getNumberOfBucketsInTimeBucketWheel(), ConfigIni.getInstance().getRegistrationValiditySb(),
112                 this);
113     }
114
115     public void setDataStoreBackEnd(DataStoreBackEnd dsbe) {
116         this.dsbe = dsbe;
117     }
118
119     @Override
120     public void setMappingMerge(boolean mappingMerge) {
121         this.mappingMerge = mappingMerge;
122     }
123
124     @Override
125     public void setIterateMask(boolean iterate) {
126         LOG.error("Non-longest prefix match lookups are not properly supported, variable is set to true");
127     }
128
129     public void initialize() {
130         restoreDaoFromDatastore();
131     }
132
133     private void buildMapCaches() {
134         /*
135          * There exists a direct relationship between MappingOrigins and the tables that are part of the MappingSystem.
136          * Therefore, if a new origin is added, probably a new table should be instantiated here as well. Here we
137          * instantiate a SimpleMapCache for southbound originated LISP mappings and a MultiTableMapCache for northbound
138          * originated mappings. Use of FlatMapCache would be possible when no longest prefix match is needed at all,
139          * but that option is no longer supported in the code, since it was never tested and may lead to unexpected
140          * results.
141          */
142         sdao = dao.putTable(MappingOrigin.Southbound.toString());
143         pmc = new MultiTableMapCache(dao.putTable(MappingOrigin.Northbound.toString()));
144         smc = new SimpleMapCache(sdao);
145         akdb = new AuthKeyDb(dao.putTable(AUTH_KEY_TABLE));
146         tableMap.put(MappingOrigin.Northbound, pmc);
147         tableMap.put(MappingOrigin.Southbound, smc);
148     }
149
150     public void addMapping(MappingOrigin origin, Eid key, MappingData mappingData) {
151
152         sbMappingTimeoutService.removeExpiredMappings();
153
154         if (mappingData == null) {
155             LOG.warn("addMapping() called with null mapping, ignoring");
156             return;
157         }
158
159         if (origin == MappingOrigin.Southbound) {
160             XtrId xtrId = mappingData.getXtrId();
161             if (xtrId == null && mappingMerge && mappingData.isMergeEnabled()) {
162                 LOG.warn("addMapping() called will null xTR-ID in MappingRecord, while merge is set, ignoring");
163                 return;
164             }
165             if (xtrId != null && mappingMerge) {
166                 if (mappingData.isMergeEnabled()) {
167                     smc.addMapping(key, xtrId, mappingData);
168                     handleMergedMapping(key);
169                     return;
170                 } else {
171                     clearPresentXtrIdMappings(key);
172                     smc.addMapping(key, xtrId, mappingData);
173                 }
174             }
175             addOrRefreshMappingInTimeoutService(key, mappingData);
176         }
177
178         tableMap.get(origin).addMapping(key, mappingData);
179     }
180
181     @SuppressWarnings("unchecked")
182     private void clearPresentXtrIdMappings(Eid key) {
183         List<MappingData> allXtrMappingList = (List<MappingData>) (List<?>) smc.getAllXtrIdMappings(key);
184
185         if (((MappingData) smc.getMapping(key, (XtrId) null)).isMergeEnabled()) {
186             LOG.trace("Different xTRs have different merge configuration!");
187         }
188
189         for (MappingData mappingData : allXtrMappingList) {
190             removeSbXtrIdSpecificMapping(key, mappingData.getXtrId(), mappingData);
191         }
192     }
193
194     private void addOrRefreshMappingInTimeoutService(Eid key, MappingData mappingData) {
195         Integer oldBucketId = (Integer) smc.getData(key, SubKeys.TIME_BUCKET_ID);
196         Integer updatedBucketId;
197
198         if (oldBucketId != null) {
199             //refresh mapping
200             updatedBucketId = sbMappingTimeoutService.refreshMapping(key, mappingData, oldBucketId);
201         } else {
202             updatedBucketId = sbMappingTimeoutService.addMapping(key, mappingData);
203         }
204
205         smc.addData(key, SubKeys.TIME_BUCKET_ID, updatedBucketId);
206     }
207
208     @Override
209     public MappingData addNegativeMapping(Eid key) {
210         MappingRecord mapping = buildNegativeMapping(key);
211         MappingData mappingData = new MappingData(mapping);
212         LOG.debug("Adding negative mapping for EID {}", LispAddressStringifier.getString(key));
213         LOG.trace(mappingData.getString());
214         smc.addMapping(mapping.getEid(), mappingData);
215         dsbe.addMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, mapping.getEid(), null, mappingData));
216         return mappingData;
217     }
218
219     private MappingRecord buildNegativeMapping(Eid eid) {
220         MappingRecordBuilder recordBuilder = new MappingRecordBuilder();
221         recordBuilder.setAuthoritative(false);
222         recordBuilder.setMapVersion((short) 0);
223         recordBuilder.setEid(eid);
224         if (eid.getAddressType().equals(Ipv4PrefixBinaryAfi.class)
225                 || eid.getAddressType().equals(Ipv6PrefixBinaryAfi.class)) {
226             Eid widestNegativePrefix = getWidestNegativePrefix(eid);
227             if (widestNegativePrefix != null) {
228                 recordBuilder.setEid(widestNegativePrefix);
229             }
230         }
231         recordBuilder.setAction(LispMessage.NEGATIVE_MAPPING_ACTION);
232         //if (getAuthenticationKey(eid) != null) {
233         //    recordBuilder.setRecordTtl(TTL_RLOC_TIMED_OUT);
234         //} else {
235         recordBuilder.setRecordTtl(TTL_NO_RLOC_KNOWN);
236         //}
237         return recordBuilder.build();
238     }
239
240     /*
241      * Since this method is only called when there is a hit in the southbound Map-Register cache, and that cache is
242      * not used when merge is on, it's OK to ignore the effects of timestamp changes on merging for now.
243      */
244     public void refreshMappingRegistration(Eid key, XtrId xtrId, Long timestamp) {
245
246         sbMappingTimeoutService.removeExpiredMappings();
247
248         if (timestamp == null) {
249             timestamp = System.currentTimeMillis();
250         }
251         MappingData mappingData = (MappingData) smc.getMapping(null, key);
252         if (mappingData != null) {
253             mappingData.setTimestamp(new Date(timestamp));
254             addOrRefreshMappingInTimeoutService(key, mappingData);
255         } else {
256             LOG.warn("Could not update timestamp for EID {}, no mapping found", LispAddressStringifier.getString(key));
257         }
258         if (mappingMerge && xtrId != null) {
259             MappingData xtrIdMappingData = (MappingData) smc.getMapping(key, xtrId);
260             if (xtrIdMappingData != null) {
261                 xtrIdMappingData.setTimestamp(new Date(timestamp));
262             } else {
263                 LOG.warn("Could not update timestamp for EID {} xTR-ID {}, no mapping found",
264                         LispAddressStringifier.getString(key), LispAddressStringifier.getString(xtrId));
265             }
266         }
267     }
268
269     private MappingData updateServicePathMappingRecord(MappingData mappingData, Eid eid) {
270         // keep properties of original record
271         MappingRecordBuilder recordBuilder = new MappingRecordBuilder(mappingData.getRecord());
272         recordBuilder.setLocatorRecord(new ArrayList<LocatorRecord>());
273
274         // there should only be one locator record
275         if (mappingData.getRecord().getLocatorRecord().size() != 1) {
276             LOG.warn("MappingRecord associated to ServicePath EID has more than one locator!");
277             return mappingData;
278         }
279
280         LocatorRecord locatorRecord = mappingData.getRecord().getLocatorRecord().get(0);
281         long serviceIndex = ((ServicePath) eid.getAddress()).getServicePath().getServiceIndex();
282         int index = LispAddressUtil.STARTING_SERVICE_INDEX - (int) serviceIndex;
283         Rloc rloc = locatorRecord.getRloc();
284         if (rloc.getAddress() instanceof Ipv4 || rloc.getAddress() instanceof Ipv6) {
285             if (index != 0) {
286                 LOG.warn("Service Index should be 255 for simple IP RLOCs!");
287             }
288             return mappingData;
289         } else if (rloc.getAddress() instanceof ExplicitLocatorPath) {
290             ExplicitLocatorPath elp = (ExplicitLocatorPath) rloc.getAddress();
291             List<Hop> hops = elp.getExplicitLocatorPath().getHop();
292
293             if (index < 0 || index > hops.size())  {
294                 LOG.warn("Service Index out of bounds!");
295                 return mappingData;
296             }
297
298             SimpleAddress nextHop = hops.get(index).getAddress();
299             LocatorRecordBuilder lrb = new LocatorRecordBuilder(locatorRecord);
300             lrb.setRloc(LispAddressUtil.toRloc(nextHop));
301             recordBuilder.getLocatorRecord().add(lrb.build());
302             return new MappingData(recordBuilder.build());
303         } else {
304             LOG.warn("Nothing to do with ServicePath mapping record");
305             return mappingData;
306         }
307     }
308
309     private MappingData handleMergedMapping(Eid key) {
310         List<MappingData> expiredMappingDataList = new ArrayList<>();
311         Set<IpAddressBinary> sourceRlocs = new HashSet<>();
312
313         MappingData mergedMappingData = MappingMergeUtil.mergeXtrIdMappings(smc.getAllXtrIdMappings(key),
314                 expiredMappingDataList, sourceRlocs);
315
316         for (MappingData mappingData : expiredMappingDataList) {
317             removeSbXtrIdSpecificMapping(key, mappingData.getXtrId(), mappingData);
318         }
319
320         if (mergedMappingData != null) {
321             smc.addMapping(key, mergedMappingData, sourceRlocs);
322             dsbe.addMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key, mergedMappingData));
323             addOrRefreshMappingInTimeoutService(key, mergedMappingData);
324         } else {
325             removeSbMapping(key, mergedMappingData);
326         }
327         return mergedMappingData;
328     }
329
330     @Override
331     public MappingData getMapping(Eid src, Eid dst) {
332         // NOTE: Currently we have two lookup algorithms implemented, which are configurable
333
334         if (ConfigIni.getInstance().getLookupPolicy() == IMappingService.LookupPolicy.NB_AND_SB) {
335             return getMappingNbSbIntersection(src, dst);
336         } else {
337             return getMappingNbFirst(src, dst);
338         }
339     }
340
341     @Override
342     public MappingData getMapping(Eid dst) {
343         return getMapping((Eid) null, dst);
344     }
345
346     @Override
347     public MappingData getMapping(Eid src, Eid dst, XtrId xtrId) {
348         // Note: If xtrId is null, we need to go through regular policy checking else Policy doesn't matter
349
350         if (xtrId == null) {
351             return getMapping(src, dst);
352         }
353
354         return getSbMappingWithExpiration(src, dst, xtrId);
355     }
356
357     @Override
358     public MappingData getMapping(MappingOrigin origin, Eid key) {
359         if (origin.equals(MappingOrigin.Southbound)) {
360             return getSbMappingWithExpiration(null, key, null);
361         }
362         return (MappingData) tableMap.get(origin).getMapping(null, key);
363     }
364
365     private MappingData getMappingNbFirst(Eid src, Eid dst) {
366
367         // Default lookup policy is northboundFirst
368         //lookupPolicy == NB_FIRST
369
370         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
371
372         if (nbMappingData == null) {
373             return getSbMappingWithExpiration(src, dst, null);
374         }
375         if (dst.getAddress() instanceof ServicePath) {
376             return updateServicePathMappingRecord(nbMappingData, dst);
377         }
378         return nbMappingData;
379     }
380
381     private MappingData getMappingNbSbIntersection(Eid src, Eid dst) {
382         //lookupPolicy == NB_AND_SB, we return intersection
383         //of NB and SB mappings, or NB mapping if intersection is empty.
384
385         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
386         if (nbMappingData == null) {
387             return nbMappingData;
388         }
389         // no intersection for Service Path mappings
390         if (dst.getAddress() instanceof ServicePath) {
391             return updateServicePathMappingRecord(nbMappingData, dst);
392         }
393         MappingData sbMappingData = getSbMappingWithExpiration(src, dst, null);
394         if (sbMappingData == null) {
395             return nbMappingData;
396         }
397         // both NB and SB mappings exist. Compute intersection of the mappings
398         return MappingMergeUtil.computeNbSbIntersection(nbMappingData, sbMappingData);
399     }
400
401     private MappingData getSbMappingWithExpiration(Eid src, Eid dst, XtrId xtrId) {
402         MappingData mappingData = (MappingData) smc.getMapping(dst, xtrId);
403         if (mappingData != null && MappingMergeUtil.mappingIsExpired(mappingData)) {
404             return handleSbExpiredMapping(dst, xtrId, mappingData);
405         } else {
406             return mappingData;
407         }
408     }
409
410     public MappingData handleSbExpiredMapping(Eid key, XtrId xtrId, MappingData mappingData) {
411         if (mappingMerge && mappingData.isMergeEnabled()) {
412             return handleMergedMapping(key);
413         }
414
415         if (xtrId != null) {
416             removeSbXtrIdSpecificMapping(key, xtrId, mappingData);
417         } else {
418             removeSbMapping(key, mappingData);
419         }
420         return null;
421     }
422
423     private void removeSbXtrIdSpecificMapping(Eid key, XtrId xtrId, MappingData mappingData) {
424         smc.removeMapping(key, xtrId);
425         dsbe.removeXtrIdMapping(DSBEInputUtil.toXtrIdMapping(mappingData));
426     }
427
428     private void removeSbMapping(Eid key, MappingData mappingData) {
429         if (mappingData != null && mappingData.getXtrId() != null) {
430             removeSbXtrIdSpecificMapping(key, mappingData.getXtrId(), mappingData);
431         }
432
433         removeFromSbTimeoutService(key);
434         Set<Subscriber> subscribers = getSubscribers(key);
435         smc.removeMapping(key);
436         dsbe.removeMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key, mappingData));
437         notifyChange(mappingData, subscribers, null, MappingChange.Removed);
438         removeSubscribers(key);
439     }
440
441     private void removeFromSbTimeoutService(Eid key) {
442         Integer bucketId = (Integer) smc.getData(key, SubKeys.TIME_BUCKET_ID);
443         if (bucketId != null) {
444             sbMappingTimeoutService.removeMappingFromTimeoutService(key, bucketId);
445         }
446     }
447
448     @Override
449     public Eid getWidestNegativePrefix(Eid key) {
450         if (!MaskUtil.isMaskable(key.getAddress())) {
451             LOG.warn("Widest negative prefix only makes sense for maskable addresses!");
452             return null;
453         }
454
455         // We assume that ILispMapCache#getWidestNegativeMapping() returns null for positive mappings, and 0/0
456         // for empty cache.
457         Eid nbPrefix = pmc.getWidestNegativeMapping(key);
458         if (nbPrefix == null) {
459             LOG.trace("getWidestNegativePrefix NB: positive mapping, returning null");
460             return null;
461         }
462         if (LOG.isTraceEnabled()) {
463             LOG.trace("getWidestNegativePrefix NB: {}", LispAddressStringifier.getString(nbPrefix));
464         }
465
466         Eid sbPrefix = smc.getWidestNegativeMapping(key);
467         if (sbPrefix == null) {
468             LOG.trace("getWidestNegativePrefix SB: positive mapping, returning null");
469             return null;
470         }
471         if (LOG.isTraceEnabled()) {
472             LOG.trace("getWidestNegativePrefix SB: {}", LispAddressStringifier.getString(sbPrefix));
473         }
474
475         // since prefixes overlap, just return the more specific (larger mask)
476         if (LispAddressUtil.getIpPrefixMask(nbPrefix) < LispAddressUtil.getIpPrefixMask(sbPrefix)) {
477             return sbPrefix;
478         } else {
479             return nbPrefix;
480         }
481     }
482
483     @Override
484     public void removeMapping(MappingOrigin origin, Eid key) {
485         Set<Subscriber> subscribers = null;
486         Set<Subscriber> dstSubscribers = null;
487         MappingData mapping = (MappingData) tableMap.get(origin).getMapping(null, key);
488
489         if (LOG.isDebugEnabled()) {
490             LOG.debug("Removing mapping for EID {} from {}",
491                     LispAddressStringifier.getString(key), origin);
492         }
493         if (LOG.isTraceEnabled()) {
494             LOG.trace(mapping.getString());
495         }
496
497         MappingData notificationMapping = mapping;
498
499         if (mapping != null) {
500             subscribers = getSubscribers(key);
501             // For SrcDst LCAF also send SMRs to Dst prefix
502             if (key.getAddress() instanceof SourceDestKey) {
503                 Eid dstAddr = SourceDestKeyHelper.getDstBinary(key);
504                 dstSubscribers = getSubscribers(dstAddr);
505                 if (!(mapping.getRecord().getEid().getAddress() instanceof SourceDestKey)) {
506                     notificationMapping = new MappingData(new MappingRecordBuilder().setEid(key).build());
507                 }
508             }
509         }
510
511         removeSubscribers(key);
512
513         if (origin == MappingOrigin.Southbound) {
514             removeFromSbTimeoutService(key);
515         }
516
517         if (origin == MappingOrigin.Southbound && mapping != null && mapping.isPositive().or(false)) {
518             mergeNegativePrefixes(key);
519         } else {
520             // mergeNegativePrefixes() above removes the mapping, so addNegativeMapping() will work correctly
521             tableMap.get(origin).removeMapping(key);
522         }
523
524         if (notificationMapping != null) {
525             notifyChange(notificationMapping, subscribers, dstSubscribers, MappingChange.Removed);
526         }
527     }
528
529     private void notifyChange(MappingData mapping, Set<Subscriber> subscribers, Set<Subscriber> dstSubscribers,
530             MappingChange mappingChange) {
531         MappingChanged notification = MSNotificationInputUtil.toMappingChanged(mapping, subscribers, dstSubscribers,
532                 mappingChange);
533         try {
534             notificationPublishService.putNotification(notification);
535         } catch (InterruptedException e) {
536             LOG.warn("Notification publication interrupted!");
537         }
538     }
539
540
541     /*
542      * Merges adjacent negative prefixes and notifies their subscribers.
543      */
544     private void mergeNegativePrefixes(Eid eid) {
545         LOG.debug("Merging negative prefixes starting from EID {}", LispAddressStringifier.getString(eid));
546
547         // If we delete nodes while we walk up the radix trie the algorithm will give incorrect results, because
548         // removals rearrange relationships in the trie. So we save prefixes to be removed into a HashMap.
549         Map<Eid, MappingData> mergedMappings = new HashMap<>();
550
551         Eid currentNode = smc.getSiblingPrefix(eid);
552         MappingData mapping = (MappingData) smc.getMapping(null, currentNode);
553         if (mapping != null && mapping.isNegative().or(false)) {
554             mergedMappings.put(currentNode, mapping);
555         } else {
556             return;
557         }
558
559         Eid previousNode = currentNode;
560         currentNode = smc.getVirtualParentSiblingPrefix(currentNode);
561         while (currentNode != null) {
562             mapping = (MappingData) smc.getMapping(null, currentNode);
563             if (mapping != null && mapping.isNegative().or(false)) {
564                 mergedMappings.put(currentNode, mapping);
565             } else {
566                 break;
567             }
568             previousNode = currentNode;
569             currentNode = smc.getVirtualParentSiblingPrefix(previousNode);
570         }
571
572         for (Eid key : mergedMappings.keySet()) {
573             removeSbMapping(key, mergedMappings.get(key));
574         }
575         smc.removeMapping(eid);
576
577         addNegativeMapping(getVirtualParent(previousNode));
578     }
579
580     private static Eid getVirtualParent(Eid eid) {
581         if (eid.getAddress() instanceof Ipv4PrefixBinary) {
582             Ipv4PrefixBinary prefix = (Ipv4PrefixBinary) eid.getAddress();
583             short parentPrefixLength = (short) (prefix.getIpv4MaskLength() - 1);
584             byte[] parentPrefix = MaskUtil.normalizeByteArray(prefix.getIpv4AddressBinary().getValue(),
585                     parentPrefixLength);
586             return LispAddressUtil.asIpv4PrefixBinaryEid(eid, parentPrefix, parentPrefixLength);
587         } else if (eid.getAddress() instanceof Ipv6PrefixBinary) {
588             Ipv6PrefixBinary prefix = (Ipv6PrefixBinary) eid.getAddress();
589             short parentPrefixLength = (short) (prefix.getIpv6MaskLength() - 1);
590             byte[] parentPrefix = MaskUtil.normalizeByteArray(prefix.getIpv6AddressBinary().getValue(),
591                     parentPrefixLength);
592             return LispAddressUtil.asIpv6PrefixBinaryEid(eid, parentPrefix, parentPrefixLength);
593         }
594         return null;
595     }
596
597     @Override
598     public synchronized void subscribe(Subscriber subscriber, Eid subscribedEid) {
599         Set<Subscriber> subscribers = getSubscribers(subscribedEid);
600         if (subscribers == null) {
601             subscribers = Sets.newConcurrentHashSet();
602         } else if (subscribers.contains(subscriber)) {
603             // If there is an entry already for this subscriber, remove it, so that it gets the new timestamp
604             subscribers.remove(subscriber);
605         }
606         if (LOG.isDebugEnabled()) {
607             LOG.debug("Adding new subscriber {} for EID {}", subscriber.getString(),
608                     LispAddressStringifier.getString(subscribedEid));
609         }
610         subscribers.add(subscriber);
611         addSubscribers(subscribedEid, subscribers);
612     }
613
614     private void addSubscribers(Eid address, Set<Subscriber> subscribers) {
615         LoggingUtil.logSubscribers(LOG, address, subscribers);
616         subscriberdb.put(address, subscribers);
617     }
618
619     @Override
620     public Set<Subscriber> getSubscribers(Eid address) {
621         if (LOG.isDebugEnabled()) {
622             LOG.debug("Retrieving subscribers for EID {}", LispAddressStringifier.getString(address));
623         }
624
625         Set<Subscriber> subscribers = subscriberdb.get(address);
626         LoggingUtil.logSubscribers(LOG, address, subscribers);
627         return subscribers;
628     }
629
630     private void removeSubscribers(Eid address) {
631         if (LOG.isDebugEnabled()) {
632             LOG.debug("Removing subscribers for EID {}", LispAddressStringifier.getString(address));
633         }
634         subscriberdb.remove(address);
635     }
636
637     @Override
638     public void addAuthenticationKey(Eid key, MappingAuthkey authKey) {
639         LOG.debug("Adding authentication key '{}' with key-ID {} for {}", authKey.getKeyString(), authKey.getKeyType(),
640                 LispAddressStringifier.getString(key));
641         akdb.addAuthenticationKey(key, authKey);
642     }
643
644     @Override
645     public MappingAuthkey getAuthenticationKey(Eid key) {
646         if (LOG.isDebugEnabled()) {
647             LOG.debug("Retrieving authentication key for {}", LispAddressStringifier.getString(key));
648         }
649         return akdb.getAuthenticationKey(key);
650     }
651
652     @Override
653     public void removeAuthenticationKey(Eid key) {
654         if (LOG.isDebugEnabled()) {
655             LOG.debug("Removing authentication key for {}", LispAddressStringifier.getString(key));
656         }
657         akdb.removeAuthenticationKey(key);
658     }
659
660     @Override
661     public void addData(MappingOrigin origin, Eid key, String subKey, Object data) {
662         if (LOG.isDebugEnabled()) {
663             LOG.debug("Add data of {} for key {} and subkey {}", data.getClass(),
664                     LispAddressStringifier.getString(key), subKey);
665         }
666         tableMap.get(origin).addData(key, subKey, data);
667     }
668
669     @Override
670     public Object getData(MappingOrigin origin, Eid key, String subKey) {
671         if (LOG.isDebugEnabled()) {
672             LOG.debug("Retrieving data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
673         }
674         return tableMap.get(origin).getData(key, subKey);
675     }
676
677     @Override
678     public void removeData(MappingOrigin origin, Eid key, String subKey) {
679         if (LOG.isDebugEnabled()) {
680             LOG.debug("Removing data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
681         }
682         tableMap.get(origin).removeData(key, subKey);
683     }
684
685     @Override
686     public Eid getParentPrefix(Eid key) {
687         return smc.getParentPrefix(key);
688     }
689
690
691     /**
692      * Restore all mappings and keys from mdsal datastore.
693      */
694     private void restoreDaoFromDatastore() {
695         List<AuthenticationKey> authKeys = dsbe.getAllAuthenticationKeys();
696         List<Mapping> mappings = dsbe.getAllMappings(LogicalDatastoreType.CONFIGURATION);
697
698         /*
699          * XXX By default, the operational datastore is not persisted to disk, either at run-time, or on shutdown,
700          * so the following will have no effect (getLastUpdateTimestamp() will fail, since it's reading from
701          * the operational datastore, and even if it didn't getAllMappings() will fail anyway). According to rovarga it
702          * should be possible to turn on persistence for the operational datastore editing
703          * etc/opendaylight/karaf/05-clustering.xml, by setting <persistence>true</persistence>. At the time of writing
704          * the below code block that didn't seem to work though.
705          */
706         Long lastUpdateTimestamp = dsbe.getLastUpdateTimestamp();
707         if (lastUpdateTimestamp != null && System.currentTimeMillis() - lastUpdateTimestamp
708                 > ConfigIni.getInstance().getRegistrationValiditySb()) {
709             LOG.warn("Restore threshold passed, not restoring operational datastore into DAO");
710         } else {
711             mappings.addAll(dsbe.getAllMappings(LogicalDatastoreType.OPERATIONAL));
712         }
713         dsbe.removeLastUpdateTimestamp();
714
715         LOG.info("Restoring {} mappings and {} keys from datastore into DAO", mappings.size(), authKeys.size());
716
717         for (Mapping mapping : mappings) {
718             addMapping(mapping.getOrigin(), mapping.getMappingRecord().getEid(),
719                     new MappingData(mapping.getMappingRecord()));
720         }
721
722         for (AuthenticationKey authKey : authKeys) {
723             addAuthenticationKey(authKey.getEid(), authKey.getMappingAuthkey());
724         }
725     }
726
727     public void destroy() {
728         LOG.info("Mapping System is being destroyed!");
729         dsbe.saveLastUpdateTimestamp();
730     }
731
732     @Override
733     public String printMappings() {
734         final StringBuffer sb = new StringBuffer();
735         sb.append("Policy map-cache\n----------------\n");
736         sb.append(pmc.printMappings());
737         sb.append("\nSouthbound map-cache\n--------------------\n");
738         sb.append(smc.printMappings());
739         return sb.toString();
740     }
741
742     @Override
743     public String prettyPrintMappings() {
744         final StringBuffer sb = new StringBuffer();
745         sb.append("Policy map-cache\n----------------\n");
746         sb.append(pmc.prettyPrintMappings());
747         sb.append("\nSouthbound map-cache\n--------------------\n");
748         sb.append(smc.prettyPrintMappings());
749         sb.append("\nSubscribers\n-----------\n");
750         sb.append(prettyPrintSubscribers(subscriberdb));
751         return sb.toString();
752     }
753
754     private static String prettyPrintSubscribers(Map<Eid, Set<Subscriber>> subscribers) {
755         final StringBuffer sb = new StringBuffer();
756         for (Eid eid: subscribers.keySet()) {
757             sb.append("\n  ");
758             sb.append(LispAddressStringifier.getString(eid));
759             sb.append("\n");
760             sb.append(LispMapCacheStringifier.prettyPrintSubscriberSet(subscribers.get(eid), 4));
761             sb.append("\n");
762         }
763         return sb.toString();
764     }
765
766     @Override
767     public String printKeys() {
768         return akdb.printKeys();
769     }
770
771     @Override
772     public String prettyPrintKeys() {
773         return akdb.prettyPrintKeys();
774     }
775
776     public void cleanCaches() {
777         dao.removeAll();
778         subscriberdb.clear();
779         buildMapCaches();
780     }
781
782     public void cleanSBMappings() {
783         smc = new SimpleMapCache(sdao);
784     }
785
786     @Override
787     public void setIsMaster(boolean isMaster) {
788         this.isMaster = isMaster;
789     }
790
791     @Override
792     public boolean isMaster() {
793         return isMaster;
794     }
795 }