1b2128957e9352ffd93a0a0991abf91ec3a354ea
[controller.git] / opendaylight / forwarding / staticrouting / src / main / java / org / opendaylight / controller / forwarding / staticrouting / internal / StaticRoutingImplementation.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.forwarding.staticrouting.internal;
11
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.ObjectInputStream;
15 import java.net.Inet4Address;
16 import java.net.InetAddress;
17 import java.nio.ByteBuffer;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.Dictionary;
21 import java.util.EnumSet;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.Timer;
26 import java.util.TimerTask;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.Future;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36 import org.apache.felix.dm.Component;
37 import org.opendaylight.controller.clustering.services.CacheConfigException;
38 import org.opendaylight.controller.clustering.services.CacheExistException;
39 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
40 import org.opendaylight.controller.clustering.services.IClusterServices;
41 import org.opendaylight.controller.configuration.IConfigurationContainerAware;
42 import org.opendaylight.controller.forwarding.staticrouting.IForwardingStaticRouting;
43 import org.opendaylight.controller.forwarding.staticrouting.IStaticRoutingAware;
44 import org.opendaylight.controller.forwarding.staticrouting.StaticRoute;
45 import org.opendaylight.controller.forwarding.staticrouting.StaticRouteConfig;
46 import org.opendaylight.controller.hosttracker.IfIptoHost;
47 import org.opendaylight.controller.hosttracker.IfNewHostNotify;
48 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
49 import org.opendaylight.controller.sal.utils.GlobalConstants;
50 import org.opendaylight.controller.sal.utils.IObjectReader;
51 import org.opendaylight.controller.sal.utils.ObjectReader;
52 import org.opendaylight.controller.sal.utils.ObjectWriter;
53 import org.opendaylight.controller.sal.utils.Status;
54 import org.opendaylight.controller.sal.utils.StatusCode;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * Static Routing feature provides the bridge between SDN and Non-SDN networks.
60  */
61 public class StaticRoutingImplementation implements IfNewHostNotify,
62         IForwardingStaticRouting, IObjectReader, IConfigurationContainerAware {
63     private static Logger log = LoggerFactory
64             .getLogger(StaticRoutingImplementation.class);
65     private static String ROOT = GlobalConstants.STARTUPHOME.toString();
66     private static final String SAVE = "Save";
67     ConcurrentMap<String, StaticRoute> staticRoutes;
68     ConcurrentMap<String, StaticRouteConfig> staticRouteConfigs;
69     private IfIptoHost hostTracker;
70     private Timer gatewayProbeTimer;
71     private String staticRoutesFileName = null;
72     private IClusterContainerServices clusterContainerService = null;
73     private Set<IStaticRoutingAware> staticRoutingAware = Collections
74             .synchronizedSet(new HashSet<IStaticRoutingAware>());
75     private ExecutorService executor;
76
77     void setStaticRoutingAware(IStaticRoutingAware s) {
78         if (this.staticRoutingAware != null) {
79             this.staticRoutingAware.add(s);
80         }
81     }
82
83     void unsetStaticRoutingAware(IStaticRoutingAware s) {
84         if (this.staticRoutingAware != null) {
85             this.staticRoutingAware.remove(s);
86         }
87     }
88
89     public void setHostTracker(IfIptoHost hostTracker) {
90         log.debug("Setting HostTracker");
91         this.hostTracker = hostTracker;
92     }
93
94     public void unsetHostTracker(IfIptoHost hostTracker) {
95         if (this.hostTracker == hostTracker) {
96             this.hostTracker = null;
97         }
98     }
99
100     @Override
101     public ConcurrentMap<String, StaticRouteConfig> getStaticRouteConfigs() {
102         return staticRouteConfigs;
103     }
104
105     public void setStaticRouteConfigs(
106             ConcurrentMap<String, StaticRouteConfig> staticRouteConfigs) {
107         this.staticRouteConfigs = staticRouteConfigs;
108     }
109
110     @Override
111     public Object readObject(ObjectInputStream ois)
112             throws FileNotFoundException, IOException, ClassNotFoundException {
113         // Perform the class deserialization locally, from inside the package
114         // where the class is defined
115         return ois.readObject();
116     }
117
118     @SuppressWarnings("unchecked")
119     private void loadConfiguration() {
120         ObjectReader objReader = new ObjectReader();
121         ConcurrentMap<String, StaticRouteConfig> confList = (ConcurrentMap<String, StaticRouteConfig>) objReader
122                 .read(this, staticRoutesFileName);
123
124         if (confList == null) {
125             return;
126         }
127
128         for (StaticRouteConfig conf : confList.values()) {
129             addStaticRoute(conf);
130         }
131     }
132
133
134     private Status saveConfig() {
135         return saveConfigInternal();
136     }
137
138     public Status saveConfigInternal() {
139         Status status;
140         ObjectWriter objWriter = new ObjectWriter();
141
142         status = objWriter.write(
143                 new ConcurrentHashMap<String, StaticRouteConfig>(
144                         staticRouteConfigs), staticRoutesFileName);
145
146         if (status.isSuccess()) {
147             return status;
148         } else {
149             return new Status(StatusCode.INTERNALERROR, "Save failed");
150         }
151     }
152
153     @SuppressWarnings("deprecation")
154         private void allocateCaches() {
155         if (this.clusterContainerService == null) {
156             log
157                     .info("un-initialized clusterContainerService, can't create cache");
158             return;
159         }
160
161         try {
162             clusterContainerService.createCache(
163                     "forwarding.staticrouting.routes", EnumSet
164                             .of(IClusterServices.cacheMode.TRANSACTIONAL));
165             clusterContainerService.createCache(
166                     "forwarding.staticrouting.configs", EnumSet
167                             .of(IClusterServices.cacheMode.TRANSACTIONAL));
168         } catch (CacheExistException cee) {
169             log
170                     .error("\nCache already exists - destroy and recreate if needed");
171         } catch (CacheConfigException cce) {
172             log.error("\nCache configuration invalid - check cache mode");
173         }
174     }
175
176     @SuppressWarnings({ "unchecked", "deprecation" })
177     private void retrieveCaches() {
178         if (this.clusterContainerService == null) {
179             log
180                     .info("un-initialized clusterContainerService, can't retrieve cache");
181             return;
182         }
183
184         staticRoutes = (ConcurrentMap<String, StaticRoute>) clusterContainerService
185                 .getCache("forwarding.staticrouting.routes");
186         if (staticRoutes == null) {
187             log.error("\nFailed to get rulesDB handle");
188         }
189
190         staticRouteConfigs = (ConcurrentMap<String, StaticRouteConfig>) clusterContainerService
191                 .getCache("forwarding.staticrouting.configs");
192         if (staticRouteConfigs == null) {
193             log.error("\nFailed to get rulesDB handle");
194         }
195     }
196
197     private void notifyStaticRouteUpdate(StaticRoute s, boolean update) {
198         if (this.staticRoutingAware != null) {
199             log.info("Invoking StaticRoutingAware listeners");
200             synchronized (this.staticRoutingAware) {
201                 for (IStaticRoutingAware ra : this.staticRoutingAware) {
202                     try {
203                         ra.staticRouteUpdate(s, update);
204                     } catch (Exception e) {
205                         log.error("",e);
206                     }
207                 }
208             }
209         }
210     }
211
212     private class NotifyStaticRouteWorker implements Callable<Object> {
213         private StaticRoute staticRoute;
214         private boolean added;
215
216         public NotifyStaticRouteWorker(StaticRoute s, boolean update) {
217             this.staticRoute = s;
218             this.added = update;
219         }
220
221         @Override
222         public Object call() throws Exception {
223             if (!added
224                     || (staticRoute.getType() == StaticRoute.NextHopType.SWITCHPORT)) {
225                 notifyStaticRouteUpdate(staticRoute, added);
226             } else {
227                 InetAddress nh = staticRoute.getNextHopAddress();
228                 HostNodeConnector host = hostTracker.hostQuery(nh);
229                 if (host == null) {
230                     log.debug("Next hop {}  is not present, try to discover it", nh.getHostAddress());
231                     Future<HostNodeConnector> future = hostTracker.discoverHost(nh);
232                     if (future != null) {
233                         try {
234                             host = future.get();
235                         } catch (InterruptedException ioe) {
236                             log.trace("Thread interrupted {}", ioe);
237                         } catch (Exception e) {
238                             log.error("", e);
239                         }
240                     }
241                 }
242                 if (host != null) {
243                     log.debug("Next hop {} is found", nh.getHostAddress());
244                     staticRoute.setHost(host);
245                     notifyStaticRouteUpdate(staticRoute, added);
246                 } else {
247                     log.debug("Next hop {}  is still not present, try again later", nh.getHostAddress());
248                 }
249             }
250             return null;
251         }
252     }
253
254     private void checkAndUpdateListeners(StaticRoute staticRoute, boolean added) {
255         NotifyStaticRouteWorker worker = new NotifyStaticRouteWorker(staticRoute, added);
256         try {
257             executor.submit(worker);
258         } catch (Exception e) {
259             log.error("got Exception ", e);
260         }
261     }
262
263     private void notifyHostUpdate(HostNodeConnector host, boolean added) {
264         if (host == null) {
265             return;
266         }
267         for (StaticRoute s : staticRoutes.values()) {
268             if (s.getType() == StaticRoute.NextHopType.SWITCHPORT) {
269                 continue;
270             }
271             if (s.getNextHopAddress().equals(host.getNetworkAddress())) {
272                 if (added) {
273                     s.setHost(host);
274                 } else {
275                     s.setHost(null);
276                 }
277                 notifyStaticRouteUpdate(s, added);
278             }
279         }
280     }
281
282     @Override
283     public void notifyHTClient(HostNodeConnector host) {
284         notifyHostUpdate(host, true);
285     }
286
287     @Override
288     public void notifyHTClientHostRemoved(HostNodeConnector host) {
289         notifyHostUpdate(host, false);
290     }
291
292     public boolean isIPv4AddressValid(String cidr) {
293         if (cidr == null) {
294             return false;
295         }
296
297         String values[] = cidr.split("/");
298         Pattern ipv4Pattern = Pattern
299                 .compile("(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])");
300         Matcher mm = ipv4Pattern.matcher(values[0]);
301         if (!mm.matches()) {
302             log.debug("IPv4 source address {} is not valid", cidr);
303             return false;
304         }
305         if (values.length >= 2) {
306             int prefix = Integer.valueOf(values[1]);
307             if ((prefix < 0) || (prefix > 32)) {
308                 log.debug("prefix {} is not valid", prefix);
309                 return false;
310             }
311         }
312         return true;
313     }
314
315     public static short getUnsignedByte(ByteBuffer bb, int position) {
316         return ((short) (bb.get(position) & (short) 0xff));
317     }
318
319     public static int compareByteBuffers(ByteBuffer buf1, ByteBuffer buf2) {
320         for (int i = 0; i < buf1.array().length; i++) {
321             if (getUnsignedByte(buf1, i) > getUnsignedByte(buf2, i)) {
322                 return 1;
323             } else if (getUnsignedByte(buf1, i) < getUnsignedByte(buf2, i)) {
324                 return -1;
325             }
326         }
327
328         return 0;
329     }
330
331     @Override
332     public StaticRoute getBestMatchStaticRoute(InetAddress ipAddress) {
333         ByteBuffer bblongestPrefix = null;
334         try {
335             bblongestPrefix = ByteBuffer.wrap(InetAddress.getByName("0.0.0.0")
336                     .getAddress());
337         } catch (Exception e) {
338             return null;
339         }
340
341         if (staticRoutes == null) {
342             return null;
343         }
344
345         StaticRoute longestPrefixRoute = null;
346         for (StaticRoute s : staticRoutes.values()) {
347             InetAddress prefix = s.longestPrefixMatch(ipAddress);
348             if ((prefix != null) && (prefix instanceof Inet4Address)) {
349                 ByteBuffer bbtmp = ByteBuffer.wrap(prefix.getAddress());
350                 if (compareByteBuffers(bbtmp, bblongestPrefix) > 0) {
351                     bblongestPrefix = bbtmp;
352                     longestPrefixRoute = s;
353                 }
354             }
355         }
356         return longestPrefixRoute;
357     }
358
359     @Override
360     public Status addStaticRoute(StaticRouteConfig config) {
361         Status status = config.isValid();
362         if (!status.isSuccess()) {
363             return status;
364         }
365         if (staticRouteConfigs.get(config.getName()) != null) {
366                 return new Status(StatusCode.CONFLICT,
367                                 "A valid Static Route configuration with this name " +
368                                                 "already exists. Please use a different name");
369         }
370
371         // Update database
372         StaticRoute sRoute = new StaticRoute(config);
373
374         for (Map.Entry<String, StaticRoute> entry : staticRoutes.entrySet()) {
375             if (entry.getValue().compareTo(sRoute) == 0) {
376                 return new Status(StatusCode.CONFLICT,
377                         "This conflicts with an existing Static Route " +
378                                 "Configuration. Please check the configuration " +
379                                         "and try again");
380             }
381         }
382         staticRoutes.put(config.getName(), sRoute);
383
384         // Update config databse
385         staticRouteConfigs.put(config.getName(), config);
386
387         // Notify
388         checkAndUpdateListeners(sRoute, true);
389         return status;
390     }
391
392     @Override
393     public Status removeStaticRoute(String name) {
394         staticRouteConfigs.remove(name);
395         StaticRoute sRoute = staticRoutes.remove(name);
396         if (sRoute != null) {
397             checkAndUpdateListeners(sRoute, false);
398             return new Status(StatusCode.SUCCESS, null);
399         }
400         return new Status(StatusCode.NOTFOUND,
401                         "Static Route with name " + name + " is not found");
402     }
403
404     void setClusterContainerService(IClusterContainerServices s) {
405         log.debug("Cluster Service set");
406         this.clusterContainerService = s;
407     }
408
409     void unsetClusterContainerService(IClusterContainerServices s) {
410         if (this.clusterContainerService == s) {
411             log.debug("Cluster Service removed!");
412             this.clusterContainerService = null;
413         }
414     }
415
416     /**
417      * Function called by the dependency manager when all the required
418      * dependencies are satisfied
419      *
420      */
421     void init(Component c) {
422         String containerName = null;
423         Dictionary props = c.getServiceProperties();
424         if (props != null) {
425             containerName = (String) props.get("containerName");
426         } else {
427             // In the Global instance case the containerName is empty
428             containerName = "";
429         }
430
431         staticRoutesFileName = ROOT + "staticRouting_" + containerName
432                 + ".conf";
433
434         log.debug("forwarding.staticrouting starting on container {}",
435                   containerName);
436         //staticRoutes = new ConcurrentHashMap<String, StaticRoute>();
437         allocateCaches();
438         retrieveCaches();
439         this.executor = Executors.newFixedThreadPool(1);
440         if (staticRouteConfigs.isEmpty()) {
441             loadConfiguration();
442         }
443
444         /*
445          *  Slow probe to identify any gateway that might have silently appeared
446          *  after the Static Routing Configuration.
447          */
448         gatewayProbeTimer = new Timer();
449         gatewayProbeTimer.schedule(new TimerTask() {
450             @Override
451             public void run() {
452                 for (StaticRoute s : staticRoutes.values()) {
453                     if ((s.getType() == StaticRoute.NextHopType.IPADDRESS)
454                             && s.getHost() == null) {
455                         checkAndUpdateListeners(s, true);
456                     }
457                 }
458             }
459         }, 60 * 1000, 60 * 1000);
460     }
461
462     /**
463      * Function called by the dependency manager when at least one
464      * dependency become unsatisfied or when the component is shutting
465      * down because for example bundle is being stopped.
466      *
467      */
468     void destroy() {
469         log.debug("Destroy all the Static Routing Rules given we are "
470                 + "shutting down");
471
472         gatewayProbeTimer.cancel();
473
474         // Clear the listener so to be ready in next life
475         this.staticRoutingAware.clear();
476     }
477
478     /**
479      * Function called by dependency manager after "init ()" is called
480      * and after the services provided by the class are registered in
481      * the service registry
482      *
483      */
484     void start() {
485     }
486
487     /**
488      * Function called by the dependency manager before the services
489      * exported by the component are unregistered, this will be
490      * followed by a "destroy ()" calls
491      *
492      */
493     void stop() {
494         executor.shutdown();
495     }
496
497     @Override
498     public Status saveConfiguration() {
499         return this.saveConfig();
500     }
501
502 }