Added range type to subject-feature-definition/parameter
[groupbasedpolicy.git] / groupbasedpolicy / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / SwitchManager.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, 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.groupbasedpolicy.renderer.ofoverlay;
10
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Set;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.CopyOnWriteArrayList;
18 import java.util.concurrent.ScheduledExecutorService;
19
20 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
24 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.EncapsulationFormat;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayNodeConfig;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
35 import org.opendaylight.yangtools.concepts.ListenerRegistration;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.google.common.base.Function;
42 import com.google.common.base.Optional;
43 import com.google.common.base.Predicate;
44 import com.google.common.collect.Collections2;
45 import com.google.common.util.concurrent.FutureCallback;
46 import com.google.common.util.concurrent.Futures;
47 import com.google.common.util.concurrent.ListenableFuture;
48
49 /**
50  * Manage connected switches and ensure their configuration is set up
51  * correctly
52  * @author readams
53  */
54 public class SwitchManager implements AutoCloseable {
55     private static final Logger LOG = 
56             LoggerFactory.getLogger(SwitchManager.class);
57
58     private final DataBroker dataProvider;
59
60     private final static InstanceIdentifier<Nodes> nodesIid =
61             InstanceIdentifier.builder(Nodes.class).build();
62     private final static InstanceIdentifier<Node> nodeIid =
63             InstanceIdentifier.builder(Nodes.class)
64                 .child(Node.class).build();
65     private ListenerRegistration<DataChangeListener> nodesReg;
66     private ListenerRegistration<DataChangeListener> nodesConfigReg;
67
68     protected ConcurrentHashMap<NodeId, SwitchState> switches = 
69             new ConcurrentHashMap<>();
70     protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>();
71
72     public SwitchManager(DataBroker dataProvider,
73                          ScheduledExecutorService executor) {
74         super();
75         this.dataProvider = dataProvider;
76         if (dataProvider != null) {
77             nodesReg = dataProvider
78                 .registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, 
79                                             nodeIid, new NodesListener(), 
80                                             DataChangeScope.SUBTREE);
81             nodesConfigReg = dataProvider
82                     .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, 
83                                                 nodeIid, new NodesConfigListener(), 
84                                                 DataChangeScope.SUBTREE);
85         }
86         readSwitches();
87         LOG.debug("Initialized OFOverlay switch manager");
88     }
89
90     // *************
91     // SwitchManager
92     // *************
93
94     /**
95      * Get the collection of switches that are in the "ready" state.  Note
96      * that the collection may be concurrently modified
97      * @return A {@link Collection} containing the switches that are ready.
98      */
99     public Collection<NodeId> getReadySwitches() {
100         Collection<SwitchState> ready =
101                 Collections2.filter(switches.values(),
102                             new Predicate<SwitchState>() {
103                     @Override
104                     public boolean apply(SwitchState input) {
105                         return SwitchStatus.READY.equals(input.status);
106                     }
107                 });
108         return Collections2.transform(ready,
109                                       new Function<SwitchState, NodeId>() {
110             @Override
111             public NodeId apply(SwitchState input) {
112                 return input.nodeId;
113             }
114         });
115     }
116
117     /**
118      * Check whether the specified switch is in the ready state
119      * @param nodeId the node
120      * @return <code>true</code> if the switch is in the ready state
121      */
122     public boolean isSwitchReady(NodeId nodeId) {
123         SwitchState state = switches.get(nodeId);
124         if (state == null) return false;
125         return SwitchStatus.READY.equals(state.status);
126     }
127     
128     public Set<NodeConnectorId> getExternalPorts(NodeId nodeId) {
129         SwitchState state = switches.get(nodeId);
130         if (state == null) return Collections.emptySet();
131         return state.externalPorts;
132     }
133     
134     public NodeConnectorId getTunnelPort(NodeId nodeId) {
135         SwitchState state = switches.get(nodeId);
136         if (state == null) return null;
137         return state.tunnelPort;
138     }
139     
140     public IpAddress getTunnelIP(NodeId nodeId) {
141         SwitchState state = switches.get(nodeId);
142         if (state == null || state.nodeConfig == null) return null;
143         return state.nodeConfig.getTunnelIp();
144     }
145
146     /**
147      * Add a {@link SwitchListener} to get notifications of switch events
148      * @param listener the {@link SwitchListener} to add
149      */
150     public void registerListener(SwitchListener listener) {
151         listeners.add(listener);
152     }
153
154     /**
155      * Set the encapsulation format the specified value
156      * @param format The new format
157      */
158     public void setEncapsulationFormat(EncapsulationFormat format) {
159         // No-op for now
160     }
161
162     // *************
163     // AutoCloseable
164     // *************
165
166     @Override
167     public void close() throws Exception {
168         nodesReg.close();
169         nodesConfigReg.close();
170     }
171
172     // ******************
173     // DataChangeListener
174     // ******************
175
176     private class NodesListener implements DataChangeListener {
177
178         @Override
179         public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, 
180                                                        DataObject> change) {
181             for (InstanceIdentifier<?> iid : change.getRemovedPaths()) {
182                 DataObject old = change.getOriginalData().get(iid);
183                 if (old != null && old instanceof Node) {
184                     removeSwitch(((Node)old).getId());
185                 }
186             }
187
188             for (DataObject dao : change.getCreatedData().values()) {
189                 updateSwitch(dao);
190             }
191             for (DataObject dao : change.getUpdatedData().values()) {
192                 updateSwitch(dao);
193             }
194         }
195     }
196     
197     private class NodesConfigListener implements DataChangeListener {
198
199         @Override
200         public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, 
201                                                        DataObject> change) {
202             readSwitches();
203         }
204     }
205
206     // **************
207     // Implementation
208     // **************
209
210     private SwitchState getSwitchState(NodeId id) {
211         SwitchState state = switches.get(id); 
212         if (state == null) {
213             state = new SwitchState(id);
214             SwitchState old = 
215                     switches.putIfAbsent(id, state);
216             if (old != null)
217                 state = old;
218         }
219         return state;
220     }
221     
222     private void updateSwitch(DataObject dao) {
223         if (!(dao instanceof Node)) return;
224         // Switches are registered as Nodes in the inventory; OpenFlow switches
225         // are of type FlowCapableNode
226         Node node = (Node)dao;
227         FlowCapableNode fcn = node.getAugmentation(FlowCapableNode.class);
228         if (fcn == null) return;
229
230         LOG.debug("{} update", node.getId());
231
232         SwitchState state = getSwitchState(node.getId());
233
234         state.setNode(node);
235         
236         if (SwitchStatus.DISCONNECTED.equals(state.status))
237             switchConnected(node.getId());
238         else if (SwitchStatus.READY.equals(state.status))
239             notifySwitchUpdated(node.getId());
240     }
241     
242     private void updateSwitchConfig(NodeId nodeId, OfOverlayNodeConfig config) {
243         SwitchState state = getSwitchState(nodeId);
244         state.setConfig(config);
245         notifySwitchUpdated(nodeId);
246     }
247
248     private void notifySwitchUpdated(NodeId nodeId) {
249         for (SwitchListener listener : listeners) {
250             listener.switchUpdated(nodeId);
251         }
252     }
253
254     // XXX there's a race condition here if a switch exists at startup and is
255     // removed very quickly.
256     private final FutureCallback<Optional<Nodes>> readSwitchesCallback =
257             new FutureCallback<Optional<Nodes>>() {
258         @Override
259         public void onSuccess(Optional<Nodes> result) {
260             if (result.isPresent() && result.get() instanceof Nodes) {
261                 Nodes nodes = (Nodes)result.get();
262                 for (Node node : nodes.getNode()) {
263                     updateSwitch(node);
264                 }
265             }
266         }
267
268         @Override
269         public void onFailure(Throwable t) {
270             LOG.error("Count not read switch information", t);
271         }
272     };
273
274     private final FutureCallback<Optional<Nodes>> readSwitchConfCallback =
275             new FutureCallback<Optional<Nodes>>() {
276         @Override
277         public void onSuccess(Optional<Nodes> result) {
278             if (result.isPresent()) {
279                 Nodes nodes = (Nodes)result.get();
280                 for (Node node : nodes.getNode()) {
281                     OfOverlayNodeConfig config = 
282                             node.getAugmentation(OfOverlayNodeConfig.class);
283                     if (config != null)
284                         updateSwitchConfig(node.getId(), config);
285                 }
286             }
287         }
288
289         @Override
290         public void onFailure(Throwable t) {
291             LOG.error("Count not read switch information", t);
292         }
293     };
294
295     /**
296      * Read the set of switches from the ODL inventory and update our internal
297      * map.
298      *
299      * <p>This is safe only if there can only be one notification at a time,
300      * as there are race conditions in the face of concurrent data change
301      * notifications
302      */
303     private void readSwitches() {
304         if (dataProvider != null) {
305             ListenableFuture<Optional<Nodes>> future = 
306                     dataProvider.newReadOnlyTransaction()
307                     .read(LogicalDatastoreType.CONFIGURATION, nodesIid);
308             Futures.addCallback(future, readSwitchConfCallback);
309             
310             future = dataProvider.newReadOnlyTransaction()
311                     .read(LogicalDatastoreType.OPERATIONAL, nodesIid);
312             Futures.addCallback(future, readSwitchesCallback);
313         }
314     }
315
316     /**
317      * Set the ready state of the node to PREPARING and begin the initialization
318      * process
319      */
320     private void switchConnected(NodeId nodeId) {
321         SwitchState state = switches.get(nodeId);
322         if (state != null) {
323             // XXX - TODO - For now we just go straight to ready state.
324             // need to configure tunnels and tables as needed
325             switchReady(nodeId);
326             LOG.info("New switch {} connected", nodeId);
327         }
328     }
329
330     /**
331      * Set the ready state of the node to READY and notify listeners
332      */
333     private void switchReady(NodeId nodeId) {
334         SwitchState state = switches.get(nodeId);
335         if (state != null) {
336             state.status = SwitchStatus.READY;
337             for (SwitchListener listener : listeners) {
338                 listener.switchReady(nodeId);
339             }
340         }
341     }
342
343     /**
344      * Remove the switch from the switches we're keeping track of and
345      * notify listeners
346      */
347     private void removeSwitch(NodeId nodeId) {
348         switches.remove(nodeId);
349         for (SwitchListener listener : listeners) {
350             listener.switchRemoved(nodeId);
351         }
352         LOG.info("Switch {} removed", nodeId);
353     }
354
355     protected enum SwitchStatus {
356         /**
357          * The switch is not currently connected
358          */
359         DISCONNECTED,
360         /**
361          * The switch is connected but not yet configured
362          */
363         PREPARING,
364         /**
365          * The switch is ready to for policy rules to be installed
366          */
367         READY
368     }
369
370     /**
371      * Internal representation of the state of a connected switch
372      */
373     protected static class SwitchState {
374         NodeId nodeId;
375
376         Node switchNode;
377         OfOverlayNodeConfig nodeConfig;
378         
379         NodeConnectorId tunnelPort;
380         Set<NodeConnectorId> externalPorts = Collections.emptySet();
381
382         SwitchStatus status = SwitchStatus.DISCONNECTED;
383         
384         public SwitchState(NodeId switchNode) {
385             super();
386             nodeId = switchNode;
387         }
388
389         /**
390          * Constructor used for tests
391          */
392         public SwitchState(NodeId node,
393                            NodeConnectorId tunnelPort,
394                            Set<NodeConnectorId> externalPorts,
395                            OfOverlayNodeConfig nodeConfig) {
396             this.nodeId = node;
397             this.tunnelPort = tunnelPort;
398             this.externalPorts = externalPorts;
399             this.nodeConfig = nodeConfig;
400         }
401         
402         private void update() {
403             if (switchNode == null) return;
404             FlowCapableNode fcn = 
405                     switchNode.getAugmentation(FlowCapableNode.class);
406             if (fcn == null) return;
407             
408             List<NodeConnector> ports = switchNode.getNodeConnector();
409             HashSet<NodeConnectorId> externalPorts = new HashSet<>();
410             if (ports != null) {
411                 for (NodeConnector nc : ports) {
412                     FlowCapableNodeConnector fcnc = 
413                             nc.getAugmentation(FlowCapableNodeConnector.class);
414                     if (fcnc == null || fcnc.getName() == null) continue;
415
416                     if (fcnc.getName().matches(".*_(vxlan|tun)\\d+")) {
417                         tunnelPort = nc.getId();
418                     }
419                     if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null ) {
420                         for (String pattern : nodeConfig.getExternalInterfaces()) {
421                             if (fcnc.getName().matches(pattern))
422                                 externalPorts.add(nc.getId());
423                         }
424                     }
425                 }
426             }
427             this.externalPorts = Collections.unmodifiableSet(externalPorts);
428         }
429         
430         public void setNode(Node switchNode) {
431             this.switchNode = switchNode;
432             update();
433         }
434     
435         public void setConfig(OfOverlayNodeConfig config) {
436             nodeConfig = config;
437             update();
438         }
439     }
440
441 }