SMR parent prefix
[lispflowmapping.git] / mappingservice / implementation / src / main / java / org / opendaylight / lispflowmapping / implementation / MappingSystem.java
1 /*
2  * Copyright (c) 2015 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 java.util.ArrayList;
12 import java.util.Date;
13 import java.util.EnumMap;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.lispflowmapping.config.ConfigIni;
19 import org.opendaylight.lispflowmapping.dsbackend.DataStoreBackEnd;
20 import org.opendaylight.lispflowmapping.implementation.util.DSBEInputUtil;
21 import org.opendaylight.lispflowmapping.implementation.util.MappingMergeUtil;
22 import org.opendaylight.lispflowmapping.interfaces.dao.ILispDAO;
23 import org.opendaylight.lispflowmapping.interfaces.mapcache.IAuthKeyDb;
24 import org.opendaylight.lispflowmapping.interfaces.mapcache.ILispMapCache;
25 import org.opendaylight.lispflowmapping.interfaces.mapcache.IMapCache;
26 import org.opendaylight.lispflowmapping.interfaces.mapcache.IMappingSystem;
27 import org.opendaylight.lispflowmapping.interfaces.mappingservice.IMappingService;
28 import org.opendaylight.lispflowmapping.lisp.type.MappingData;
29 import org.opendaylight.lispflowmapping.lisp.util.LispAddressStringifier;
30 import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil;
31 import org.opendaylight.lispflowmapping.mapcache.AuthKeyDb;
32 import org.opendaylight.lispflowmapping.mapcache.MultiTableMapCache;
33 import org.opendaylight.lispflowmapping.mapcache.SimpleMapCache;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.SimpleAddress;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.ExplicitLocatorPath;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.Ipv4;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.Ipv6;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.ServicePath;
39 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;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.inet.binary.types.rev160303.IpAddressBinary;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.SiteId;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.XtrId;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecord;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.locatorrecords.LocatorRecordBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.authkey.container.MappingAuthkey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecordBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.rloc.container.Rloc;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingOrigin;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.db.instance.AuthenticationKey;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.db.instance.Mapping;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The Mapping System coordinates caching of md-sal stored mappings and if so configured enables longest prefix match
57  * mapping lookups.
58  *
59  * @author Florin Coras
60  *
61  */
62 public class MappingSystem implements IMappingSystem {
63     private static final Logger LOG = LoggerFactory.getLogger(MappingSystem.class);
64     private static final String AUTH_KEY_TABLE = "authentication";
65     private boolean notificationService;
66     private boolean mappingMerge;
67     private ILispDAO dao;
68     private ILispDAO sdao;
69     private ILispMapCache smc;
70     private IMapCache pmc;
71     private IAuthKeyDb akdb;
72     private final EnumMap<MappingOrigin, IMapCache> tableMap = new EnumMap<>(MappingOrigin.class);
73     private DataStoreBackEnd dsbe;
74     private boolean isMaster = false;
75
76     public MappingSystem(ILispDAO dao, boolean iterateMask, boolean notifications, boolean mappingMerge) {
77         this.dao = dao;
78         this.notificationService = notifications;
79         this.mappingMerge = mappingMerge;
80         buildMapCaches();
81     }
82
83     public void setDataStoreBackEnd(DataStoreBackEnd dsbe) {
84         this.dsbe = dsbe;
85     }
86
87     @Override
88     public void setMappingMerge(boolean mappingMerge) {
89         this.mappingMerge = mappingMerge;
90     }
91
92     @Override
93     public void setIterateMask(boolean iterate) {
94         LOG.error("Non-longest prefix match lookups are not properly supported, variable is set to true");
95     }
96
97     public void initialize() {
98         restoreDaoFromDatastore();
99     }
100
101     private void buildMapCaches() {
102         /*
103          * There exists a direct relationship between MappingOrigins and the tables that are part of the MappingSystem.
104          * Therefore, if a new origin is added, probably a new table should be instantiated here as well. Here we
105          * instantiate a SimpleMapCache for southbound originated LISP mappings and a MultiTableMapCache for northbound
106          * originated mappings. Use of FlatMapCache would be possible when no longest prefix match is needed at all,
107          * but that option is no longer supported in the code, since it was never tested and may lead to unexpected
108          * results.
109          */
110         sdao = dao.putTable(MappingOrigin.Southbound.toString());
111         pmc = new MultiTableMapCache(dao.putTable(MappingOrigin.Northbound.toString()));
112         smc = new SimpleMapCache(sdao);
113         akdb = new AuthKeyDb(dao.putTable(AUTH_KEY_TABLE));
114         tableMap.put(MappingOrigin.Northbound, pmc);
115         tableMap.put(MappingOrigin.Southbound, smc);
116     }
117
118     public void addMapping(MappingOrigin origin, Eid key, MappingData mappingData) {
119         if (mappingData == null) {
120             LOG.warn("addMapping() called with null mapping, ignoring");
121             return;
122         }
123
124         if (origin == MappingOrigin.Southbound) {
125             XtrId xtrId = mappingData.getXtrId();
126             if (xtrId == null && mappingMerge && mappingData.isMergeEnabled()) {
127                 LOG.warn("addMapping() called will null xTR-ID in MappingRecord, while merge is set, ignoring");
128                 return;
129             }
130             if (xtrId != null && mappingMerge) {
131                 smc.addMapping(key, xtrId, mappingData);
132                 if (mappingData.isMergeEnabled()) {
133                     mergeMappings(key);
134                     return;
135                 }
136             }
137         }
138
139         tableMap.get(origin).addMapping(key, mappingData);
140     }
141
142     /*
143      * Since this method is only called when there is a hit in the southbound Map-Register cache, and that cache is
144      * not used when merge is on, it's OK to ignore the effects of timestamp changes on merging for now.
145      */
146     public void refreshMappingRegistration(Eid key, XtrId xtrId, Long timestamp) {
147         if (timestamp == null) {
148             timestamp = System.currentTimeMillis();
149         }
150         MappingData mappingData = (MappingData) smc.getMapping(null, key);
151         if (mappingData != null) {
152             mappingData.setTimestamp(new Date(timestamp));
153         } else {
154             LOG.warn("Could not update timestamp for EID {}, no mapping found", LispAddressStringifier.getString(key));
155         }
156         if (xtrId != null) {
157             MappingData xtrIdMappingData = (MappingData) smc.getMapping(key, xtrId);
158             if (xtrIdMappingData != null) {
159                 xtrIdMappingData.setTimestamp(new Date(timestamp));
160             } else {
161                 LOG.warn("Could not update timestamp for EID {} xTR-ID {}, no mapping found",
162                         LispAddressStringifier.getString(key), LispAddressStringifier.getString(xtrId));
163             }
164         }
165     }
166
167     private MappingData updateServicePathMappingRecord(MappingData mappingData, Eid eid) {
168         // keep properties of original record
169         MappingRecordBuilder recordBuilder = new MappingRecordBuilder(mappingData.getRecord());
170         recordBuilder.setLocatorRecord(new ArrayList<LocatorRecord>());
171
172         // there should only be one locator record
173         if (mappingData.getRecord().getLocatorRecord().size() != 1) {
174             LOG.warn("MappingRecord associated to ServicePath EID has more than one locator!");
175             return mappingData;
176         }
177
178         LocatorRecord locatorRecord = mappingData.getRecord().getLocatorRecord().get(0);
179         long serviceIndex = ((ServicePath) eid.getAddress()).getServicePath().getServiceIndex();
180         int index = LispAddressUtil.STARTING_SERVICE_INDEX - (int) serviceIndex;
181         Rloc rloc = locatorRecord.getRloc();
182         if (rloc.getAddress() instanceof Ipv4 || rloc.getAddress() instanceof Ipv6) {
183             if (index != 0) {
184                 LOG.warn("Service Index should be 255 for simple IP RLOCs!");
185             }
186             return mappingData;
187         } else if (rloc.getAddress() instanceof ExplicitLocatorPath) {
188             ExplicitLocatorPath elp = (ExplicitLocatorPath) rloc.getAddress();
189             List<Hop> hops = elp.getExplicitLocatorPath().getHop();
190
191             if (index < 0 || index > hops.size())  {
192                 LOG.warn("Service Index out of bounds!");
193                 return mappingData;
194             }
195
196             SimpleAddress nextHop = hops.get(index).getAddress();
197             LocatorRecordBuilder lrb = new LocatorRecordBuilder(locatorRecord);
198             lrb.setRloc(LispAddressUtil.toRloc(nextHop));
199             recordBuilder.getLocatorRecord().add(lrb.build());
200             return new MappingData(recordBuilder.build());
201         } else {
202             LOG.warn("Nothing to do with ServicePath mapping record");
203             return mappingData;
204         }
205     }
206
207     private MappingData mergeMappings(Eid key) {
208         List<XtrId> expiredMappings = new ArrayList<>();
209         Set<IpAddressBinary> sourceRlocs = new HashSet<>();
210         MappingData mergedMappingData = MappingMergeUtil.mergeXtrIdMappings(smc.getAllXtrIdMappings(key),
211                 expiredMappings, sourceRlocs);
212         smc.removeXtrIdMappings(key, expiredMappings);
213         for (XtrId xtrId : expiredMappings) {
214             dsbe.removeXtrIdMapping(DSBEInputUtil.toXtrIdMapping(xtrId));
215         }
216         if (mergedMappingData != null) {
217             smc.addMapping(key, mergedMappingData, sourceRlocs);
218             dsbe.addMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key,
219                     mergedMappingData.getRecord().getSiteId(), mergedMappingData));
220         } else {
221             smc.removeMapping(key);
222             dsbe.removeMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, key));
223         }
224         return mergedMappingData;
225     }
226
227     @Override
228     public MappingData getMapping(Eid src, Eid dst) {
229         // NOTE: Currently we have two lookup algorithms implemented, which are configurable
230
231         if (ConfigIni.getInstance().getLookupPolicy() == IMappingService.LookupPolicy.NB_AND_SB) {
232             return getMappingNbSbIntersection(src, dst);
233         } else {
234             return getMappingNbFirst(src, dst);
235         }
236     }
237
238     @Override
239     public MappingData getMapping(Eid dst) {
240         return getMapping((Eid) null, dst);
241     }
242
243     @Override
244     public MappingData getMapping(Eid src, Eid dst, XtrId xtrId) {
245         // Note: If xtrId is null, we need to go through regular policy checking else Policy doesn't matter
246
247         if (xtrId == null) {
248             return getMapping(src, dst);
249         }
250
251         return getSbMappingWithExpiration(src, dst, xtrId);
252     }
253
254     @Override
255     public MappingData getMapping(MappingOrigin origin, Eid key) {
256         if (origin.equals(MappingOrigin.Southbound)) {
257             return getSbMappingWithExpiration(null, key, null);
258         }
259         return (MappingData) tableMap.get(origin).getMapping(null, key);
260     }
261
262     private MappingData getMappingNbFirst(Eid src, Eid dst) {
263
264         // Default lookup policy is northboundFirst
265         //lookupPolicy == NB_FIRST
266
267         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
268
269         if (nbMappingData == null) {
270             return getSbMappingWithExpiration(src, dst, null);
271         }
272         if (dst.getAddress() instanceof ServicePath) {
273             return updateServicePathMappingRecord(nbMappingData, dst);
274         }
275         return nbMappingData;
276     }
277
278     private MappingData getMappingNbSbIntersection(Eid src, Eid dst) {
279         //lookupPolicy == NB_AND_SB, we return intersection
280         //of NB and SB mappings, or NB mapping if intersection is empty.
281
282         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
283         if (nbMappingData == null) {
284             return nbMappingData;
285         }
286         // no intersection for Service Path mappings
287         if (dst.getAddress() instanceof ServicePath) {
288             return updateServicePathMappingRecord(nbMappingData, dst);
289         }
290         MappingData sbMappingData = getSbMappingWithExpiration(src, dst, null);
291         if (sbMappingData == null) {
292             return nbMappingData;
293         }
294         // both NB and SB mappings exist. Compute intersection of the mappings
295         return MappingMergeUtil.computeNbSbIntersection(nbMappingData, sbMappingData);
296     }
297
298     private MappingData getSbMappingWithExpiration(Eid src, Eid dst, XtrId xtrId) {
299         MappingData mappingData = (MappingData) smc.getMapping(dst, xtrId);
300         if (mappingData != null && MappingMergeUtil.mappingIsExpired(mappingData)) {
301             return removeExpiredMapping(dst, xtrId, mappingData);
302         } else {
303             return mappingData;
304         }
305     }
306
307     /*
308      * This private method either removes the main mapping ONLY, or the xTR-ID mapping ONLY, based on xtrId being
309      * null or non-null. Caller functions should take care of removing both when necessary.
310      */
311     private MappingData removeExpiredMapping(Eid key, XtrId xtrId, MappingData mappingData) {
312         if (mappingMerge && mappingData.isMergeEnabled()) {
313             return mergeMappings(key);
314         }
315         if (xtrId == null) {
316             smc.removeMapping(key);
317             dsbe.removeMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, mappingData.getRecord().getEid(),
318                     new SiteId(mappingData.getRecord().getSiteId()), mappingData));
319         } else {
320             smc.removeMapping(key, xtrId);
321             dsbe.removeXtrIdMapping(DSBEInputUtil.toXtrIdMapping(mappingData));
322         }
323         return null;
324     }
325
326     @Override
327     public Eid getWidestNegativePrefix(Eid key) {
328         Eid nbPrefix = pmc.getWidestNegativeMapping(key);
329         if (nbPrefix == null) {
330             return null;
331         }
332
333         Eid sbPrefix = smc.getWidestNegativeMapping(key);
334         if (sbPrefix == null) {
335             return null;
336         }
337
338         // since prefixes overlap, just return the more specific (larger mask)
339         if (LispAddressUtil.getIpPrefixMask(nbPrefix) < LispAddressUtil.getIpPrefixMask(sbPrefix)) {
340             return sbPrefix;
341         } else {
342             return nbPrefix;
343         }
344     }
345
346     @Override
347     public void removeMapping(MappingOrigin origin, Eid key) {
348         tableMap.get(origin).removeMapping(key);
349         if (notificationService) {
350             // TODO
351         }
352     }
353
354     @Override
355     public void addAuthenticationKey(Eid key, MappingAuthkey authKey) {
356         LOG.debug("Adding authentication key '{}' with key-ID {} for {}", authKey.getKeyString(), authKey.getKeyType(),
357                 LispAddressStringifier.getString(key));
358         akdb.addAuthenticationKey(key, authKey);
359     }
360
361     @Override
362     public MappingAuthkey getAuthenticationKey(Eid key) {
363         if (LOG.isDebugEnabled()) {
364             LOG.debug("Retrieving authentication key for {}", LispAddressStringifier.getString(key));
365         }
366         return akdb.getAuthenticationKey(key);
367     }
368
369     @Override
370     public void removeAuthenticationKey(Eid key) {
371         if (LOG.isDebugEnabled()) {
372             LOG.debug("Removing authentication key for {}", LispAddressStringifier.getString(key));
373         }
374         akdb.removeAuthenticationKey(key);
375     }
376
377     @Override
378     public void addData(MappingOrigin origin, Eid key, String subKey, Object data) {
379         if (LOG.isDebugEnabled()) {
380             LOG.debug("Add data of class {} for key {} and subkey {}", data.getClass(),
381                     LispAddressStringifier.getString(key), subKey);
382         }
383         tableMap.get(origin).addData(key, subKey, data);
384     }
385
386     @Override
387     public Object getData(MappingOrigin origin, Eid key, String subKey) {
388         if (LOG.isDebugEnabled()) {
389             LOG.debug("Retrieving data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
390         }
391         return tableMap.get(origin).getData(key, subKey);
392     }
393
394     @Override
395     public void removeData(MappingOrigin origin, Eid key, String subKey) {
396         if (LOG.isDebugEnabled()) {
397             LOG.debug("Removing data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
398         }
399         tableMap.get(origin).removeData(key, subKey);
400     }
401
402     @Override
403     public Eid getParentPrefix(Eid key) {
404         return smc.getParentPrefix(key);
405     }
406
407
408     /**
409      * Restore all mappings and keys from mdsal datastore.
410      */
411     private void restoreDaoFromDatastore() {
412         List<AuthenticationKey> authKeys = dsbe.getAllAuthenticationKeys();
413         List<Mapping> mappings = dsbe.getAllMappings(LogicalDatastoreType.CONFIGURATION);
414
415         /*
416          * XXX By default, the operational datastore is not persisted to disk, either at run-time, or on shutdown,
417          * so the following will have no effect (getLastUpdateTimestamp() will fail, since it's reading from
418          * the operational datastore, and even if it didn't getAllMappings() will fail anyway). According to rovarga it
419          * should be possible to turn on persistence for the operational datastore editing
420          * etc/opendaylight/karaf/05-clustering.xml, by setting <persistence>true</persistence>. At the time of writing
421          * the below code block that didn't seem to work though.
422          */
423         Long lastUpdateTimestamp = dsbe.getLastUpdateTimestamp();
424         if (lastUpdateTimestamp != null && System.currentTimeMillis() - lastUpdateTimestamp
425                 > ConfigIni.getInstance().getRegistrationValiditySb()) {
426             LOG.warn("Restore threshold passed, not restoring operational datastore into DAO");
427         } else {
428             mappings.addAll(dsbe.getAllMappings(LogicalDatastoreType.OPERATIONAL));
429         }
430         dsbe.removeLastUpdateTimestamp();
431
432         LOG.info("Restoring {} mappings and {} keys from datastore into DAO", mappings.size(), authKeys.size());
433
434         for (Mapping mapping : mappings) {
435             addMapping(mapping.getOrigin(), mapping.getMappingRecord().getEid(),
436                     new MappingData(mapping.getMappingRecord()));
437         }
438
439         for (AuthenticationKey authKey : authKeys) {
440             addAuthenticationKey(authKey.getEid(), authKey.getMappingAuthkey());
441         }
442     }
443
444     public void destroy() {
445         LOG.info("Mapping System is being destroyed!");
446         dsbe.saveLastUpdateTimestamp();
447     }
448
449     @Override
450     public String printMappings() {
451         final StringBuffer sb = new StringBuffer();
452         sb.append("PolicyMapCache\n--------------\n");
453         sb.append(pmc.printMappings());
454         sb.append("SbMapCache\n----------\n");
455         sb.append(smc.printMappings());
456         return sb.toString();
457     }
458
459     @Override
460     public String printKeys() {
461         return akdb.printKeys();
462     }
463
464     public void cleanCaches() {
465         dao.removeAll();
466         buildMapCaches();
467     }
468
469     /*
470      * XXX  Mappings and keys should be separated for this to work properly, as is it will remove northbound originated
471      * authentication keys too, since they are currently stored in smc.
472      */
473     public void cleanSBMappings() {
474         smc = new SimpleMapCache(sdao);
475     }
476
477     @Override
478     public void setIsMaster(boolean isMaster) {
479         this.isMaster = isMaster;
480     }
481
482     @Override
483     public boolean isMaster() {
484         return isMaster;
485     }
486 }