Merge "Remove SimpleMapCache#getXtrIdTable()"
[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 void 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         if (mergedMappingData != null) {
214             smc.addMapping(key, mergedMappingData, sourceRlocs);
215         }
216     }
217
218     @Override
219     public MappingData getMapping(Eid src, Eid dst) {
220         // NOTE: Currently we have two lookup algorithms implemented, which are configurable
221
222         if (ConfigIni.getInstance().getLookupPolicy() == IMappingService.LookupPolicy.NB_AND_SB) {
223             return getMappingNbSbIntersection(src, dst);
224         } else {
225             return getMappingNbFirst(src, dst);
226         }
227     }
228
229     @Override
230     public MappingData getMapping(Eid dst) {
231         return getMapping((Eid) null, dst);
232     }
233
234     @Override
235     public MappingData getMapping(Eid src, Eid dst, XtrId xtrId) {
236         // Note: If xtrId is null, we need to go through regular policy checking else Policy doesn't matter
237
238         if (xtrId == null) {
239             return getMapping(src, dst);
240         }
241
242         return getSbMappingWithExpiration(src, dst, xtrId);
243     }
244
245     @Override
246     public MappingData getMapping(MappingOrigin origin, Eid key) {
247         if (origin.equals(MappingOrigin.Southbound)) {
248             return getSbMappingWithExpiration(null, key, null);
249         }
250         return (MappingData) tableMap.get(origin).getMapping(null, key);
251     }
252
253     private MappingData getMappingNbFirst(Eid src, Eid dst) {
254
255         // Default lookup policy is northboundFirst
256         //lookupPolicy == NB_FIRST
257
258         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
259
260         if (nbMappingData == null) {
261             return getSbMappingWithExpiration(src, dst, null);
262         }
263         if (dst.getAddress() instanceof ServicePath) {
264             return updateServicePathMappingRecord(nbMappingData, dst);
265         }
266         return nbMappingData;
267     }
268
269     private MappingData getMappingNbSbIntersection(Eid src, Eid dst) {
270         //lookupPolicy == NB_AND_SB, we return intersection
271         //of NB and SB mappings, or NB mapping if intersection is empty.
272
273         MappingData nbMappingData = (MappingData) pmc.getMapping(src, dst);
274         if (nbMappingData == null) {
275             return nbMappingData;
276         }
277         // no intersection for Service Path mappings
278         if (dst.getAddress() instanceof ServicePath) {
279             return updateServicePathMappingRecord(nbMappingData, dst);
280         }
281         MappingData sbMappingData = getSbMappingWithExpiration(src, dst, null);
282         if (sbMappingData == null) {
283             return nbMappingData;
284         }
285         // both NB and SB mappings exist. Compute intersection of the mappings
286         return MappingMergeUtil.computeNbSbIntersection(nbMappingData, sbMappingData);
287     }
288
289     private MappingData getSbMappingWithExpiration(Eid src, Eid dst, XtrId xtrId) {
290         MappingData mappingData = (MappingData) smc.getMapping(dst, xtrId);
291         if (mappingData != null && MappingMergeUtil.mappingIsExpired(mappingData)) {
292             removeExpiredMapping(dst, xtrId, mappingData);
293             return null;
294         } else {
295             return mappingData;
296         }
297     }
298
299     /*
300      * This private method either removes the main mapping ONLY, or the xTR-ID mapping ONLY, based on xtrId being
301      * null or non-null. Caller functions should take care of removing both when necessary.
302      */
303     private void removeExpiredMapping(Eid key, XtrId xtrId, MappingData mappingData) {
304         if (xtrId == null) {
305             smc.removeMapping(key);
306             dsbe.removeMapping(DSBEInputUtil.toMapping(MappingOrigin.Southbound, mappingData.getRecord().getEid(),
307                     new SiteId(mappingData.getRecord().getSiteId()), mappingData));
308         } else {
309             smc.removeMapping(key, xtrId);
310             dsbe.removeXtrIdMapping(DSBEInputUtil.toXtrIdMapping(mappingData));
311             if (mappingMerge && mappingData.isMergeEnabled()) {
312                 mergeMappings(key);
313             }
314         }
315     }
316
317     @Override
318     public Eid getWidestNegativePrefix(Eid key) {
319         Eid nbPrefix = pmc.getWidestNegativeMapping(key);
320         if (nbPrefix == null) {
321             return null;
322         }
323
324         Eid sbPrefix = smc.getWidestNegativeMapping(key);
325         if (sbPrefix == null) {
326             return null;
327         }
328
329         // since prefixes overlap, just return the more specific (larger mask)
330         if (LispAddressUtil.getIpPrefixMask(nbPrefix) < LispAddressUtil.getIpPrefixMask(sbPrefix)) {
331             return sbPrefix;
332         } else {
333             return nbPrefix;
334         }
335     }
336
337     @Override
338     public void removeMapping(MappingOrigin origin, Eid key) {
339         tableMap.get(origin).removeMapping(key);
340         if (notificationService) {
341             // TODO
342         }
343     }
344
345     @Override
346     public void addAuthenticationKey(Eid key, MappingAuthkey authKey) {
347         LOG.debug("Adding authentication key '{}' with key-ID {} for {}", authKey.getKeyString(), authKey.getKeyType(),
348                 LispAddressStringifier.getString(key));
349         akdb.addAuthenticationKey(key, authKey);
350     }
351
352     @Override
353     public MappingAuthkey getAuthenticationKey(Eid key) {
354         if (LOG.isDebugEnabled()) {
355             LOG.debug("Retrieving authentication key for {}", LispAddressStringifier.getString(key));
356         }
357         return akdb.getAuthenticationKey(key);
358     }
359
360     @Override
361     public void removeAuthenticationKey(Eid key) {
362         if (LOG.isDebugEnabled()) {
363             LOG.debug("Removing authentication key for {}", LispAddressStringifier.getString(key));
364         }
365         akdb.removeAuthenticationKey(key);
366     }
367
368     @Override
369     public void addData(MappingOrigin origin, Eid key, String subKey, Object data) {
370         if (LOG.isDebugEnabled()) {
371             LOG.debug("Add data of class {} for key {} and subkey {}", data.getClass(),
372                     LispAddressStringifier.getString(key), subKey);
373         }
374         tableMap.get(origin).addData(key, subKey, data);
375     }
376
377     @Override
378     public Object getData(MappingOrigin origin, Eid key, String subKey) {
379         if (LOG.isDebugEnabled()) {
380             LOG.debug("Retrieving data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
381         }
382         return tableMap.get(origin).getData(key, subKey);
383     }
384
385     @Override
386     public void removeData(MappingOrigin origin, Eid key, String subKey) {
387         if (LOG.isDebugEnabled()) {
388             LOG.debug("Removing data for key {} and subkey {}", LispAddressStringifier.getString(key), subKey);
389         }
390         tableMap.get(origin).removeData(key, subKey);
391     }
392
393
394     /**
395      * Restore all mappings and keys from mdsal datastore.
396      */
397     private void restoreDaoFromDatastore() {
398         List<AuthenticationKey> authKeys = dsbe.getAllAuthenticationKeys();
399         List<Mapping> mappings = dsbe.getAllMappings(LogicalDatastoreType.CONFIGURATION);
400
401         /*
402          * XXX By default, the operational datastore is not persisted to disk, either at run-time, or on shutdown,
403          * so the following will have no effect (getLastUpdateTimestamp() will fail, since it's reading from
404          * the operational datastore, and even if it didn't getAllMappings() will fail anyway). According to rovarga it
405          * should be possible to turn on persistence for the operational datastore editing
406          * etc/opendaylight/karaf/05-clustering.xml, by setting <persistence>true</persistence>. At the time of writing
407          * the below code block that didn't seem to work though.
408          */
409         Long lastUpdateTimestamp = dsbe.getLastUpdateTimestamp();
410         if (lastUpdateTimestamp != null && System.currentTimeMillis() - lastUpdateTimestamp
411                 > ConfigIni.getInstance().getRegistrationValiditySb()) {
412             LOG.warn("Restore threshold passed, not restoring operational datastore into DAO");
413         } else {
414             mappings.addAll(dsbe.getAllMappings(LogicalDatastoreType.OPERATIONAL));
415         }
416         dsbe.removeLastUpdateTimestamp();
417
418         LOG.info("Restoring {} mappings and {} keys from datastore into DAO", mappings.size(), authKeys.size());
419
420         for (Mapping mapping : mappings) {
421             addMapping(mapping.getOrigin(), mapping.getMappingRecord().getEid(),
422                     new MappingData(mapping.getMappingRecord()));
423         }
424
425         for (AuthenticationKey authKey : authKeys) {
426             addAuthenticationKey(authKey.getEid(), authKey.getMappingAuthkey());
427         }
428     }
429
430     public void destroy() {
431         LOG.info("Mapping System is being destroyed!");
432         dsbe.saveLastUpdateTimestamp();
433     }
434
435     @Override
436     public String printMappings() {
437         final StringBuffer sb = new StringBuffer();
438         sb.append("PolicyMapCache\n--------------\n");
439         sb.append(pmc.printMappings());
440         sb.append("SbMapCache\n----------\n");
441         sb.append(smc.printMappings());
442         return sb.toString();
443     }
444
445     @Override
446     public String printKeys() {
447         return akdb.printKeys();
448     }
449
450     public void cleanCaches() {
451         dao.removeAll();
452         buildMapCaches();
453     }
454
455     /*
456      * XXX  Mappings and keys should be separated for this to work properly, as is it will remove northbound originated
457      * authentication keys too, since they are currently stored in smc.
458      */
459     public void cleanSBMappings() {
460         smc = new SimpleMapCache(sdao);
461     }
462
463     @Override
464     public void setIsMaster(boolean isMaster) {
465         this.isMaster = isMaster;
466     }
467
468     @Override
469     public boolean isMaster() {
470         return isMaster;
471     }
472 }