Bug 6351 - Clustering rework.
[lispflowmapping.git] / mappingservice / implementation / src / main / java / org / opendaylight / lispflowmapping / implementation / lisp / MapServer.java
1 /*
2  * Copyright (c) 2014 Contextream, Inc. and others.  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.lisp;
10
11 import com.google.common.base.Preconditions;
12 import java.net.InetAddress;
13 import java.net.NetworkInterface;
14 import java.net.SocketException;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Enumeration;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Objects;
22 import java.util.Set;
23 import org.apache.commons.lang3.BooleanUtils;
24 import org.opendaylight.controller.md.sal.binding.api.NotificationService;
25 import org.opendaylight.lispflowmapping.implementation.config.ConfigIni;
26 import org.opendaylight.lispflowmapping.interfaces.dao.SubKeys;
27 import org.opendaylight.lispflowmapping.interfaces.dao.SubscriberRLOC;
28 import org.opendaylight.lispflowmapping.interfaces.lisp.IMapNotifyHandler;
29 import org.opendaylight.lispflowmapping.interfaces.lisp.IMapServerAsync;
30 import org.opendaylight.lispflowmapping.interfaces.mappingservice.IMappingService;
31 import org.opendaylight.lispflowmapping.lisp.authentication.LispAuthenticationUtil;
32 import org.opendaylight.lispflowmapping.lisp.type.LispMessage;
33 import org.opendaylight.lispflowmapping.lisp.util.LispAddressStringifier;
34 import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil;
35 import org.opendaylight.lispflowmapping.lisp.util.MapNotifyBuilderHelper;
36 import org.opendaylight.lispflowmapping.lisp.util.MapRequestUtil;
37 import org.opendaylight.lispflowmapping.lisp.util.SourceDestKeyHelper;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.SourceDestKey;
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.MapRegister;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.SiteId;
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.eid.list.EidItem;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.list.EidItemBuilder;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapnotifymessage.MapNotifyBuilder;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.authkey.container.MappingAuthkey;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecord;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecordBuilder;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.list.MappingRecordItem;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.list.MappingRecordItemBuilder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.maprequestnotification.MapRequestBuilder;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.transport.address.TransportAddress;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.transport.address.TransportAddressBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChange;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChanged;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingOrigin;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.OdlMappingserviceListener;
59 import org.opendaylight.yangtools.concepts.ListenerRegistration;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 public class MapServer implements IMapServerAsync, OdlMappingserviceListener {
64
65     protected static final Logger LOG = LoggerFactory.getLogger(MapServer.class);
66     private static final byte[] ALL_ZEROES_XTR_ID = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0};
67     private IMappingService mapService;
68     private boolean subscriptionService;
69     private IMapNotifyHandler notifyHandler;
70     private NotificationService notificationService;
71     private ListenerRegistration<MapServer> mapServerListenerRegistration;
72     private boolean isMaster = false;
73
74     public MapServer(IMappingService mapService, boolean subscriptionService,
75                      IMapNotifyHandler notifyHandler, NotificationService notificationService) {
76         Preconditions.checkNotNull(mapService);
77         this.mapService = mapService;
78         this.subscriptionService = subscriptionService;
79         this.notifyHandler = notifyHandler;
80         this.notificationService = notificationService;
81         if (notificationService != null) {
82             notificationService.registerNotificationListener(this);
83         }
84     }
85
86     @Override
87     public void setSubscriptionService(boolean subscriptionService) {
88         this.subscriptionService = subscriptionService;
89     }
90
91     @SuppressWarnings("unchecked")
92     public void handleMapRegister(MapRegister mapRegister) {
93         boolean mappingUpdated = false;
94         boolean merge = ConfigIni.getInstance().mappingMergeIsSet() && mapRegister.isMergeEnabled();
95         Set<SubscriberRLOC> subscribers = null;
96         MappingRecord oldMapping;
97
98         if (merge) {
99             if (!mapRegister.isXtrSiteIdPresent() || mapRegister.getXtrId() == null) {
100                 LOG.error("Merge bit is set in Map-Register, but xTR-ID is not present. Will not merge.");
101                 merge = false;
102             } else if (Arrays.equals(mapRegister.getXtrId().getValue(), ALL_ZEROES_XTR_ID)) {
103                 LOG.warn("Merge bit is set in Map-Register, but xTR-ID is all zeroes.");
104             }
105         }
106
107         for (MappingRecordItem record : mapRegister.getMappingRecordItem()) {
108             MappingRecord mapping = record.getMappingRecord();
109
110             oldMapping = (MappingRecord) mapService.getMapping(MappingOrigin.Southbound, mapping.getEid());
111             mapService.addMapping(MappingOrigin.Southbound, mapping.getEid(), getSiteId(mapRegister), mapping, merge);
112
113             if (subscriptionService) {
114                 MappingRecord newMapping = merge
115                         ? (MappingRecord) mapService.getMapping(MappingOrigin.Southbound, mapping.getEid()) : mapping;
116
117                 if (mappingChanged(oldMapping, newMapping)) {
118                     if (LOG.isDebugEnabled()) {
119                         LOG.debug("Mapping update occured for {} SMRs will be sent for its subscribers.",
120                                 LispAddressStringifier.getString(mapping.getEid()));
121                     }
122                     subscribers = getSubscribers(mapping.getEid());
123                     sendSmrs(mapping, subscribers);
124                     mappingUpdated = true;
125                 }
126             }
127         }
128         if (BooleanUtils.isTrue(mapRegister.isWantMapNotify())) {
129             LOG.trace("MapRegister wants MapNotify");
130             MapNotifyBuilder builder = new MapNotifyBuilder();
131             List<TransportAddress> rlocs = null;
132             if (merge) {
133                 Set<IpAddressBinary> notifyRlocs = new HashSet<IpAddressBinary>();
134                 List<MappingRecordItem> mergedMappings = new ArrayList<MappingRecordItem>();
135                 for (MappingRecordItem record : mapRegister.getMappingRecordItem()) {
136                     MappingRecord mapping = record.getMappingRecord();
137                     MappingRecord currentRecord = (MappingRecord) mapService.getMapping(MappingOrigin.Southbound,
138                             mapping.getEid());
139                     mergedMappings.add(new MappingRecordItemBuilder().setMappingRecord(currentRecord).build());
140                     Set<IpAddressBinary> sourceRlocs = (Set<IpAddressBinary>) mapService.getData(
141                             MappingOrigin.Southbound, mapping.getEid(), SubKeys.SRC_RLOCS);
142                     if (sourceRlocs != null) {
143                         notifyRlocs.addAll(sourceRlocs);
144                     }
145                 }
146                 MapNotifyBuilderHelper.setFromMapRegisterAndMappingRecordItems(builder, mapRegister, mergedMappings);
147                 // send map-notify to merge group only when mapping record is changed
148                 if (mappingUpdated) {
149                     rlocs = getTransportAddresses(notifyRlocs);
150                 }
151             } else {
152                 MapNotifyBuilderHelper.setFromMapRegister(builder, mapRegister);
153             }
154             List<MappingRecordItem> mappings = builder.getMappingRecordItem();
155             if (mappings != null && mappings.get(0) != null && mappings.get(0).getMappingRecord() != null
156                     && mappings.get(0).getMappingRecord().getEid() != null) {
157                 MappingAuthkey authkey = mapService.getAuthenticationKey(mappings.get(0).getMappingRecord().getEid());
158                 if (authkey != null) {
159                     builder.setAuthenticationData(LispAuthenticationUtil.createAuthenticationData(builder.build(),
160                             authkey.getKeyString()));
161                 }
162             }
163             notifyHandler.handleMapNotify(builder.build(), rlocs);
164         }
165     }
166
167     @Override
168     public void setIsMaster(boolean isMaster) {
169         this.isMaster = isMaster;
170     }
171
172     private static List<TransportAddress> getTransportAddresses(Set<IpAddressBinary> addresses) {
173         List<TransportAddress> rlocs = new ArrayList<TransportAddress>();
174         for (IpAddressBinary address : addresses) {
175             TransportAddressBuilder tab = new TransportAddressBuilder();
176             tab.setIpAddress(address);
177             tab.setPort(new PortNumber(LispMessage.PORT_NUM));
178             rlocs.add(tab.build());
179         }
180         return rlocs;
181     }
182
183     private SiteId getSiteId(MapRegister mapRegister) {
184         return (mapRegister.getSiteId() != null) ? new SiteId(mapRegister.getSiteId()) : null;
185     }
186
187     @Override
188     public void onMappingChanged(MappingChanged notification) {
189         if (subscriptionService) {
190             if (isMaster) {
191                 sendSmrs(notification.getMappingRecord(), getSubscribers(notification.getMappingRecord().getEid()));
192             }
193             if (notification.getChangeType().equals(MappingChange.Removed)) {
194                 removeSubscribers(notification.getMappingRecord().getEid());
195             }
196         }
197     }
198
199     private static boolean mappingChanged(MappingRecord oldMapping, MappingRecord newMapping) {
200         // We only check for fields we care about
201         // XXX: This code needs to be checked and updated when the YANG model is modified
202         Preconditions.checkNotNull(newMapping, "The new mapping should never be null");
203         if (oldMapping == null) {
204             LOG.trace("mappingChanged(): old mapping is null");
205             return true;
206         } else if (!Objects.equals(oldMapping.getEid(), newMapping.getEid())) {
207             LOG.trace("mappingChanged(): EID");
208             return true;
209         } else if (!Objects.equals(oldMapping.getLocatorRecord(), newMapping.getLocatorRecord())) {
210             LOG.trace("mappingChanged(): RLOC");
211             return true;
212         } else if (!Objects.equals(oldMapping.getAction(), newMapping.getAction())) {
213             LOG.trace("mappingChanged(): action");
214             return true;
215         } else if (!Objects.equals(oldMapping.getRecordTtl(), newMapping.getRecordTtl())) {
216             LOG.trace("mappingChanged(): TTL");
217             return true;
218         } else if (!Objects.equals(oldMapping.getMapVersion(), newMapping.getMapVersion())) {
219             LOG.trace("mappingChanged(): mapping version");
220             return true;
221         }
222         return false;
223     }
224
225     private void sendSmrs(MappingRecord record, Set<SubscriberRLOC> subscribers) {
226         Eid eid = record.getEid();
227         handleSmr(eid, subscribers, notifyHandler);
228
229         // For SrcDst LCAF also send SMRs to Dst prefix
230         if (eid.getAddress() instanceof SourceDestKey) {
231             Eid dstAddr = SourceDestKeyHelper.getDstBinary(eid);
232             Set<SubscriberRLOC> dstSubs = getSubscribers(dstAddr);
233             MappingRecord newRecord = new MappingRecordBuilder(record).setEid(dstAddr).build();
234             handleSmr(newRecord.getEid(), dstSubs, notifyHandler);
235         }
236     }
237
238     @SuppressWarnings("checkstyle:IllegalCatch")
239     private void handleSmr(Eid eid, Set<SubscriberRLOC> subscribers, IMapNotifyHandler callback) {
240         if (subscribers == null) {
241             return;
242         }
243         MapRequestBuilder mrb = MapRequestUtil.prepareSMR(eid, LispAddressUtil.toRloc(getLocalAddress()));
244         LOG.trace("Built SMR packet: " + mrb.build().toString());
245         // Using Iterator ensures that we don't get a ConcurrentModificationException when removing a SubscriberRLOC
246         // from a Set.
247         Iterator<SubscriberRLOC> iterator = subscribers.iterator();
248         while (iterator.hasNext()) {
249             SubscriberRLOC subscriber = iterator.next();
250             if (subscriber.timedOut()) {
251                 LOG.trace("Lazy removing expired subscriber entry " + subscriber.toString());
252                 iterator.remove();
253             } else {
254                 try {
255                     // The address stored in the SMR's EID record is used as Source EID in the SMR-invoked Map-Request.
256                     // To ensure consistent behavior it is set to the value used to originally request a given mapping
257                     mrb.setEidItem(new ArrayList<EidItem>());
258                     mrb.getEidItem().add(new EidItemBuilder().setEid(subscriber.getSrcEid()).build());
259                     callback.handleSMR(mrb.build(), subscriber.getSrcRloc());
260                 } catch (Exception e) {
261                     LOG.error("Errors encountered while handling SMR:", e);
262                 }
263             }
264         }
265         addSubscribers(eid, subscribers);
266     }
267
268     @SuppressWarnings("unchecked")
269     private Set<SubscriberRLOC> getSubscribers(Eid address) {
270         return (Set<SubscriberRLOC>) mapService.getData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS);
271     }
272
273     private void removeSubscribers(Eid address) {
274         mapService.removeData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS);
275     }
276
277     private void addSubscribers(Eid address, Set<SubscriberRLOC> subscribers) {
278         mapService.addData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS, subscribers);
279     }
280
281     private static InetAddress getLocalAddress() {
282         try {
283             Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
284             while (interfaces.hasMoreElements()) {
285                 NetworkInterface current = interfaces.nextElement();
286                 LOG.debug("Interface " + current.toString());
287                 if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
288                     continue;
289                 }
290                 Enumeration<InetAddress> addresses = current.getInetAddresses();
291                 while (addresses.hasMoreElements()) {
292                     InetAddress currentAddr = addresses.nextElement();
293                     // Skip loopback and link local addresses
294                     if (currentAddr.isLoopbackAddress() || currentAddr.isLinkLocalAddress()) {
295                         continue;
296                     }
297                     LOG.debug(currentAddr.getHostAddress());
298                     return currentAddr;
299                 }
300             }
301         } catch (SocketException se) {
302             LOG.debug("Caught socket exceptio", se);
303         }
304         return null;
305     }
306 }