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 /* XXX (NOTE): Extract parent node data from originalData(), rather then extracting it from
326 updatedData() because, extracting it from originalData() will give all the
327 (existing data of parent node + old data of the OvsdbBridgeAugmentation).
328 If we extract parent node data from updatedData, it will give us
329 ( Node data + new OvsdbBridgeAugmentation data). To determine the update in
330 OvsdbBridgeAugmentation, we need to compare it's old and new values, so parent
331 node data from originalData will contain old value and OvsdbBridgeAugmentation
332 from updateData() will provide new data.
335 Node bridgeParentNode = getNode(changes.getOriginalData(), updatedBridge);
336 if(bridgeParentNode == null){
337 // Logging this warning, to catch any change in southbound plugin behavior
338 LOG.warn("Parent Node for bridge is not found. Bridge update must provide the Node "
339 + "details in original Data Changes. This condition should not occure" );
342 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
343 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
344 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
349 private void processBridgeDeletion(
350 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
351 LOG.info("processBridgeDeletion - Received changes : {}", changes);
353 for(InstanceIdentifier<?> removedBridge : changes.getRemovedPaths()) {
354 if(removedBridge.getTargetType().equals(OvsdbBridgeAugmentation.class)){
355 Node bridgeParentNode = getNode(changes.getOriginalData(), removedBridge);
356 if(bridgeParentNode == null){
357 //Throwing this warning to catch the behavior change of southbound plugin.
358 LOG.warn("Bridge's {} parent node details are not present in original data"
359 + ", it should not happen", removedBridge);
362 //Fetch data of removed bridge from original data
363 @SuppressWarnings("unchecked")
364 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
365 (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
367 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
368 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
369 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
374 //TODO: Will remove it if not needed
375 private Node getNodeFromCreatedData(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes,
376 Map.Entry<InstanceIdentifier<?>, DataObject> change) {
377 InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
378 return (Node)changes.getCreatedData().get(nodeInstanceIdentifier);
381 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,
382 Map.Entry<InstanceIdentifier<?>, DataObject> change) {
383 InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
384 return (Node)changes.get(nodeInstanceIdentifier);
387 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
388 InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
389 return (Node)changes.get(nodeInstanceIdentifier);
392 private <T extends DataObject> T getDataChanges(
393 Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
395 for(Map.Entry<InstanceIdentifier<?>,DataObject> change : changes.entrySet()){
396 if(change.getKey().getTargetType().equals(path.getTargetType())){
397 @SuppressWarnings("unchecked")
398 T dataObject = (T) change.getValue();
405 private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
406 OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
408 Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
409 for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
410 mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);