2 * Copyright (C) 2013 Red Hat, Inc.
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
8 * Authors : Madhu Venugopal, Brent Salisbury, Sam Hague, Dave Tucker
10 package org.opendaylight.ovsdb.openstack.netvirt;
12 import org.opendaylight.controller.networkconfig.neutron.NeutronNetwork;
13 import org.opendaylight.controller.sal.core.Node;
14 import org.opendaylight.controller.sal.core.NodeConnector;
15 import org.opendaylight.controller.sal.core.Property;
16 import org.opendaylight.controller.sal.core.UpdateType;
17 import org.opendaylight.controller.switchmanager.IInventoryListener;
18 import org.opendaylight.ovsdb.lib.notation.Row;
19 import org.opendaylight.ovsdb.lib.notation.UUID;
20 import org.opendaylight.ovsdb.openstack.netvirt.api.BridgeConfigurationManager;
21 import org.opendaylight.ovsdb.openstack.netvirt.api.NetworkingProviderManager;
22 import org.opendaylight.ovsdb.openstack.netvirt.api.TenantNetworkManager;
23 import org.opendaylight.ovsdb.plugin.api.OvsdbConfigurationService;
24 import org.opendaylight.ovsdb.plugin.api.OvsdbConnectionService;
25 import org.opendaylight.ovsdb.plugin.api.OvsdbInventoryListener;
26 import org.opendaylight.ovsdb.schema.openvswitch.Interface;
27 import org.opendaylight.ovsdb.schema.openvswitch.OpenVSwitch;
28 import org.opendaylight.ovsdb.schema.openvswitch.Port;
30 import com.google.common.collect.Lists;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import java.net.InetAddress;
35 import java.util.List;
38 import java.util.concurrent.ConcurrentMap;
40 public class SouthboundHandler extends AbstractHandler implements OvsdbInventoryListener,
42 static final Logger logger = LoggerFactory.getLogger(SouthboundHandler.class);
43 //private Thread eventThread;
46 // The implementation for each of these services is resolved by the OSGi Service Manager
47 private volatile org.opendaylight.ovsdb.openstack.netvirt.api.ConfigurationService configurationService;
48 private volatile BridgeConfigurationManager bridgeConfigurationManager;
49 private volatile TenantNetworkManager tenantNetworkManager;
50 private volatile NetworkingProviderManager networkingProviderManager;
51 private volatile OvsdbConfigurationService ovsdbConfigurationService;
52 private volatile OvsdbConnectionService connectionService;
55 nodeCache = Lists.newArrayList();
59 this.triggerUpdates();
63 public void nodeAdded(Node node, InetAddress address, int port) {
64 this.enqueueEvent(new SouthboundEvent(node, SouthboundEvent.Action.ADD));
68 public void nodeRemoved(Node node) {
69 this.enqueueEvent(new SouthboundEvent(node, SouthboundEvent.Action.DELETE));
73 public void rowAdded(Node node, String tableName, String uuid, Row row) {
74 this.enqueueEvent(new SouthboundEvent(node, tableName, uuid, row, SouthboundEvent.Action.ADD));
78 public void rowUpdated(Node node, String tableName, String uuid, Row oldRow, Row newRow) {
79 if (this.isUpdateOfInterest(node, oldRow, newRow)) {
80 this.enqueueEvent(new SouthboundEvent(node, tableName, uuid, newRow, SouthboundEvent.Action.UPDATE));
85 * Ignore unnecessary updates to be even considered for processing.
86 * (Especially stats update are fast and furious).
89 private boolean isUpdateOfInterest(Node node, Row oldRow, Row newRow) {
90 if (oldRow == null) return true;
91 if (newRow.getTableSchema().getName().equals(ovsdbConfigurationService.getTableName(node, Interface.class))) {
92 // We are NOT interested in Stats only updates
93 Interface oldIntf = ovsdbConfigurationService.getTypedRow(node, Interface.class, oldRow);
94 if (oldIntf.getName() == null && oldIntf.getExternalIdsColumn() == null && oldIntf.getMacColumn() == null &&
95 oldIntf.getOpenFlowPortColumn() == null && oldIntf.getOptionsColumn() == null && oldIntf.getOtherConfigColumn() == null &&
96 oldIntf.getTypeColumn() == null) {
97 logger.trace("IGNORING Interface Update: node {}, row: {}", node, newRow);
100 } else if (newRow.getTableSchema().getName().equals(ovsdbConfigurationService.getTableName(node, Port.class))) {
101 // We are NOT interested in Stats only updates
102 Port oldPort = ovsdbConfigurationService.getTypedRow(node, Port.class, oldRow);
103 if (oldPort.getName() == null && oldPort.getExternalIdsColumn() == null && oldPort.getMacColumn() == null &&
104 oldPort.getInterfacesColumn() == null && oldPort.getTagColumn() == null && oldPort.getTrunksColumn() == null) {
105 logger.trace("IGNORING Port Update: node {}, row: {}", node, newRow);
108 } else if (newRow.getTableSchema().getName().equals(ovsdbConfigurationService.getTableName(node, OpenVSwitch.class))) {
109 OpenVSwitch oldOpenvSwitch = ovsdbConfigurationService.getTypedRow(node, OpenVSwitch.class, oldRow);
110 if (oldOpenvSwitch.getOtherConfigColumn()== null) {
111 /* we are only interested in other_config field change */
119 public void rowRemoved(Node node, String tableName, String uuid, Row row, Object context) {
120 this.enqueueEvent(new SouthboundEvent(node, tableName, uuid, row, context, SouthboundEvent.Action.DELETE));
123 public void processNodeUpdate(Node node, SouthboundEvent.Action action) {
124 if (action == SouthboundEvent.Action.DELETE) return;
125 logger.trace("Process Node added {}", node);
126 bridgeConfigurationManager.prepareNode(node);
129 private void processRowUpdate(Node node, String tableName, String uuid, Row row,
130 Object context, SouthboundEvent.Action action) {
131 if (action == SouthboundEvent.Action.DELETE) {
132 if (tableName.equalsIgnoreCase(ovsdbConfigurationService.getTableName(node, Interface.class))) {
133 logger.debug("Processing update of {}. Deleted node: {}, uuid: {}, row: {}", tableName, node, uuid, row);
134 Interface deletedIntf = ovsdbConfigurationService.getTypedRow(node, Interface.class, row);
135 NeutronNetwork network = null;
136 if (context == null) {
137 network = tenantNetworkManager.getTenantNetwork(deletedIntf);
139 network = (NeutronNetwork)context;
141 List<String> phyIfName = bridgeConfigurationManager.getAllPhysicalInterfaceNames(node);
142 logger.info("Delete interface " + deletedIntf.getName());
144 if (deletedIntf.getTypeColumn().getData().equalsIgnoreCase(NetworkHandler.NETWORK_TYPE_VXLAN) ||
145 deletedIntf.getTypeColumn().getData().equalsIgnoreCase(NetworkHandler.NETWORK_TYPE_GRE) ||
146 phyIfName.contains(deletedIntf.getName())) {
147 /* delete tunnel interfaces or physical interfaces */
148 this.handleInterfaceDelete(node, uuid, deletedIntf, false, null);
149 } else if (network != null && !network.getRouterExternal()) {
150 logger.debug("Processing update of {}:{} node {} intf {} network {}",
151 tableName, action, node, uuid, network.getNetworkUUID());
153 ConcurrentMap<String, Row> interfaces = this.ovsdbConfigurationService
154 .getRows(node, ovsdbConfigurationService.getTableName(node, Interface.class));
155 if (interfaces != null) {
156 boolean isLastInstanceOnNode = true;
157 for (String intfUUID : interfaces.keySet()) {
158 if (intfUUID.equals(uuid)) continue;
159 Interface intf = this.ovsdbConfigurationService.getTypedRow(node, Interface.class, interfaces.get(intfUUID));
160 NeutronNetwork neutronNetwork = tenantNetworkManager.getTenantNetwork(intf);
161 if (neutronNetwork != null && neutronNetwork.equals(network)) isLastInstanceOnNode = false;
163 this.handleInterfaceDelete(node, uuid, deletedIntf, isLastInstanceOnNode, network);
165 } catch (Exception e) {
166 logger.error("Error fetching Interface Rows for node " + node, e);
171 else if (tableName.equalsIgnoreCase(ovsdbConfigurationService.getTableName(node, Interface.class))) {
172 logger.debug("Processing update of {}:{} node: {}, interface uuid: {}, row: {}",
173 tableName, action, node, uuid, row);
174 Interface intf = this.ovsdbConfigurationService.getTypedRow(node, Interface.class, row);
175 NeutronNetwork network = tenantNetworkManager.getTenantNetwork(intf);
176 if (network != null && !network.getRouterExternal()) {
177 if (networkingProviderManager.getProvider(node).hasPerTenantTunneling()) {
178 int vlan = tenantNetworkManager.networkCreated(node, network.getID());
179 String portUUID = this.getPortIdForInterface(node, uuid, intf);
180 if (portUUID != null) {
181 logger.debug("Neutron Network {}:{} Created with Internal vlan {} port {}",
182 network.getNetworkUUID(), network.getNetworkName(), vlan, portUUID);
183 tenantNetworkManager.programInternalVlan(node, portUUID, network);
185 logger.trace("Neutron Network {}:{} Created with Internal vlan {} but have no portUUID",
186 network.getNetworkUUID(), network.getNetworkName(), vlan);
189 this.handleInterfaceUpdate(node, uuid, intf);
192 } else if (tableName.equalsIgnoreCase(ovsdbConfigurationService.getTableName(node, Port.class))) {
193 logger.debug("Processing update of {}:{} node: {}, port uuid: {}, row: {}", tableName, action, node, uuid, row);
194 Port port = this.ovsdbConfigurationService.getTypedRow(node, Port.class, row);
195 Set<UUID> interfaceUUIDs = port.getInterfacesColumn().getData();
196 for (UUID intfUUID : interfaceUUIDs) {
197 logger.trace("Scanning interface "+intfUUID);
199 Row intfRow = this.ovsdbConfigurationService
200 .getRow(node, ovsdbConfigurationService.getTableName(node, Interface.class),
201 intfUUID.toString());
202 Interface intf = this.ovsdbConfigurationService.getTypedRow(node, Interface.class, intfRow);
203 NeutronNetwork network = tenantNetworkManager.getTenantNetwork(intf);
204 if (network != null && !network.getRouterExternal()) {
205 logger.debug("Processing update of {}:{} node {} intf {} network {}",
206 tableName, action, node, intfUUID, network.getNetworkUUID());
207 tenantNetworkManager.programInternalVlan(node, uuid, network);
208 this.handleInterfaceUpdate(node, intfUUID.toString(), intf);
210 logger.trace("Ignoring update because there is not a neutron network {} for port {}, interface {}",
211 network, uuid, intfUUID);
213 } catch (Exception e) {
214 logger.error("Failed to process row update", e);
217 } else if (tableName.equalsIgnoreCase(ovsdbConfigurationService.getTableName(node, OpenVSwitch.class))) {
218 logger.debug("Processing update of {}:{} node: {}, ovs uuid: {}, row: {}", tableName, action, node, uuid, row);
220 ConcurrentMap<String, Row> interfaces = this.ovsdbConfigurationService
221 .getRows(node, ovsdbConfigurationService.getTableName(node, Interface.class));
222 if (interfaces != null) {
223 for (String intfUUID : interfaces.keySet()) {
224 Interface intf = ovsdbConfigurationService.getTypedRow(node, Interface.class, interfaces.get(intfUUID));
225 this.handleInterfaceUpdate(node, intfUUID, intf);
228 } catch (Exception e) {
229 logger.error("Error fetching Interface Rows for node " + node, e);
234 private void handleInterfaceUpdate (Node node, String uuid, Interface intf) {
235 logger.trace("Interface update of node: {}, uuid: {}", node, uuid);
236 NeutronNetwork network = tenantNetworkManager.getTenantNetwork(intf);
237 if (network != null) {
238 if (bridgeConfigurationManager.createLocalNetwork(node, network))
239 networkingProviderManager.getProvider(node).handleInterfaceUpdate(network, node, intf);
241 logger.debug("No tenant network found on node: {}, uuid: {} for interface: {}", node, uuid, intf);
245 private void handleInterfaceDelete (Node node, String uuid, Interface intf, boolean isLastInstanceOnNode,
246 NeutronNetwork network) {
247 logger.debug("handleInterfaceDelete: node: {}, uuid: {}, isLastInstanceOnNode: {}, interface: {}",
248 node, uuid, isLastInstanceOnNode, intf);
250 List<String> phyIfName = bridgeConfigurationManager.getAllPhysicalInterfaceNames(node);
251 if (intf.getTypeColumn().getData().equalsIgnoreCase(NetworkHandler.NETWORK_TYPE_VXLAN) ||
252 intf.getTypeColumn().getData().equalsIgnoreCase(NetworkHandler.NETWORK_TYPE_GRE) ||
253 phyIfName.contains(intf.getName())) {
254 /* delete tunnel or physical interfaces */
255 networkingProviderManager.getProvider(node).handleInterfaceDelete(intf.getTypeColumn().getData(), null, node, intf, isLastInstanceOnNode);
256 } else if (network != null) {
257 if (!network.getProviderNetworkType().equalsIgnoreCase(NetworkHandler.NETWORK_TYPE_VLAN)) { /* vlan doesn't need a tunnel endpoint */
258 if (configurationService.getTunnelEndPoint(node) == null) {
259 logger.error("Tunnel end-point configuration missing. Please configure it in OpenVSwitch Table");
263 if (isLastInstanceOnNode & networkingProviderManager.getProvider(node).hasPerTenantTunneling()) {
264 tenantNetworkManager.reclaimInternalVlan(node, uuid, network);
266 networkingProviderManager.getProvider(node).handleInterfaceDelete(network.getProviderNetworkType(), network, node, intf, isLastInstanceOnNode);
270 private String getPortIdForInterface (Node node, String uuid, Interface intf) {
272 Map<String, Row> ports = this.ovsdbConfigurationService.getRows(node, ovsdbConfigurationService.getTableName(node, Port.class));
273 if (ports == null) return null;
274 for (String portUUID : ports.keySet()) {
275 Port port = ovsdbConfigurationService.getTypedRow(node, Port.class, ports.get(portUUID));
276 Set<UUID> interfaceUUIDs = port.getInterfacesColumn().getData();
277 logger.trace("Scanning Port {} to identify interface : {} ",port, uuid);
278 for (UUID intfUUID : interfaceUUIDs) {
279 if (intfUUID.toString().equalsIgnoreCase(uuid)) {
280 logger.trace("Found Interface {} -> {}", uuid, portUUID);
285 } catch (Exception e) {
286 logger.debug("Failed to get Port tag for for Intf {}:{}", intf, e);
292 public void notifyNode(Node node, UpdateType type, Map<String, Property> propMap) {
293 logger.debug("notifyNode: Node {} update {} from Controller's inventory Service", node, type);
295 // Add the Node Type check back once the Consistency issue is resolved between MD-SAL and AD-SAL
296 if (!type.equals(UpdateType.REMOVED) && !nodeCache.contains(node)) {
298 networkingProviderManager.getProvider(node).initializeOFFlowRules(node);
299 } else if (type.equals(UpdateType.REMOVED)){
300 nodeCache.remove(node);
305 public void notifyNodeConnector(NodeConnector nodeConnector, UpdateType type, Map<String, Property> propMap) {
306 //We are not interested in the nodeConnectors at this moment
309 private void triggerUpdates() {
310 List<Node> nodes = connectionService.getNodes();
311 if (nodes == null) return;
312 for (Node node : nodes) {
314 List<String> tableNames = ovsdbConfigurationService.getTables(node);
315 if (tableNames == null) continue;
316 for (String tableName : tableNames) {
317 Map<String, Row> rows = ovsdbConfigurationService.getRows(node, tableName);
318 if (rows == null) continue;
319 for (String uuid : rows.keySet()) {
320 Row row = rows.get(uuid);
321 this.rowAdded(node, tableName, uuid, row);
324 } catch (Exception e) {
325 logger.error("Exception during OVSDB Southbound update trigger", e);
333 * @param abstractEvent the {@link org.opendaylight.ovsdb.openstack.netvirt.AbstractEvent} event to be handled.
334 * @see EventDispatcher
337 public void processEvent(AbstractEvent abstractEvent) {
338 if (!(abstractEvent instanceof SouthboundEvent)) {
339 logger.error("Unable to process abstract event " + abstractEvent);
342 SouthboundEvent ev = (SouthboundEvent) abstractEvent;
343 switch (ev.getType()) {
346 processNodeUpdate(ev.getNode(), ev.getAction());
347 } catch (Exception e) {
348 logger.error("Exception caught in ProcessNodeUpdate for node " + ev.getNode(), e);
353 processRowUpdate(ev.getNode(), ev.getTableName(), ev.getUuid(), ev.getRow(),
354 ev.getContext(),ev.getAction());
355 } catch (Exception e) {
356 logger.error("Exception caught in ProcessRowUpdate for node " + ev.getNode(), e);
360 logger.warn("Unable to process type " + ev.getType() +
361 " action " + ev.getAction() + " for node " + ev.getNode());