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