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