2 * Copyright (c) 2015 Red Hat, 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
8 package org.opendaylight.ovsdb.openstack.netvirt.impl;
13 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
14 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
15 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
16 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.ovsdb.openstack.netvirt.api.Action;
19 import org.opendaylight.ovsdb.openstack.netvirt.api.NodeCacheManager;
20 import org.opendaylight.ovsdb.openstack.netvirt.api.OvsdbInventoryListener;
21 import org.opendaylight.ovsdb.southbound.SouthboundConstants;
22 import org.opendaylight.ovsdb.utils.servicehelper.ServiceHelper;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbTerminationPointAugmentation;
26 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
27 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
28 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
29 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
30 import org.opendaylight.yangtools.concepts.ListenerRegistration;
31 import org.opendaylight.yangtools.yang.binding.DataObject;
32 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * MDSAL dataChangeListener for the OVSDB Southbound
39 * @author Sam Hague (shague@redhat.com)
41 public class OvsdbDataChangeListener implements DataChangeListener, AutoCloseable {
42 private static final Logger LOG = LoggerFactory.getLogger(OvsdbDataChangeListener.class);
43 private DataBroker dataBroker = null;
44 private ListenerRegistration<DataChangeListener> registration;
45 private NodeCacheManager nodeCacheManager = null;
47 public OvsdbDataChangeListener (DataBroker dataBroker) {
48 LOG.info(">>>>> Registering OvsdbNodeDataChangeListener: dataBroker= {}", dataBroker);
49 this.dataBroker = dataBroker;
50 InstanceIdentifier<Node> path = InstanceIdentifier
51 .create(NetworkTopology.class)
52 .child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID))
54 registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
55 DataChangeScope.SUBTREE);
56 LOG.info("netvirt OvsdbDataChangeListener: registration= {}", registration);
60 public void close () throws Exception {
65 * Recognize when netvirt added a bridge to config and then the operational update comes in
66 * can it be ignored or just viewed as a new switch? ports and interfaces can likely be mapped
67 * to the old path where there were updates for them for update and insert row.
70 public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
71 LOG.info(">>>>> onDataChanged: {}", changes);
72 //TODO: off load this process to execution service, blocking md-sal notification thread
73 // has performance impact on overall controller performance. With new notification broker
74 //it might create weird issues.
75 processOvsdbConnections(changes);
76 processOvsdbDisconnect(changes);
77 processOvsdbConnectionAttributeUpdates(changes);
78 processOpenflowConnections(changes);
79 processBridgeCreation(changes);
80 processBridgeDeletion(changes);
81 processBridgeUpdate(changes);
82 processPortCreation(changes);
83 processPortDeletion(changes);
84 processPortUpdate(changes);
89 private void processOvsdbConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
90 LOG.trace("processOvsdbConnections - Received changes : {}",changes);
91 for (Map.Entry<InstanceIdentifier<?>, DataObject> created : changes.getCreatedData().entrySet()) {
92 if (created.getValue() instanceof OvsdbNodeAugmentation) {
93 LOG.info("Processing ovsdb connections : {}", created);
94 Node ovsdbNode = getNode(changes.getCreatedData(), created);
95 LOG.info("ovsdbNode: {}", ovsdbNode);
96 ovsdbUpdate(ovsdbNode, created.getValue(), OvsdbInventoryListener.OvsdbType.NODE, Action.ADD);
101 private void processOvsdbDisconnect(
102 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
103 LOG.info("processOvsdbDisconnect - Received changes : {}", changes);
105 for(InstanceIdentifier<?> removedOvsdbNode : changes.getRemovedPaths()) {
106 if(removedOvsdbNode.getTargetType().equals(OvsdbNodeAugmentation.class)){
107 //Get top node to get details of all the bridge/termination point augmentation
108 // in case we want to do any cleanup task while processing node disconnection
109 Node parentNode = getNode(changes.getOriginalData(), removedOvsdbNode);
110 if(parentNode == null){
111 //Throwing this warning in case behavior of southbound plugin changes.
112 LOG.warn("OvsdbNode's {} parent node details are not present in original data,"
113 + " it should not happen", parentNode);
116 //Fetch data of removed connection info from original data
117 @SuppressWarnings("unchecked")
118 OvsdbNodeAugmentation removedOvsdbNodeAugmentationData = getDataChanges(changes.getOriginalData(),
119 (InstanceIdentifier<OvsdbNodeAugmentation>)removedOvsdbNode);
121 LOG.debug("Process ovsdb node delete : {} ", removedOvsdbNode);
122 //Assuming Openvswitch type represent the ovsdb node connection and not OvsdbType.NODE
124 ovsdbUpdate(parentNode, removedOvsdbNodeAugmentationData,
125 OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.DELETE);
130 private void processOvsdbConnectionAttributeUpdates(
131 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
132 LOG.info("processOvsdbConnectionAttributeUpdates - Received changes : {}", changes);
134 for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedOvsdbNode : changes.getUpdatedData().entrySet()){
135 if(updatedOvsdbNode.getKey() instanceof OvsdbNodeAugmentation){
136 LOG.info("Processing Ovsdb Node attributes update : {}",updatedOvsdbNode);
137 // XXX (NOTE):Extract parent node data from originalData(),rather then extracting it from
138 // updatedData() because, extracting it from originalData() will give all the
139 // (existing data of parent node + old data of the OvsdbNodeAugmentation).
140 // If we extract parent node data from updatedData, it will give us
141 // ( Node data + new OvsdbNodeAugmentation data). To determine the update in
142 // OvsdbNodeAugmentation, we need to compare it's old and new values, so parent
143 // node data from originalData will contain old value and OvsdbNodeAugmentation
144 // from updateData() will provide new data.
145 Node parentNode = getNode(changes.getOriginalData(),updatedOvsdbNode);
146 if(parentNode == null){
147 // Logging this warning, to catch any change in southbound plugin's behavior.
148 LOG.warn("Parent Node for OvsdbNodeAugmentation is not found. On OvsdbNodeAugmentation update "
149 + "data store must provide the parent node update. This condition should not occure "
150 + "with the existing models define in southbound plugin." );
153 LOG.debug("Process ovsdb conenction {} related update on Node : {}",
154 updatedOvsdbNode.getValue(),parentNode);
156 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
157 OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.UPDATE);
162 private void processOpenflowConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
163 LOG.trace("processOpenflowConnections - processOpenflowConnections created: {}", changes);
164 for (Map.Entry<InstanceIdentifier<?>, DataObject> change : changes.getCreatedData().entrySet()) {
165 if (change.getValue() instanceof OvsdbBridgeAugmentation) {
166 LOG.info("Processing OpenFlow connections : {}",change);
167 OvsdbBridgeAugmentation ovsdbBridgeAugmentation = (OvsdbBridgeAugmentation)change.getValue();
168 String datapathId = MdsalUtils.getDatapathId(ovsdbBridgeAugmentation);
169 // Having a datapathId means the bridge has connected so it exists
170 if (datapathId == null) {
171 LOG.info("dataPathId not found");
174 Node node = getNode(changes.getCreatedData(), change);
176 LOG.warn("node not found");
179 // This value is not being set right now - OvsdbBridgeUpdateCommand
180 //if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
182 (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
183 nodeCacheManager.nodeAdded(node);
188 for (Map.Entry<InstanceIdentifier<?>, DataObject> change : changes.getUpdatedData().entrySet()) {
189 if (change.getValue() instanceof OvsdbBridgeAugmentation) {
190 LOG.info("Processing OpenFlow connections updates: {}",change);
191 OvsdbBridgeAugmentation ovsdbBridgeAugmentation = (OvsdbBridgeAugmentation)change.getValue();
192 String datapathId = MdsalUtils.getDatapathId(ovsdbBridgeAugmentation);
193 // Having a datapathId means the bridge has connected so it exists
194 if (datapathId == null) {
195 LOG.info("dataPathId not found");
198 Node node = getNode(changes.getUpdatedData(), change);
200 LOG.warn("node not found");
203 // This value is not being set right now - OvsdbBridgeUpdateCommand
204 // if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
206 (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
207 nodeCacheManager.nodeAdded(node);
213 private void processPortCreation(
214 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
215 LOG.info("processPortCreation - Received changes : {}", changes);
216 for(Map.Entry<InstanceIdentifier<?>, DataObject> newPort : changes.getCreatedData().entrySet()){
217 if(newPort.getKey() instanceof OvsdbTerminationPointAugmentation){
218 LOG.info("Processing creation of new port : {}",newPort);
219 //If user created termination point only, Node will get updated
220 Node tpParentNode = getNode(changes.getUpdatedData(), newPort);
221 if(tpParentNode == null){
222 //If user created port with the bridge itself, Node will be in created data
223 tpParentNode = getNode(changes.getCreatedData(),newPort);
225 if(tpParentNode == null){
226 // Logging this warning, to make sure we didn't change anything
227 // in southbound plugin that changes this behavior.
228 LOG.warn("Parent Node for port is not found. Port creation must create or "
229 + "update the Node. This condition should not occure" );
233 LOG.debug("Process new port {} creation on Node : {}", newPort.getValue(),tpParentNode);
234 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
239 private void processPortDeletion(
240 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
241 LOG.info("processPortDeletion - Received changes : {}", changes);
243 for(InstanceIdentifier<?> removedPort : changes.getRemovedPaths()) {
244 if(removedPort.getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
245 Node tpParentNode = getNode(changes.getOriginalData(), removedPort);
246 if(tpParentNode == null){
247 //Throwing this warning in case behavior of southbound plugin changes.
248 LOG.warn("Port's {} parent node details are not present in original data, "
249 + "it should not happen", removedPort);
252 //Fetch data of removed port from original data
253 @SuppressWarnings("unchecked")
254 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
255 (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
257 LOG.debug("Process port {} deletion on Node : {}", removedPort,tpParentNode);
258 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
259 OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
265 private void processPortUpdate(
266 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
267 LOG.info("processPortUpdate - Received changes : {}", changes);
269 for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedPort : changes.getUpdatedData().entrySet()){
270 if(updatedPort.getKey() instanceof OvsdbTerminationPointAugmentation){
271 LOG.info("Processing port update : {}",updatedPort);
272 // XXX (NOTE): Extract parent node data from originalData(), rather then extracting it from
273 // updatedData() because, extracting it from originalData() will give all the
274 // (existing data of parent node + old data of the OvsdbTerminationPointAugmentation).
275 // If we extract parent node data from updatedData, it will give us
276 // ( Node data + new OvsdbTerminationPointAugmentation data). To determine the update in
277 // OvsdbTerminationPointAugmentation, we need to compare it's old and new values, so parent
278 // node data from originalData will contain old value and OvsdbTerminationPointAugmentation
279 // from updateData() will provide new data.
280 Node tpParentNode = getNode(changes.getOriginalData(),updatedPort);
281 if(tpParentNode == null){
282 // Logging this warning, to catch any change in southbound plugin's behavior.
283 LOG.warn("Parent Node for port is not found. On Port/Interface update data store"
284 + " must provide the parent node update. This condition should not occure "
285 + "with the existing models define in southbound plugin." );
289 LOG.debug("Process port's {} update on Node : {}", updatedPort.getValue(),tpParentNode);
290 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
291 OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
296 private void processBridgeCreation(
297 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
298 LOG.info("processBridgeCreation - Received changes : {}", changes);
300 for(Map.Entry<InstanceIdentifier<?>, DataObject> newBridge : changes.getCreatedData().entrySet()){
301 if(newBridge.getKey() instanceof OvsdbBridgeAugmentation){
302 LOG.info("Processing creation of new bridge : {}",newBridge);
303 //Bridge augmentation happens directly on the Node so Node details should also exist in created data.
304 Node bridgeParentNode = getNode(changes.getCreatedData(),newBridge);
305 if(bridgeParentNode == null){
306 // Logging this warning, to catch any change in southbound plugin behavior
307 LOG.warn("Parent Node for bridge is not found. Bridge creation must provide the Node "
308 + "details in create Data Changes. This condition should not occure" );
311 LOG.debug("Process new bridge {} creation on Node : {}", newBridge.getValue(),bridgeParentNode);
312 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
313 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
318 private void processBridgeUpdate(
319 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
320 LOG.info("processBridgeUpdate - Received changes : {}", changes);
322 for (Map.Entry<InstanceIdentifier<?>, DataObject> updatedBridge : changes.getUpdatedData().entrySet()) {
323 if(updatedBridge.getKey() instanceof OvsdbBridgeAugmentation){
324 LOG.info("Processing update on a bridge : {}",updatedBridge);
325 Node bridgeParentNode = getNode(changes.getUpdatedData(), updatedBridge);
326 if(bridgeParentNode == null){
327 // Logging this warning, to catch any change in southbound plugin behavior
328 LOG.warn("Parent Node for bridge is not found. Bridge creation must provide the Node "
329 + "details in create Data Changes. This condition should not occure" );
332 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
333 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
334 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
339 private void processBridgeDeletion(
340 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
341 LOG.info("processBridgeDeletion - Received changes : {}", changes);
343 for(InstanceIdentifier<?> removedBridge : changes.getRemovedPaths()) {
344 if(removedBridge.getTargetType().equals(OvsdbBridgeAugmentation.class)){
345 Node bridgeParentNode = getNode(changes.getOriginalData(), removedBridge);
346 if(bridgeParentNode == null){
347 //Throwing this warning to catch the behavior change of southbound plugin.
348 LOG.warn("Bridge's {} parent node details are not present in original data"
349 + ", it should not happen", removedBridge);
352 //Fetch data of removed bridge from original data
353 @SuppressWarnings("unchecked")
354 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
355 (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
357 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
358 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
359 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
364 //TODO: Will remove it if not needed
365 private Node getNodeFromCreatedData(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes,
366 Map.Entry<InstanceIdentifier<?>, DataObject> change) {
367 InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
368 return (Node)changes.getCreatedData().get(nodeInstanceIdentifier);
371 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,
372 Map.Entry<InstanceIdentifier<?>, DataObject> change) {
373 InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
374 return (Node)changes.get(nodeInstanceIdentifier);
377 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
378 InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
379 return (Node)changes.get(nodeInstanceIdentifier);
382 private <T extends DataObject> T getDataChanges(
383 Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
385 for(Map.Entry<InstanceIdentifier<?>,DataObject> change : changes.entrySet()){
386 if(change.getKey().getTargetType().equals(path.getTargetType())){
387 @SuppressWarnings("unchecked")
388 T dataObject = (T) change.getValue();
395 private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
396 OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
398 Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
399 for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
400 mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);