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