Fixed OvsdbDataChangeListner bridge update processing
[ovsdb.git] / openstack / net-virt / src / main / java / org / opendaylight / ovsdb / openstack / netvirt / impl / OvsdbDataChangeListener.java
1 /*
2  * Copyright (c) 2015 Red Hat, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.ovsdb.openstack.netvirt.impl;
9
10 import java.util.Map;
11 import java.util.Set;
12
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;
35
36 /**
37  * MDSAL dataChangeListener for the OVSDB Southbound
38  *
39  * @author Sam Hague (shague@redhat.com)
40  */
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;
46
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))
53                 .child(Node.class);
54         registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
55                 DataChangeScope.SUBTREE);
56         LOG.info("netvirt OvsdbDataChangeListener: registration= {}", registration);
57     }
58
59     @Override
60     public void close () throws Exception {
61         registration.close();
62     }
63
64     /* TODO
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.
68      */
69     @Override
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);
85
86
87     }
88
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);
97             }
98         }
99     }
100
101     private void processOvsdbDisconnect(
102             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
103         LOG.info("processOvsdbDisconnect - Received changes : {}", changes);
104
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);
114                     continue;
115                 }
116                 //Fetch data of removed connection info from original data
117                 @SuppressWarnings("unchecked")
118                 OvsdbNodeAugmentation removedOvsdbNodeAugmentationData = getDataChanges(changes.getOriginalData(),
119                         (InstanceIdentifier<OvsdbNodeAugmentation>)removedOvsdbNode);
120
121                 LOG.debug("Process ovsdb node delete : {} ", removedOvsdbNode);
122                 //Assuming Openvswitch type represent the ovsdb node connection and not OvsdbType.NODE
123
124                 ovsdbUpdate(parentNode, removedOvsdbNodeAugmentationData,
125                         OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.DELETE);
126             }
127         }
128     }
129
130     private void processOvsdbConnectionAttributeUpdates(
131             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
132         LOG.info("processOvsdbConnectionAttributeUpdates - Received changes : {}", changes);
133
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." );
151                     continue;
152                 }
153                 LOG.debug("Process ovsdb conenction  {} related update on Node : {}",
154                         updatedOvsdbNode.getValue(),parentNode);
155
156                 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
157                         OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.UPDATE);
158             }
159         }
160     }
161
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");
172                     continue;
173                 }
174                 Node node = getNode(changes.getCreatedData(), change);
175                 if (node == null) {
176                     LOG.warn("node not found");
177                     continue;
178                 }
179                 // This value is not being set right now - OvsdbBridgeUpdateCommand
180                 //if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
181                     nodeCacheManager =
182                             (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
183                     nodeCacheManager.nodeAdded(node);
184                 //}
185             }
186         }
187
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");
196                     continue;
197                 }
198                 Node node = getNode(changes.getUpdatedData(), change);
199                 if (node == null) {
200                     LOG.warn("node not found");
201                     continue;
202                 }
203                 // This value is not being set right now - OvsdbBridgeUpdateCommand
204                 // if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
205                     nodeCacheManager =
206                             (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
207                     nodeCacheManager.nodeAdded(node);
208                 //}
209             }
210         }
211     }
212
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);
224                 }
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" );
230                     continue;
231                 }
232
233                 LOG.debug("Process new port {} creation on Node : {}", newPort.getValue(),tpParentNode);
234                 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
235             }
236         }
237     }
238
239     private void processPortDeletion(
240             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
241         LOG.info("processPortDeletion - Received changes : {}", changes);
242
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);
250                     continue;
251                 }
252                 //Fetch data of removed port from original data
253                 @SuppressWarnings("unchecked")
254                 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
255                         (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
256
257                 LOG.debug("Process port {} deletion on Node : {}", removedPort,tpParentNode);
258                 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
259                         OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
260
261             }
262         }
263     }
264
265     private void processPortUpdate(
266             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
267         LOG.info("processPortUpdate - Received changes : {}", changes);
268
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." );
286                     continue;
287                 }
288
289                 LOG.debug("Process port's {} update on Node : {}", updatedPort.getValue(),tpParentNode);
290                 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
291                         OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
292             }
293         }
294     }
295
296     private void processBridgeCreation(
297             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
298         LOG.info("processBridgeCreation - Received changes : {}", changes);
299
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" );
309                     continue;
310                 }
311                 LOG.debug("Process new bridge {} creation on Node : {}", newBridge.getValue(),bridgeParentNode);
312                 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
313                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
314             }
315         }
316     }
317
318     private void processBridgeUpdate(
319             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
320         LOG.info("processBridgeUpdate - Received changes : {}", changes);
321
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.
333                  */
334
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" );
340                     continue;
341                 }
342                 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
343                 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
344                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
345             }
346         }
347     }
348
349     private void processBridgeDeletion(
350             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
351         LOG.info("processBridgeDeletion - Received changes : {}", changes);
352
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);
360                     continue;
361                 }
362                 //Fetch data of removed bridge from original data
363                 @SuppressWarnings("unchecked")
364                 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
365                         (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
366
367                 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
368                 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
369                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
370             }
371         }
372     }
373
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);
379     }
380
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);
385     }
386
387     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
388         InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
389         return (Node)changes.get(nodeInstanceIdentifier);
390     }
391
392     private <T extends DataObject> T getDataChanges(
393             Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
394
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();
399                 return dataObject;
400             }
401         }
402         return null;
403     }
404
405     private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
406             OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
407
408         Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
409         for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
410             mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
411         }
412     }
413 }