Implement SFC integration
[groupbasedpolicy.git] / renderers / ofoverlay / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / node / 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.node;
10
11 import static com.google.common.base.Preconditions.checkNotNull;
12
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Set;
21 import java.util.concurrent.CopyOnWriteArrayList;
22
23 import javax.annotation.Nullable;
24
25 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchListener;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.EncapsulationFormat;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayNodeConfig;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.Tunnel;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.TunnelBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeBase;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlan;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.google.common.base.Function;
47 import com.google.common.base.Objects;
48 import com.google.common.base.Predicate;
49 import com.google.common.collect.FluentIterable;
50 import com.google.common.collect.ImmutableList;
51 import com.google.common.collect.ImmutableSet;
52 import com.google.common.collect.Maps;
53
54 /**
55  * Manage connected switches and ensure their configuration is set up
56  * correctly
57  */
58 public class SwitchManager implements AutoCloseable {
59
60     private static final Logger LOG = LoggerFactory.getLogger(SwitchManager.class);
61
62     protected static Map<NodeId, SwitchState> switches = new HashMap<>();
63     protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>();
64
65     private final FlowCapableNodeListener nodeListener;
66     private final OfOverlayNodeListener ofOverlayNodeListener;
67     private final FlowCapableNodeConnectorListener nodeConnectorListener;
68
69     public SwitchManager(DataBroker dataProvider) {
70         if (dataProvider == null) {
71             LOG.warn("No data provider for {}. Listeners {}, {}, {} are not registered.",
72                     SwitchManager.class.getSimpleName(), FlowCapableNodeListener.class.getSimpleName(),
73                     OfOverlayNodeListener.class.getSimpleName(), FlowCapableNodeConnectorListener.class.getSimpleName());
74             nodeListener = null;
75             ofOverlayNodeListener = null;
76             nodeConnectorListener = null;
77         } else {
78             nodeListener = new FlowCapableNodeListener(dataProvider, this);
79             ofOverlayNodeListener = new OfOverlayNodeListener(dataProvider, this);
80             nodeConnectorListener = new FlowCapableNodeConnectorListener(dataProvider, this);
81         }
82         LOG.debug("Initialized OFOverlay switch manager");
83     }
84
85     // When first endpoint is attached to switch, it can be ready
86     public static void activatingSwitch(NodeId nodeId) {
87         switches.get(nodeId).setHasEndpoints(true);
88         switches.get(nodeId).updateStatus();
89     }
90
91     // When last endpoint is removed from switch, it is no longer ready
92     public static void deactivatingSwitch(NodeId nodeId) {
93         switches.get(nodeId).setHasEndpoints(false);;
94         switches.get(nodeId).updateStatus();
95     }
96
97     /**
98      * Get the collection of switches that are in the "ready" state. Note
99      * that the collection is immutable.
100      *
101      * @return A {@link Collection} containing the switches that are ready.
102      */
103     public synchronized Collection<NodeId> getReadySwitches() {
104         ImmutableList<NodeId> readySwitches = FluentIterable.from(switches.values())
105             .filter(new Predicate<SwitchState>() {
106
107                 @Override
108                 public boolean apply(SwitchState input) {
109                     return input.status == SwitchStatus.READY;
110                 }
111             })
112             .transform(new Function<SwitchState, NodeId>() {
113
114                 @Override
115                 public NodeId apply(SwitchState input) {
116                     return input.nodeId;
117                 }
118             })
119             .toList();
120         LOG.trace("Get ready switches: {}", readySwitches);
121         return readySwitches;
122     }
123
124     public synchronized Set<NodeConnectorId> getExternalPorts(NodeId nodeId) {
125         SwitchState state = switches.get(nodeId);
126         if (state == null)
127             return Collections.emptySet();
128         return ImmutableSet.copyOf(state.externalPorts);
129     }
130
131     public static synchronized NodeConnectorId getTunnelPort(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) {
132         SwitchState state = switches.get(nodeId);
133         if (state == null) {
134             return null;
135         }
136         TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType);
137         if (tunnel == null) {
138             return null;
139         }
140         return tunnel.getNodeConnectorId();
141     }
142
143     public static synchronized IpAddress getTunnelIP(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) {
144         SwitchState state = switches.get(nodeId);
145         if (state == null) {
146             return null;
147         }
148         TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType);
149         if (tunnel == null) {
150             return null;
151         }
152         return tunnel.getIp();
153     }
154
155     /**
156      * Add a {@link SwitchListener} to get notifications of switch events
157      *
158      * @param listener the {@link SwitchListener} to add
159      */
160     public void registerListener(SwitchListener listener) {
161         listeners.add(listener);
162     }
163
164     /**
165      * Set the encapsulation format the specified value
166      *
167      * @param format The new format
168      */
169     public void setEncapsulationFormat(EncapsulationFormat format) {
170         // No-op for now
171     }
172
173     synchronized void updateSwitch(NodeId nodeId, @Nullable FlowCapableNode fcNode) {
174         SwitchState state = getSwitchState(checkNotNull(nodeId));
175         SwitchStatus oldStatus = state.status;
176         state.setFlowCapableNode(fcNode);
177         handleSwitchState(state, oldStatus);
178     }
179
180     synchronized void updateSwitchNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid,
181             @Nullable FlowCapableNodeConnector fcnc) {
182         NodeId nodeId = ncIid.firstKeyOf(Node.class, NodeKey.class).getId();
183         SwitchState state = getSwitchState(nodeId);
184         SwitchStatus oldStatus = state.status;
185         state.setNodeConnectorConfig(ncIid, fcnc);
186         handleSwitchState(state, oldStatus);
187     }
188
189     synchronized void updateSwitchConfig(NodeId nodeId, @Nullable OfOverlayNodeConfig config) {
190         SwitchState state = getSwitchState(checkNotNull(nodeId));
191         SwitchStatus oldStatus = state.status;
192         state.setConfig(config);
193         handleSwitchState(state, oldStatus);
194     }
195
196     private SwitchState getSwitchState(NodeId id) {
197         SwitchState state = switches.get(id);
198         if (state == null) {
199             state = new SwitchState(id);
200             switches.put(id, state);
201             LOG.trace("Switch {} added to switches {}", state.nodeId.getValue(), switches.keySet());
202         }
203         return state;
204     }
205
206     private void handleSwitchState(SwitchState state, SwitchStatus oldStatus) {
207         if (oldStatus == SwitchStatus.READY && state.status != SwitchStatus.READY) {
208             LOG.info("Switch {} removed", state.nodeId.getValue());
209             notifySwitchRemoved(state.nodeId);
210         } else if (oldStatus != SwitchStatus.READY && state.status == SwitchStatus.READY) {
211             LOG.info("Switch {} ready", state.nodeId.getValue());
212             notifySwitchReady(state.nodeId);
213         } else if (oldStatus == SwitchStatus.READY && state.status == SwitchStatus.READY) {
214             // TODO Be msunal we could improve this by ignoring of updates where uninteresting fields are changed
215             LOG.debug("Switch {} updated", state.nodeId.getValue());
216             notifySwitchUpdated(state.nodeId);
217         }
218         if (state.status == SwitchStatus.DISCONNECTED && state.isConfigurationEmpty()) {
219             switches.remove(state.nodeId);
220             LOG.trace("Switch {} removed from switches {}", state.nodeId, switches.keySet());
221         }
222     }
223
224     private void notifySwitchRemoved(NodeId nodeId) {
225         for (SwitchListener listener : listeners) {
226             listener.switchRemoved(nodeId);
227         }
228     }
229
230     private void notifySwitchReady(NodeId nodeId) {
231         for (SwitchListener listener : listeners) {
232             listener.switchReady(nodeId);
233         }
234     }
235
236     private void notifySwitchUpdated(NodeId nodeId) {
237         for (SwitchListener listener : listeners) {
238             listener.switchUpdated(nodeId);
239         }
240     }
241
242     @Override
243     public void close() throws Exception {
244         nodeListener.close();
245         ofOverlayNodeListener.close();
246         nodeConnectorListener.close();
247     }
248
249     /**
250      * Internal representation of the state of a connected switch
251      */
252     protected static final class SwitchState {
253
254         private NodeId nodeId;
255         private FlowCapableNode fcNode;
256         private OfOverlayNodeConfig nodeConfig;
257         private Map<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIid = Maps.newHashMap();
258         private boolean hasEndpoints=false;
259
260
261         public boolean isHasEndpoints() {
262             return hasEndpoints;
263         }
264
265
266         public void setHasEndpoints(boolean hasEndpoints) {
267             this.hasEndpoints = hasEndpoints;
268         }
269
270         Map<Class<? extends TunnelTypeBase>, TunnelBuilder> tunnelBuilderByType = new HashMap<>();
271         Set<NodeConnectorId> externalPorts = new HashSet<>();
272
273         SwitchStatus status;
274
275         public SwitchState(NodeId switchNode) {
276             super();
277             nodeId = switchNode;
278         }
279
280         /**
281          * Constructor used for tests
282          */
283         public SwitchState(NodeId node, NodeConnectorId tunnelPort, Set<NodeConnectorId> externalPorts,
284                 OfOverlayNodeConfig nodeConfig) {
285             this.nodeId = node;
286             this.nodeConfig = nodeConfig;
287             update();
288             this.externalPorts = externalPorts;
289         }
290
291         private void update() {
292             tunnelBuilderByType = new HashMap<>();
293             externalPorts = new HashSet<>();
294             for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : fcncByNcIid.entrySet()) {
295                 FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue();
296                 if (fcnc.getName() == null) {
297                     continue;
298                 }
299                 InstanceIdentifier<NodeConnector> ncIid = fcncByNcIidEntry.getKey();
300                 NodeConnectorId ncId = ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId();
301                 if (fcnc.getName().matches(".*(vxlan).*")) {
302                     TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(TunnelTypeVxlan.class);
303                     if (tunnelBuilder == null) {
304                         tunnelBuilder = new TunnelBuilder().setTunnelType(TunnelTypeVxlan.class);
305                         tunnelBuilderByType.put(TunnelTypeVxlan.class, tunnelBuilder);
306                     }
307                     tunnelBuilder.setNodeConnectorId(ncId);
308                 }
309                 if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null) {
310                     for (String pattern : nodeConfig.getExternalInterfaces()) {
311                         if (fcnc.getName().matches(pattern)) {
312                             externalPorts.add(ncId);
313                             break;
314                         }
315                     }
316                 }
317             }
318             if (nodeConfig != null && nodeConfig.getTunnel() != null) {
319                 for (Tunnel tunnel : nodeConfig.getTunnel()) {
320                     TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(tunnel.getTunnelType());
321                     if (tunnelBuilder == null) {
322                         tunnelBuilder = new TunnelBuilder();
323                         tunnelBuilderByType.put(tunnel.getTunnelType(), tunnelBuilder);
324                     }
325                     if (tunnel.getIp() != null) {
326                         tunnelBuilder.setIp(tunnel.getIp());
327                     }
328                     if (tunnel.getNodeConnectorId() != null) {
329                         tunnelBuilder.setNodeConnectorId(tunnel.getNodeConnectorId());
330                     }
331                     if (tunnel.getPort() != null) {
332                         tunnelBuilder.setPort(tunnel.getPort());
333                     }
334                 }
335             }
336         }
337
338         private void updateStatus() {
339             boolean tunnelWithIpAndNcExists = tunnelWithIpAndNcExists();
340             if (fcNode != null) {
341                 if (tunnelWithIpAndNcExists && isHasEndpoints()) {
342                     setStatus(SwitchStatus.READY);
343                 } else {
344                     setStatus(SwitchStatus.PREPARING);
345                 }
346             } else {
347                 setStatus(SwitchStatus.DISCONNECTED);
348             }
349         }
350
351         private void setStatus(SwitchStatus newStatus) {
352             if (Objects.equal(status, newStatus)) {
353                 return;
354             }
355             LOG.debug("Switch {} is changing status from {} to {}", nodeId.getValue(), this.status, newStatus);
356             this.status = newStatus;
357         }
358
359         private boolean tunnelWithIpAndNcExists() {
360             if (tunnelBuilderByType.isEmpty()) {
361                 LOG.trace("No tunnel on switch {}", nodeId.getValue());
362                 return false;
363             }
364             LOG.trace("Iterating over tunnel till tunnel with IP and node-connector is not found.");
365             for (TunnelBuilder tb : tunnelBuilderByType.values()) {
366                 if (tb.getIp() != null && tb.getNodeConnectorId() != null) {
367 //                    LOG.trace("Tunnel found. Type: {} IP: {} Port: {} Node-connector: {}", tb.getTunnelType()
368 //                        .getSimpleName(), tb.getIp(), tb.getPort(), tb.getNodeConnectorId());
369                     LOG.trace("Tunnel found.");
370                     return true;
371                 } else {
372 //                    LOG.trace("Tunnel which is not completed: Type: {} IP: {} Port: {} Node-connector: {}",
373 //                            tb.getTunnelType().getSimpleName(), tb.getIp(), tb.getPort(), tb.getNodeConnectorId());
374                     LOG.trace("Tunnel which is not completed");
375                 }
376             }
377             return false;
378         }
379
380         public boolean isConfigurationEmpty() {
381             if (fcNode != null)
382                 return false;
383             if (nodeConfig != null)
384                 return false;
385             if (!fcncByNcIid.isEmpty())
386                 return false;
387             return true;
388         }
389
390         public void setFlowCapableNode(FlowCapableNode fcNode) {
391             this.fcNode = fcNode;
392             LOG.trace("Switch {} set {}", nodeId.getValue(), fcNode);
393             updateStatus();
394         }
395
396         public void setConfig(OfOverlayNodeConfig config) {
397             this.nodeConfig = config;
398             LOG.trace("Switch {} set {}", nodeId.getValue(), config);
399             update();
400             updateStatus();
401         }
402
403         public void setNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid, FlowCapableNodeConnector fcnc) {
404             if (fcnc == null) {
405                 fcncByNcIid.remove(ncIid);
406             } else {
407                 fcncByNcIid.put(ncIid, fcnc);
408             }
409             LOG.trace("Switch {} node connector {} set {}", nodeId.getValue(),
410                     ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue(), fcnc);
411             update();
412             updateStatus();
413         }
414
415     }
416
417     protected enum SwitchStatus {
418         /**
419          * The switch is not currently connected
420          */
421         DISCONNECTED,
422         /**
423          * The switch is connected but not yet configured
424          */
425         PREPARING,
426         /**
427          * The switch is ready to for policy rules to be installed
428          */
429         READY
430     }
431
432
433
434 }