0f11cab8459c98327c8475d0846e074d48f27d47
[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.inventory.rev130819.NodeConnectorId;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
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.Objects;
43 import com.google.common.base.Predicate;
44 import com.google.common.collect.FluentIterable;
45 import com.google.common.collect.ImmutableList;
46 import com.google.common.collect.Maps;
47
48 /**
49  * Manage connected switches and ensure their configuration is set up
50  * correctly
51  */
52 public class SwitchManager implements AutoCloseable {
53
54     private static final Logger LOG = LoggerFactory.getLogger(SwitchManager.class);
55
56     protected Map<NodeId, SwitchState> switches = new HashMap<>();
57     protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>();
58
59     private final FlowCapableNodeListener nodeListener;
60     private final OfOverlayNodeListener ofOverlayNodeListener;
61     private final FlowCapableNodeConnectorListener nodeConnectorListener;
62
63     public SwitchManager(DataBroker dataProvider) {
64         if (dataProvider == null) {
65             LOG.warn("No data provider for {}. Listeners {}, {}, {} are not registered.",
66                     SwitchManager.class.getSimpleName(), FlowCapableNodeListener.class.getSimpleName(),
67                     OfOverlayNodeListener.class.getSimpleName(), FlowCapableNodeConnectorListener.class.getSimpleName());
68             nodeListener = null;
69             ofOverlayNodeListener = null;
70             nodeConnectorListener = null;
71         } else {
72             nodeListener = new FlowCapableNodeListener(dataProvider, this);
73             ofOverlayNodeListener = new OfOverlayNodeListener(dataProvider, this);
74             nodeConnectorListener = new FlowCapableNodeConnectorListener(dataProvider, this);
75         }
76         LOG.debug("Initialized OFOverlay switch manager");
77     }
78
79     /**
80      * Get the collection of switches that are in the "ready" state. Note
81      * that the collection is immutable.
82      *
83      * @return A {@link Collection} containing the switches that are ready.
84      */
85     public synchronized Collection<NodeId> getReadySwitches() {
86         ImmutableList<NodeId> readySwitches = FluentIterable.from(switches.values())
87             .filter(new Predicate<SwitchState>() {
88
89                 @Override
90                 public boolean apply(SwitchState input) {
91                     return input.status == SwitchStatus.READY;
92                 }
93             })
94             .transform(new Function<SwitchState, NodeId>() {
95
96                 @Override
97                 public NodeId apply(SwitchState input) {
98                     return input.nodeId;
99                 }
100             })
101             .toList();
102         LOG.trace("Get ready switches: {}", readySwitches);
103         return readySwitches;
104     }
105
106     public synchronized Set<NodeConnectorId> getExternalPorts(NodeId nodeId) {
107         SwitchState state = switches.get(nodeId);
108         if (state == null)
109             return Collections.emptySet();
110         return state.externalPorts;
111     }
112
113     public synchronized NodeConnectorId getTunnelPort(NodeId nodeId) {
114         SwitchState state = switches.get(nodeId);
115         if (state == null)
116             return null;
117         return state.tunnelPort;
118     }
119
120     public synchronized IpAddress getTunnelIP(NodeId nodeId) {
121         SwitchState state = switches.get(nodeId);
122         if (state == null || state.nodeConfig == null)
123             return null;
124         return state.nodeConfig.getTunnelIp();
125     }
126
127     /**
128      * Add a {@link SwitchListener} to get notifications of switch events
129      *
130      * @param listener the {@link SwitchListener} to add
131      */
132     public void registerListener(SwitchListener listener) {
133         listeners.add(listener);
134     }
135
136     /**
137      * Set the encapsulation format the specified value
138      *
139      * @param format The new format
140      */
141     public void setEncapsulationFormat(EncapsulationFormat format) {
142         // No-op for now
143     }
144
145     synchronized void updateSwitch(NodeId nodeId, @Nullable FlowCapableNode fcNode) {
146         SwitchState state = getSwitchState(checkNotNull(nodeId));
147         SwitchStatus oldStatus = state.status;
148         state.setFlowCapableNode(fcNode);
149         handleSwitchState(state, oldStatus);
150     }
151
152     synchronized void updateSwitchNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid,
153             @Nullable FlowCapableNodeConnector fcnc) {
154         NodeId nodeId = ncIid.firstKeyOf(Node.class, NodeKey.class).getId();
155         SwitchState state = getSwitchState(nodeId);
156         SwitchStatus oldStatus = state.status;
157         state.setNodeConnectorConfig(ncIid, fcnc);
158         handleSwitchState(state, oldStatus);
159     }
160
161     synchronized void updateSwitchConfig(NodeId nodeId, @Nullable OfOverlayNodeConfig config) {
162         SwitchState state = getSwitchState(checkNotNull(nodeId));
163         SwitchStatus oldStatus = state.status;
164         state.setConfig(config);
165         handleSwitchState(state, oldStatus);
166     }
167
168     private SwitchState getSwitchState(NodeId id) {
169         SwitchState state = switches.get(id);
170         if (state == null) {
171             state = new SwitchState(id);
172             switches.put(id, state);
173             LOG.trace("Switch {} added to switches {}", state.nodeId.getValue(), switches.keySet());
174         }
175         return state;
176     }
177
178     private void handleSwitchState(SwitchState state, SwitchStatus oldStatus) {
179         if (oldStatus == SwitchStatus.READY && state.status != SwitchStatus.READY) {
180             LOG.info("Switch {} removed", state.nodeId.getValue());
181             notifySwitchRemoved(state.nodeId);
182         } else if (oldStatus != SwitchStatus.READY && state.status == SwitchStatus.READY) {
183             LOG.info("Switch {} ready", state.nodeId.getValue());
184             notifySwitchReady(state.nodeId);
185         } else if (oldStatus == SwitchStatus.READY && state.status == SwitchStatus.READY) {
186             // TODO Be msunal we could improve this by ignoring of updates where uninteresting fields are changed
187             LOG.debug("Switch {} updated", state.nodeId.getValue());
188             notifySwitchUpdated(state.nodeId);
189         }
190         if (state.status == SwitchStatus.DISCONNECTED && state.isConfigurationEmpty()) {
191             switches.remove(state.nodeId);
192             LOG.trace("Switch {} removed from switches {}", state.nodeId, switches.keySet());
193         }
194     }
195
196     private void notifySwitchRemoved(NodeId nodeId) {
197         for (SwitchListener listener : listeners) {
198             listener.switchRemoved(nodeId);
199         }
200     }
201
202     private void notifySwitchReady(NodeId nodeId) {
203         for (SwitchListener listener : listeners) {
204             listener.switchReady(nodeId);
205         }
206     }
207
208     private void notifySwitchUpdated(NodeId nodeId) {
209         for (SwitchListener listener : listeners) {
210             listener.switchUpdated(nodeId);
211         }
212     }
213
214     @Override
215     public void close() throws Exception {
216         nodeListener.close();
217         ofOverlayNodeListener.close();
218         nodeConnectorListener.close();
219     }
220
221     /**
222      * Internal representation of the state of a connected switch
223      */
224     protected static final class SwitchState {
225
226         private NodeId nodeId;
227         private FlowCapableNode fcNode;
228         private OfOverlayNodeConfig nodeConfig;
229         private Map<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIid = Maps.newHashMap();
230
231         NodeConnectorId tunnelPort;
232         Set<NodeConnectorId> externalPorts = new HashSet<>();
233
234         SwitchStatus status;
235
236         public SwitchState(NodeId switchNode) {
237             super();
238             nodeId = switchNode;
239         }
240
241         /**
242          * Constructor used for tests
243          */
244         public SwitchState(NodeId node, NodeConnectorId tunnelPort, Set<NodeConnectorId> externalPorts,
245                 OfOverlayNodeConfig nodeConfig) {
246             this.nodeId = node;
247             this.tunnelPort = tunnelPort;
248             this.externalPorts = externalPorts;
249             this.nodeConfig = nodeConfig;
250         }
251
252         private void update() {
253             HashSet<NodeConnectorId> externalPorts = new HashSet<>();
254             NodeConnectorId tunnelPort = null;
255             for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : fcncByNcIid.entrySet()) {
256                 FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue();
257                 if (fcnc.getName() == null) {
258                     continue;
259                 }
260                 InstanceIdentifier<NodeConnector> ncIid = fcncByNcIidEntry.getKey();
261                 NodeConnectorId ncId = ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId();
262                 if (fcnc.getName().matches(".*(vxlan|tun).*")) {
263                     tunnelPort = ncId;
264                 }
265                 if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null) {
266                     for (String pattern : nodeConfig.getExternalInterfaces()) {
267                         if (fcnc.getName().matches(pattern)) {
268                             externalPorts.add(ncId);
269                             break;
270                         }
271                     }
272                 }
273             }
274             this.tunnelPort = tunnelPort;
275             this.externalPorts = Collections.unmodifiableSet(externalPorts);
276         }
277
278         private void updateStatus() {
279             boolean tunnelPortWithIpExists = tunnelPortWithIpExists();
280             if (fcNode != null) {
281                 if (tunnelPortWithIpExists) {
282                     setStatus(SwitchStatus.READY);
283                 } else {
284                     setStatus(SwitchStatus.PREPARING);
285                 }
286             } else {
287                 setStatus(SwitchStatus.DISCONNECTED);
288             }
289         }
290
291         private void setStatus(SwitchStatus newStatus) {
292             if (Objects.equal(status, newStatus)) {
293                 return;
294             }
295             LOG.debug("Switch {} is changing status from {} to {}", nodeId.getValue(), this.status, newStatus);
296             this.status = newStatus;
297         }
298
299         private boolean tunnelPortWithIpExists() {
300             boolean tunnelPortWithIpExists = false;
301             if (tunnelPort != null && nodeConfig != null && nodeConfig.getTunnelIp() != null) {
302                 tunnelPortWithIpExists = true;
303             }
304             LOG.trace("Status of tunnel on switch {} - tunnelPort: {} tunnelIp: {}", nodeId.getValue(),
305                     tunnelPort == null ? null : tunnelPort.getValue(),
306                     nodeConfig == null ? null : nodeConfig.getTunnelIp());
307             return tunnelPortWithIpExists;
308         }
309
310         public boolean isConfigurationEmpty() {
311             if (fcNode != null)
312                 return false;
313             if (nodeConfig != null)
314                 return false;
315             if (!fcncByNcIid.isEmpty())
316                 return false;
317             return true;
318         }
319
320         public void setFlowCapableNode(FlowCapableNode fcNode) {
321             this.fcNode = fcNode;
322             LOG.trace("Switch {} set {}", nodeId.getValue(), fcNode);
323             updateStatus();
324         }
325
326         public void setConfig(OfOverlayNodeConfig config) {
327             this.nodeConfig = config;
328             LOG.trace("Switch {} set {}", nodeId.getValue(), config);
329             update();
330             updateStatus();
331         }
332
333         public void setNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid, FlowCapableNodeConnector fcnc) {
334             if (fcnc == null) {
335                 fcncByNcIid.remove(ncIid);
336             } else {
337                 fcncByNcIid.put(ncIid, fcnc);
338             }
339             LOG.trace("Switch {} node connector {} set {}", nodeId.getValue(),
340                     ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue(), fcnc);
341             update();
342             updateStatus();
343         }
344
345     }
346
347     protected enum SwitchStatus {
348         /**
349          * The switch is not currently connected
350          */
351         DISCONNECTED,
352         /**
353          * The switch is connected but not yet configured
354          */
355         PREPARING,
356         /**
357          * The switch is ready to for policy rules to be installed
358          */
359         READY
360     }
361
362 }