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.MdsalUtils;
19 import org.opendaylight.ovsdb.openstack.netvirt.api.Action;
20 import org.opendaylight.ovsdb.openstack.netvirt.api.NodeCacheManager;
21 import org.opendaylight.ovsdb.openstack.netvirt.api.OvsdbInventoryListener;
22 import org.opendaylight.ovsdb.southbound.SouthboundConstants;
23 import org.opendaylight.ovsdb.utils.servicehelper.ServiceHelper;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbTerminationPointAugmentation;
27 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
28 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
29 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
30 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
31 import org.opendaylight.yangtools.concepts.ListenerRegistration;
32 import org.opendaylight.yangtools.yang.binding.DataObject;
33 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * MDSAL dataChangeListener for the OVSDB Southbound
40 * @author Sam Hague (shague@redhat.com)
42 public class OvsdbDataChangeListener implements DataChangeListener, AutoCloseable {
43 private static final Logger LOG = LoggerFactory.getLogger(OvsdbDataChangeListener.class);
44 private DataBroker dataBroker = null;
45 private ListenerRegistration<DataChangeListener> registration;
46 private NodeCacheManager nodeCacheManager = null;
48 public OvsdbDataChangeListener (DataBroker dataBroker) {
49 LOG.info(">>>>> Registering OvsdbNodeDataChangeListener: dataBroker= {}", dataBroker);
50 this.dataBroker = dataBroker;
51 InstanceIdentifier<Node> path = InstanceIdentifier
52 .create(NetworkTopology.class)
53 .child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID))
55 registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
56 DataChangeScope.SUBTREE);
57 LOG.info("netvirt OvsdbDataChangeListener: registration= {}", registration);
61 public void close () throws Exception {
66 * Recognize when netvirt added a bridge to config and then the operational update comes in
67 * can it be ignored or just viewed as a new switch? ports and interfaces can likely be mapped
68 * to the old path where there were updates for them for update and insert row.
71 public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
72 LOG.debug(">>>>> onDataChanged: {}", changes);
73 //TODO: off load this process to execution service, blocking md-sal notification thread
74 // has performance impact on overall controller performance. With new notification broker
75 //it might create weird issues.
76 processOvsdbConnections(changes);
77 processOvsdbDisconnect(changes);
78 processOvsdbConnectionAttributeUpdates(changes);
79 processOpenflowConnections(changes);
80 processBridgeCreation(changes);
81 processBridgeDeletion(changes);
82 processBridgeUpdate(changes);
83 processPortCreation(changes);
84 processPortDeletion(changes);
85 processPortUpdate(changes);
90 private void processOvsdbConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
92 for (Map.Entry<InstanceIdentifier<?>, DataObject> created : changes.getCreatedData().entrySet()) {
93 if (created.getValue() instanceof OvsdbNodeAugmentation) {
94 LOG.info("Processing ovsdb connections : {}", created);
95 Node ovsdbNode = getNode(changes.getCreatedData(), created);
96 LOG.info("ovsdbNode: {}", ovsdbNode);
97 ovsdbUpdate(ovsdbNode, created.getValue(), OvsdbInventoryListener.OvsdbType.NODE, Action.ADD);
102 private void processOvsdbDisconnect(
103 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> 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) {
133 for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedOvsdbNode : changes.getUpdatedData().entrySet()){
134 if(updatedOvsdbNode.getKey() instanceof OvsdbNodeAugmentation){
135 LOG.info("Processing Ovsdb Node attributes update : {}",updatedOvsdbNode);
136 // XXX (NOTE):Extract parent node data from originalData(),rather then extracting it from
137 // updatedData() because, extracting it from originalData() will give all the
138 // (existing data of parent node + old data of the OvsdbNodeAugmentation).
139 // If we extract parent node data from updatedData, it will give us
140 // ( Node data + new OvsdbNodeAugmentation data). To determine the update in
141 // OvsdbNodeAugmentation, we need to compare it's old and new values, so parent
142 // node data from originalData will contain old value and OvsdbNodeAugmentation
143 // from updateData() will provide new data.
144 Node parentNode = getNode(changes.getOriginalData(),updatedOvsdbNode);
145 if(parentNode == null){
146 // Logging this warning, to catch any change in southbound plugin's behavior.
147 LOG.warn("Parent Node for OvsdbNodeAugmentation is not found. On OvsdbNodeAugmentation update "
148 + "data store must provide the parent node update. This condition should not occure "
149 + "with the existing models define in southbound plugin." );
152 LOG.debug("Process ovsdb conenction {} related update on Node : {}",
153 updatedOvsdbNode.getValue(),parentNode);
155 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
156 OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.UPDATE);
161 private void processOpenflowConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
162 for (Map.Entry<InstanceIdentifier<?>, DataObject> change : changes.getCreatedData().entrySet()) {
163 if (change.getValue() instanceof OvsdbBridgeAugmentation) {
164 LOG.info("Processing OpenFlow connections : {}",change);
165 OvsdbBridgeAugmentation ovsdbBridgeAugmentation = (OvsdbBridgeAugmentation)change.getValue();
166 String datapathId = MdsalUtils.getDatapathId(ovsdbBridgeAugmentation);
167 // Having a datapathId means the bridge has connected so it exists
168 if (datapathId == null) {
169 LOG.info("dataPathId not found");
172 Node node = getNode(changes.getCreatedData(), change);
174 LOG.warn("node not found");
177 // This value is not being set right now - OvsdbBridgeUpdateCommand
178 //if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
180 (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
181 nodeCacheManager.nodeAdded(node);
186 for (Map.Entry<InstanceIdentifier<?>, DataObject> change : changes.getUpdatedData().entrySet()) {
187 if (change.getValue() instanceof OvsdbBridgeAugmentation) {
188 LOG.info("Processing OpenFlow connections updates: {}",change);
189 OvsdbBridgeAugmentation ovsdbBridgeAugmentation = (OvsdbBridgeAugmentation)change.getValue();
190 String datapathId = MdsalUtils.getDatapathId(ovsdbBridgeAugmentation);
191 // Having a datapathId means the bridge has connected so it exists
192 if (datapathId == null) {
193 LOG.info("dataPathId not found");
196 Node node = getNode(changes.getUpdatedData(), change);
198 LOG.warn("node not found");
201 // This value is not being set right now - OvsdbBridgeUpdateCommand
202 // if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
204 (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
205 nodeCacheManager.nodeAdded(node);
211 private void processPortCreation(
212 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
213 for(Map.Entry<InstanceIdentifier<?>, DataObject> newPort : changes.getCreatedData().entrySet()){
214 if(newPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
215 LOG.info("Processing creation of new port : {}",newPort);
216 //If user created termination point only, Node will get updated
217 Node tpParentNode = getNode(changes.getUpdatedData(), newPort);
218 if(tpParentNode == null){
219 //If user created port with the bridge itself, Node will be in created data
220 tpParentNode = getNode(changes.getCreatedData(),newPort);
222 if(tpParentNode == null){
223 // Logging this warning, to make sure we didn't change anything
224 // in southbound plugin that changes this behavior.
225 LOG.warn("Parent Node for port is not found. Port creation must create or "
226 + "update the Node. This condition should not occure" );
230 LOG.debug("Process new port {} creation on Node : {}", newPort.getValue(),tpParentNode);
231 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
236 private void processPortDeletion(
237 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
239 for(InstanceIdentifier<?> removedPort : changes.getRemovedPaths()) {
240 if(removedPort.getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
241 Node tpParentNode = getNode(changes.getOriginalData(), removedPort);
242 if(tpParentNode == null){
243 //Throwing this warning in case behavior of southbound plugin changes.
244 LOG.warn("Port's {} parent node details are not present in original data, "
245 + "it should not happen", removedPort);
248 //Fetch data of removed port from original data
249 @SuppressWarnings("unchecked")
250 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
251 (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
253 LOG.debug("Process port {} deletion on Node : {}", removedPort,tpParentNode);
254 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
255 OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
261 private void processPortUpdate(
262 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
264 for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedPort : changes.getUpdatedData().entrySet()){
265 if(updatedPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
266 LOG.info("Processing port update : {}",updatedPort);
267 // XXX (NOTE): Extract parent node data from originalData(), rather then extracting it from
268 // updatedData() because, extracting it from originalData() will give all the
269 // (existing data of parent node + old data of the OvsdbTerminationPointAugmentation).
270 // If we extract parent node data from updatedData, it will give us
271 // ( Node data + new OvsdbTerminationPointAugmentation data). To determine the update in
272 // OvsdbTerminationPointAugmentation, we need to compare it's old and new values, so parent
273 // node data from originalData will contain old value and OvsdbTerminationPointAugmentation
274 // from updateData() will provide new data.
275 Node tpParentNode = getNode(changes.getOriginalData(),updatedPort);
276 if(tpParentNode == null){
277 // Logging this warning, to catch any change in southbound plugin's behavior.
278 LOG.warn("Parent Node for port is not found. On Port/Interface update data store"
279 + " must provide the parent node update. This condition should not occure "
280 + "with the existing models define in southbound plugin." );
284 LOG.debug("Process port's {} update on Node : {}", updatedPort.getValue(),tpParentNode);
285 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
286 OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
291 private void processBridgeCreation(
292 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
294 for(Map.Entry<InstanceIdentifier<?>, DataObject> newBridge : changes.getCreatedData().entrySet()){
295 if(newBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
296 LOG.info("Processing creation of new bridge : {}",newBridge);
297 //Bridge augmentation happens directly on the Node so Node details should also exist in created data.
298 Node bridgeParentNode = getNode(changes.getCreatedData(),newBridge);
299 if(bridgeParentNode == null){
300 // Logging this warning, to catch any change in southbound plugin behavior
301 LOG.warn("Parent Node for bridge is not found. Bridge creation must provide the Node "
302 + "details in create Data Changes. This condition should not occure" );
305 LOG.debug("Process new bridge {} creation on Node : {}", newBridge.getValue(),bridgeParentNode);
306 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
307 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
312 private void processBridgeUpdate(
313 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
315 for (Map.Entry<InstanceIdentifier<?>, DataObject> updatedBridge : changes.getUpdatedData().entrySet()) {
316 if(updatedBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
317 LOG.info("Processing update on a bridge : {}",updatedBridge);
318 /* XXX (NOTE): Extract parent node data from originalData(), rather then extracting it from
319 updatedData() because, extracting it from originalData() will give all the
320 (existing data of parent node + old data of the OvsdbBridgeAugmentation).
321 If we extract parent node data from updatedData, it will give us
322 ( Node data + new OvsdbBridgeAugmentation data). To determine the update in
323 OvsdbBridgeAugmentation, we need to compare it's old and new values, so parent
324 node data from originalData will contain old value and OvsdbBridgeAugmentation
325 from updateData() will provide new data.
328 Node bridgeParentNode = getNode(changes.getOriginalData(), updatedBridge);
329 if(bridgeParentNode == null){
330 // Logging this warning, to catch any change in southbound plugin behavior
331 LOG.warn("Parent Node for bridge is not found. Bridge update must provide the Node "
332 + "details in original Data Changes. This condition should not occure" );
335 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
336 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
337 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
342 private void processBridgeDeletion(
343 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
345 for(InstanceIdentifier<?> removedBridge : changes.getRemovedPaths()) {
346 if(removedBridge.getTargetType().equals(OvsdbBridgeAugmentation.class)){
347 Node bridgeParentNode = getNode(changes.getOriginalData(), removedBridge);
348 if(bridgeParentNode == null){
349 //Throwing this warning to catch the behavior change of southbound plugin.
350 LOG.warn("Bridge's {} parent node details are not present in original data"
351 + ", it should not happen", removedBridge);
354 //Fetch data of removed bridge from original data
355 @SuppressWarnings("unchecked")
356 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
357 (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
359 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
360 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
361 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
366 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,
367 Map.Entry<InstanceIdentifier<?>, DataObject> change) {
368 InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
369 return (Node)changes.get(nodeInstanceIdentifier);
372 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
373 InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
374 return (Node)changes.get(nodeInstanceIdentifier);
377 private <T extends DataObject> T getDataChanges(
378 Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
380 for(Map.Entry<InstanceIdentifier<?>,DataObject> change : changes.entrySet()){
381 if(change.getKey().getTargetType().equals(path.getTargetType())){
382 @SuppressWarnings("unchecked")
383 T dataObject = (T) change.getValue();
390 private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
391 OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
393 Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
394 for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
395 mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);