2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.groupbasedpolicy.renderer.ofoverlay;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.HashSet;
14 import java.util.List;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.CopyOnWriteArrayList;
18 import java.util.concurrent.ScheduledExecutorService;
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;
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;
50 * Manage connected switches and ensure their configuration is set up
54 public class SwitchManager implements AutoCloseable {
55 private static final Logger LOG =
56 LoggerFactory.getLogger(SwitchManager.class);
58 private final DataBroker dataProvider;
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;
68 protected ConcurrentHashMap<NodeId, SwitchState> switches =
69 new ConcurrentHashMap<>();
70 protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>();
72 public SwitchManager(DataBroker dataProvider,
73 ScheduledExecutorService executor) {
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);
87 LOG.debug("Initialized OFOverlay switch manager");
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.
99 public Collection<NodeId> getReadySwitches() {
100 Collection<SwitchState> ready =
101 Collections2.filter(switches.values(),
102 new Predicate<SwitchState>() {
104 public boolean apply(SwitchState input) {
105 return SwitchStatus.READY.equals(input.status);
108 return Collections2.transform(ready,
109 new Function<SwitchState, NodeId>() {
111 public NodeId apply(SwitchState input) {
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
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);
128 public Set<NodeConnectorId> getExternalPorts(NodeId nodeId) {
129 SwitchState state = switches.get(nodeId);
130 if (state == null) return Collections.emptySet();
131 return state.externalPorts;
134 public NodeConnectorId getTunnelPort(NodeId nodeId) {
135 SwitchState state = switches.get(nodeId);
136 if (state == null) return null;
137 return state.tunnelPort;
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();
147 * Add a {@link SwitchListener} to get notifications of switch events
148 * @param listener the {@link SwitchListener} to add
150 public void registerListener(SwitchListener listener) {
151 listeners.add(listener);
155 * Set the encapsulation format the specified value
156 * @param format The new format
158 public void setEncapsulationFormat(EncapsulationFormat format) {
167 public void close() throws Exception {
169 nodesConfigReg.close();
172 // ******************
173 // DataChangeListener
174 // ******************
176 private class NodesListener implements DataChangeListener {
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());
188 for (DataObject dao : change.getCreatedData().values()) {
191 for (DataObject dao : change.getUpdatedData().values()) {
197 private class NodesConfigListener implements DataChangeListener {
200 public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>,
201 DataObject> change) {
210 private SwitchState getSwitchState(NodeId id) {
211 SwitchState state = switches.get(id);
213 state = new SwitchState(id);
215 switches.putIfAbsent(id, state);
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;
230 LOG.debug("{} update", node.getId());
232 SwitchState state = getSwitchState(node.getId());
236 if (SwitchStatus.DISCONNECTED.equals(state.status))
237 switchConnected(node.getId());
238 else if (SwitchStatus.READY.equals(state.status))
239 notifySwitchUpdated(node.getId());
242 private void updateSwitchConfig(NodeId nodeId, OfOverlayNodeConfig config) {
243 SwitchState state = getSwitchState(nodeId);
244 state.setConfig(config);
245 notifySwitchUpdated(nodeId);
248 private void notifySwitchUpdated(NodeId nodeId) {
249 for (SwitchListener listener : listeners) {
250 listener.switchUpdated(nodeId);
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>>() {
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()) {
269 public void onFailure(Throwable t) {
270 LOG.error("Count not read switch information", t);
274 private final FutureCallback<Optional<Nodes>> readSwitchConfCallback =
275 new FutureCallback<Optional<Nodes>>() {
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);
284 updateSwitchConfig(node.getId(), config);
290 public void onFailure(Throwable t) {
291 LOG.error("Count not read switch information", t);
296 * Read the set of switches from the ODL inventory and update our internal
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
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);
310 future = dataProvider.newReadOnlyTransaction()
311 .read(LogicalDatastoreType.OPERATIONAL, nodesIid);
312 Futures.addCallback(future, readSwitchesCallback);
317 * Set the ready state of the node to PREPARING and begin the initialization
320 private void switchConnected(NodeId nodeId) {
321 SwitchState state = switches.get(nodeId);
323 // XXX - TODO - For now we just go straight to ready state.
324 // need to configure tunnels and tables as needed
326 LOG.info("New switch {} connected", nodeId);
331 * Set the ready state of the node to READY and notify listeners
333 private void switchReady(NodeId nodeId) {
334 SwitchState state = switches.get(nodeId);
336 state.status = SwitchStatus.READY;
337 for (SwitchListener listener : listeners) {
338 listener.switchReady(nodeId);
344 * Remove the switch from the switches we're keeping track of and
347 private void removeSwitch(NodeId nodeId) {
348 switches.remove(nodeId);
349 for (SwitchListener listener : listeners) {
350 listener.switchRemoved(nodeId);
352 LOG.info("Switch {} removed", nodeId);
355 protected enum SwitchStatus {
357 * The switch is not currently connected
361 * The switch is connected but not yet configured
365 * The switch is ready to for policy rules to be installed
371 * Internal representation of the state of a connected switch
373 protected static class SwitchState {
377 OfOverlayNodeConfig nodeConfig;
379 NodeConnectorId tunnelPort;
380 Set<NodeConnectorId> externalPorts = Collections.emptySet();
382 SwitchStatus status = SwitchStatus.DISCONNECTED;
384 public SwitchState(NodeId switchNode) {
390 * Constructor used for tests
392 public SwitchState(NodeId node,
393 NodeConnectorId tunnelPort,
394 Set<NodeConnectorId> externalPorts,
395 OfOverlayNodeConfig nodeConfig) {
397 this.tunnelPort = tunnelPort;
398 this.externalPorts = externalPorts;
399 this.nodeConfig = nodeConfig;
402 private void update() {
403 if (switchNode == null) return;
404 FlowCapableNode fcn =
405 switchNode.getAugmentation(FlowCapableNode.class);
406 if (fcn == null) return;
408 List<NodeConnector> ports = switchNode.getNodeConnector();
409 HashSet<NodeConnectorId> externalPorts = new HashSet<>();
411 for (NodeConnector nc : ports) {
412 FlowCapableNodeConnector fcnc =
413 nc.getAugmentation(FlowCapableNodeConnector.class);
414 if (fcnc == null || fcnc.getName() == null) continue;
416 if (fcnc.getName().matches(".*_(vxlan|tun)\\d+")) {
417 tunnelPort = nc.getId();
419 if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null ) {
420 for (String pattern : nodeConfig.getExternalInterfaces()) {
421 if (fcnc.getName().matches(pattern))
422 externalPorts.add(nc.getId());
427 this.externalPorts = Collections.unmodifiableSet(externalPorts);
430 public void setNode(Node switchNode) {
431 this.switchNode = switchNode;
435 public void setConfig(OfOverlayNodeConfig config) {