Implemented processing of Bridge Update
[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                 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" );
330                     continue;
331                 }
332                 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
333                 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
334                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
335             }
336         }
337     }
338
339     private void processBridgeDeletion(
340             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
341         LOG.info("processBridgeDeletion - Received changes : {}", changes);
342
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);
350                     continue;
351                 }
352                 //Fetch data of removed bridge from original data
353                 @SuppressWarnings("unchecked")
354                 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
355                         (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
356
357                 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
358                 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
359                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
360             }
361         }
362     }
363
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);
369     }
370
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);
375     }
376
377     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
378         InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
379         return (Node)changes.get(nodeInstanceIdentifier);
380     }
381
382     private <T extends DataObject> T getDataChanges(
383             Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
384
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();
389                 return dataObject;
390             }
391         }
392         return null;
393     }
394
395     private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
396             OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
397
398         Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
399         for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
400             mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
401         }
402     }
403 }