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.node;
11 import static com.google.common.base.Preconditions.checkNotNull;
12 import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.getOfPortNum;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
20 import java.util.Map.Entry;
22 import java.util.concurrent.CopyOnWriteArrayList;
24 import javax.annotation.Nullable;
26 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext;
27 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchListener;
28 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.Name;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.EncapsulationFormat;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayNodeConfig;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.ExternalInterfaces;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.Tunnel;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.nodes.node.TunnelBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeBase;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlan;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.overlay.rev150105.TunnelTypeVxlanGpe;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
51 import com.google.common.base.Function;
52 import com.google.common.base.Objects;
53 import com.google.common.base.Predicate;
54 import com.google.common.collect.Collections2;
55 import com.google.common.collect.FluentIterable;
56 import com.google.common.collect.ImmutableList;
57 import com.google.common.collect.ImmutableSet;
58 import com.google.common.collect.Maps;
61 * Manage connected switches and ensure their configuration is set up
64 public class SwitchManager implements AutoCloseable {
66 private static final Logger LOG = LoggerFactory.getLogger(SwitchManager.class);
68 protected static Map<NodeId, SwitchState> switches = new HashMap<>();
69 protected List<SwitchListener> listeners = new CopyOnWriteArrayList<>();
71 private final FlowCapableNodeListener nodeListener;
72 private final OfOverlayNodeListener ofOverlayNodeListener;
73 private final FlowCapableNodeConnectorListener nodeConnectorListener;
75 public SwitchManager(DataBroker dataProvider) {
76 if (dataProvider == null) {
77 LOG.warn("No data provider for {}. Listeners {}, {}, {} are not registered.",
78 SwitchManager.class.getSimpleName(), FlowCapableNodeListener.class.getSimpleName(),
79 OfOverlayNodeListener.class.getSimpleName(), FlowCapableNodeConnectorListener.class.getSimpleName());
81 ofOverlayNodeListener = null;
82 nodeConnectorListener = null;
84 nodeListener = new FlowCapableNodeListener(dataProvider, this);
85 ofOverlayNodeListener = new OfOverlayNodeListener(dataProvider, this);
86 nodeConnectorListener = new FlowCapableNodeConnectorListener(dataProvider, this);
88 LOG.debug("Initialized OFOverlay switch manager");
91 // When first endpoint is attached to switch, it can be ready
92 public static void activatingSwitch(NodeId nodeId) {
93 SwitchState state = switches.get(nodeId);
95 state = new SwitchState(nodeId);
96 switches.put(nodeId, state);
98 state.setHasEndpoints(true);
102 // When last endpoint is removed from switch, it is no longer ready
103 public static void deactivatingSwitch(NodeId nodeId) {
104 SwitchState state = switches.get(nodeId);
106 LOG.error("No SwitchState for {} in deactivatingSwitch. This should not happen.",nodeId);
109 state.setHasEndpoints(false);
110 state.updateStatus();
113 public synchronized InstanceIdentifier<NodeConnector> getNodeConnectorIidForPortName(Name portName) {
114 for (SwitchState sw : switches.values()) {
115 if (sw.fcncByNcIid == null) {
118 for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : sw.fcncByNcIid
120 FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue();
121 if (portName.getValue().equals(fcnc.getName())) {
122 return fcncByNcIidEntry.getKey();
130 * Get the collection of switches that are in the "ready" state. Note
131 * that the collection is immutable.
133 * @return A {@link Collection} containing the switches that are ready.
135 public synchronized Collection<NodeId> getReadySwitches() {
136 ImmutableList<NodeId> readySwitches = FluentIterable.from(switches.values())
137 .filter(new Predicate<SwitchState>() {
140 public boolean apply(SwitchState input) {
141 return input.status == SwitchStatus.READY;
144 .transform(new Function<SwitchState, NodeId>() {
147 public NodeId apply(SwitchState input) {
152 LOG.trace("Get ready switches: {}", readySwitches);
153 return readySwitches;
156 public synchronized Set<NodeConnectorId> getExternalPorts(NodeId nodeId) {
157 SwitchState state = switches.get(nodeId);
159 return Collections.emptySet();
161 return ImmutableSet.copyOf(state.externalPorts);
164 public Set<Long> getExternalPortNumbers(NodeId nodeId) {
165 Set<Long> extPortNumbers = new HashSet<>();
166 for(NodeConnectorId nc : getExternalPorts(nodeId)) {
169 portNum = getOfPortNum(nc);
170 } catch (NumberFormatException ex) {
171 LOG.warn("Could not parse port number {}", nc, ex);
174 extPortNumbers.add(portNum);
176 return extPortNumbers;
179 public synchronized Collection<NodeConnectorId> getTunnelPorts(NodeId nodeId) {
180 Collection<NodeConnectorId> ncIds = new HashSet<>();
181 SwitchState state = switches.get(nodeId);
182 if (state == null ) {
183 return Collections.emptySet();
185 ncIds = Collections2.transform(state.tunnelBuilderByType.values(),new Function<TunnelBuilder, NodeConnectorId>() {
188 public NodeConnectorId apply(TunnelBuilder input) {
189 return input.getNodeConnectorId();
195 public synchronized NodeConnectorId getTunnelPort(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) {
196 SwitchState state = switches.get(nodeId);
200 TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType);
201 if (tunnel == null) {
204 return tunnel.getNodeConnectorId();
207 public synchronized IpAddress getTunnelIP(NodeId nodeId, Class<? extends TunnelTypeBase> tunnelType) {
208 SwitchState state = switches.get(nodeId);
212 TunnelBuilder tunnel = state.tunnelBuilderByType.get(tunnelType);
213 if (tunnel == null) {
216 return tunnel.getIp();
220 * Add a {@link SwitchListener} to get notifications of switch events
222 * @param listener the {@link SwitchListener} to add
224 public void registerListener(SwitchListener listener) {
225 listeners.add(listener);
229 * Set the encapsulation format the specified value
231 * @param format The new format
233 public void setEncapsulationFormat(EncapsulationFormat format) {
237 synchronized void updateSwitch(NodeId nodeId, @Nullable FlowCapableNode fcNode) {
238 SwitchState state = getSwitchState(checkNotNull(nodeId));
239 SwitchStatus oldStatus = state.status;
240 state.setFlowCapableNode(fcNode);
241 handleSwitchState(state, oldStatus);
244 synchronized void updateSwitchNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid,
245 @Nullable FlowCapableNodeConnector fcnc) {
246 NodeId nodeId = ncIid.firstKeyOf(Node.class, NodeKey.class).getId();
247 SwitchState state = getSwitchState(nodeId);
248 SwitchStatus oldStatus = state.status;
249 state.setNodeConnectorConfig(ncIid, fcnc);
250 handleSwitchState(state, oldStatus);
253 synchronized void updateSwitchConfig(NodeId nodeId, @Nullable OfOverlayNodeConfig config) {
254 SwitchState state = getSwitchState(checkNotNull(nodeId));
255 SwitchStatus oldStatus = state.status;
256 state.setConfig(config);
257 handleSwitchState(state, oldStatus);
260 private SwitchState getSwitchState(NodeId id) {
261 SwitchState state = switches.get(id);
263 state = new SwitchState(id);
264 switches.put(id, state);
265 LOG.trace("Switch {} added to switches {}", state.nodeId.getValue(), switches.keySet());
270 private void handleSwitchState(SwitchState state, SwitchStatus oldStatus) {
271 if (oldStatus == SwitchStatus.READY && state.status != SwitchStatus.READY) {
272 LOG.info("Switch {} removed", state.nodeId.getValue());
273 notifySwitchRemoved(state.nodeId);
274 } else if (oldStatus != SwitchStatus.READY && state.status == SwitchStatus.READY) {
275 LOG.info("Switch {} ready", state.nodeId.getValue());
276 notifySwitchReady(state.nodeId);
277 } else if (oldStatus == SwitchStatus.READY && state.status == SwitchStatus.READY) {
278 // TODO Be msunal we could improve this by ignoring of updates where uninteresting fields are changed
279 LOG.debug("Switch {} updated", state.nodeId.getValue());
280 notifySwitchUpdated(state.nodeId);
282 if (state.status == SwitchStatus.DISCONNECTED && state.isConfigurationEmpty()) {
283 switches.remove(state.nodeId);
284 LOG.trace("Switch {} removed from switches {}", state.nodeId, switches.keySet());
288 private void notifySwitchRemoved(NodeId nodeId) {
289 for (SwitchListener listener : listeners) {
290 listener.switchRemoved(nodeId);
294 private void notifySwitchReady(NodeId nodeId) {
295 for (SwitchListener listener : listeners) {
296 listener.switchReady(nodeId);
300 private void notifySwitchUpdated(NodeId nodeId) {
301 for (SwitchListener listener : listeners) {
302 listener.switchUpdated(nodeId);
307 public void close() throws Exception {
308 nodeListener.close();
309 ofOverlayNodeListener.close();
310 nodeConnectorListener.close();
314 * Internal representation of the state of a connected switch
316 protected static final class SwitchState {
318 private NodeId nodeId;
319 private FlowCapableNode fcNode;
320 private OfOverlayNodeConfig nodeConfig;
321 private Map<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIid = Maps.newHashMap();
322 private boolean hasEndpoints=false;
325 public boolean isHasEndpoints() {
330 public void setHasEndpoints(boolean hasEndpoints) {
331 this.hasEndpoints = hasEndpoints;
334 Map<Class<? extends TunnelTypeBase>, TunnelBuilder> tunnelBuilderByType = new HashMap<>();
335 Set<NodeConnectorId> externalPorts = new HashSet<>();
339 public SwitchState(NodeId switchNode) {
345 * Constructor used for tests
347 * @param node the node id
348 * @param tunnelPort the tunnel port
349 * @param externalPorts the set of expternal ports
350 * @param nodeConfig the ofoverlay node config
352 public SwitchState(NodeId node, NodeConnectorId tunnelPort, Set<NodeConnectorId> externalPorts,
353 OfOverlayNodeConfig nodeConfig) {
355 this.nodeConfig = nodeConfig;
357 this.externalPorts = externalPorts;
360 private void update() {
361 tunnelBuilderByType = new HashMap<>();
362 externalPorts = new HashSet<>();
363 if (nodeConfig != null && nodeConfig.getExternalInterfaces() != null) {
364 for (ExternalInterfaces nc : nodeConfig.getExternalInterfaces()) {
365 externalPorts.add(nc.getNodeConnectorId());
368 if (nodeConfig != null && nodeConfig.getTunnel() != null) {
369 for (Tunnel tunnel : nodeConfig.getTunnel()) {
370 TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(tunnel.getTunnelType());
371 if (tunnelBuilder == null) {
372 tunnelBuilder = new TunnelBuilder();
373 tunnelBuilderByType.put(tunnel.getTunnelType(), tunnelBuilder);
375 if (tunnel.getIp() != null) {
376 tunnelBuilder.setIp(tunnel.getIp());
378 if (tunnel.getNodeConnectorId() != null) {
379 tunnelBuilder.setNodeConnectorId(tunnel.getNodeConnectorId());
381 if (tunnel.getPort() != null) {
382 tunnelBuilder.setPort(tunnel.getPort());
386 for (Entry<InstanceIdentifier<NodeConnector>, FlowCapableNodeConnector> fcncByNcIidEntry : fcncByNcIid.entrySet()) {
387 FlowCapableNodeConnector fcnc = fcncByNcIidEntry.getValue();
388 if (fcnc.getName() == null) {
391 InstanceIdentifier<NodeConnector> ncIid = fcncByNcIidEntry.getKey();
392 NodeConnectorId ncId = ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId();
393 if (fcnc.getName().matches(".*(vxlan-).*")) {
394 TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(TunnelTypeVxlan.class);
395 if (tunnelBuilder == null) {
396 tunnelBuilder = new TunnelBuilder().setTunnelType(TunnelTypeVxlan.class);
397 tunnelBuilderByType.put(TunnelTypeVxlan.class, tunnelBuilder);
399 tunnelBuilder.setNodeConnectorId(ncId);
400 } else if (fcnc.getName().matches(".*(vxlangpe-).*")) {
401 TunnelBuilder tunnelBuilder = tunnelBuilderByType.get(TunnelTypeVxlanGpe.class);
402 if (tunnelBuilder == null) {
403 tunnelBuilder = new TunnelBuilder().setTunnelType(TunnelTypeVxlanGpe.class);
404 tunnelBuilderByType.put(TunnelTypeVxlanGpe.class, tunnelBuilder);
406 tunnelBuilder.setNodeConnectorId(ncId);
411 private void updateStatus() {
412 boolean tunnelWithIpAndNcExists = tunnelWithIpAndNcExists();
413 if (fcNode != null) {
414 if (tunnelWithIpAndNcExists && isHasEndpoints()) {
415 setStatus(SwitchStatus.READY);
417 setStatus(SwitchStatus.PREPARING);
420 setStatus(SwitchStatus.DISCONNECTED);
424 private void setStatus(SwitchStatus newStatus) {
425 if (Objects.equal(status, newStatus)) {
428 LOG.debug("Switch {} is changing status from {} to {}", nodeId.getValue(), this.status, newStatus);
429 this.status = newStatus;
432 private boolean tunnelWithIpAndNcExists() {
433 if (tunnelBuilderByType.isEmpty()) {
434 LOG.trace("No tunnel on switch {}", nodeId.getValue());
437 LOG.trace("Iterating over tunnel till tunnel with IP and node-connector is not found.");
438 for (TunnelBuilder tb : tunnelBuilderByType.values()) {
439 if (tb.getIp() != null && tb.getNodeConnectorId() != null) {
440 LOG.trace("Tunnel {} found.",tb.toString());
443 LOG.trace("Tunnel is not complete for node: {}", nodeId.getValue());
449 public boolean isConfigurationEmpty() {
450 if (fcNode != null) {
453 if (nodeConfig != null) {
456 if (!fcncByNcIid.isEmpty()) {
462 public void setFlowCapableNode(FlowCapableNode fcNode) {
463 this.fcNode = fcNode;
464 LOG.trace("Switch {} set {}", nodeId.getValue(), fcNode);
468 public void setConfig(OfOverlayNodeConfig config) {
469 this.nodeConfig = config;
470 LOG.trace("Switch {} set {}", nodeId.getValue(), config);
475 public void setNodeConnectorConfig(InstanceIdentifier<NodeConnector> ncIid, FlowCapableNodeConnector fcnc) {
477 fcncByNcIid.remove(ncIid);
479 fcncByNcIid.put(ncIid, fcnc);
481 LOG.trace("Switch {} node connector {} set {}", nodeId.getValue(),
482 ncIid.firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId().getValue(), fcnc);
489 protected enum SwitchStatus {
491 * The switch is not currently connected
495 * The switch is connected but not yet configured
499 * The switch is ready to for policy rules to be installed