Fixed instanceof comparison for iid
[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.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;
36
37 /**
38  * MDSAL dataChangeListener for the OVSDB Southbound
39  *
40  * @author Sam Hague (shague@redhat.com)
41  */
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;
47
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))
54                 .child(Node.class);
55         registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
56                 DataChangeScope.SUBTREE);
57         LOG.info("netvirt OvsdbDataChangeListener: registration= {}", registration);
58     }
59
60     @Override
61     public void close () throws Exception {
62         registration.close();
63     }
64
65     /* TODO
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.
69      */
70     @Override
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);
86
87
88     }
89
90     private void processOvsdbConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
91
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);
98             }
99         }
100     }
101
102     private void processOvsdbDisconnect(
103             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> 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
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." );
150                     continue;
151                 }
152                 LOG.debug("Process ovsdb conenction  {} related update on Node : {}",
153                         updatedOvsdbNode.getValue(),parentNode);
154
155                 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
156                         OvsdbInventoryListener.OvsdbType.OPENVSWITCH, Action.UPDATE);
157             }
158         }
159     }
160
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");
170                     continue;
171                 }
172                 Node node = getNode(changes.getCreatedData(), change);
173                 if (node == null) {
174                     LOG.warn("node not found");
175                     continue;
176                 }
177                 // This value is not being set right now - OvsdbBridgeUpdateCommand
178                 //if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
179                     nodeCacheManager =
180                             (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
181                     nodeCacheManager.nodeAdded(node);
182                 //}
183             }
184         }
185
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");
194                     continue;
195                 }
196                 Node node = getNode(changes.getUpdatedData(), change);
197                 if (node == null) {
198                     LOG.warn("node not found");
199                     continue;
200                 }
201                 // This value is not being set right now - OvsdbBridgeUpdateCommand
202                 // if (ovsdbBridgeAugmentation.getBridgeOpenflowNodeRef() != null) {
203                     nodeCacheManager =
204                             (NodeCacheManager) ServiceHelper.getGlobalInstance(NodeCacheManager.class, this);
205                     nodeCacheManager.nodeAdded(node);
206                 //}
207             }
208         }
209     }
210
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);
221                 }
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" );
227                     continue;
228                 }
229
230                 LOG.debug("Process new port {} creation on Node : {}", newPort.getValue(),tpParentNode);
231                 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
232             }
233         }
234     }
235
236     private void processPortDeletion(
237             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
238
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);
246                     continue;
247                 }
248                 //Fetch data of removed port from original data
249                 @SuppressWarnings("unchecked")
250                 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
251                         (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
252
253                 LOG.debug("Process port {} deletion on Node : {}", removedPort,tpParentNode);
254                 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
255                         OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
256
257             }
258         }
259     }
260
261     private void processPortUpdate(
262             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
263
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." );
281                     continue;
282                 }
283
284                 LOG.debug("Process port's {} update on Node : {}", updatedPort.getValue(),tpParentNode);
285                 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
286                         OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
287             }
288         }
289     }
290
291     private void processBridgeCreation(
292             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
293
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" );
303                     continue;
304                 }
305                 LOG.debug("Process new bridge {} creation on Node : {}", newBridge.getValue(),bridgeParentNode);
306                 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
307                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
308             }
309         }
310     }
311
312     private void processBridgeUpdate(
313             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
314
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.
326                  */
327
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" );
333                     continue;
334                 }
335                 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
336                 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
337                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
338             }
339         }
340     }
341
342     private void processBridgeDeletion(
343             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
344
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);
352                     continue;
353                 }
354                 //Fetch data of removed bridge from original data
355                 @SuppressWarnings("unchecked")
356                 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
357                         (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
358
359                 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
360                 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
361                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
362             }
363         }
364     }
365
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);
370     }
371
372     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
373         InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
374         return (Node)changes.get(nodeInstanceIdentifier);
375     }
376
377     private <T extends DataObject> T getDataChanges(
378             Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
379
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();
384                 return dataObject;
385             }
386         }
387         return null;
388     }
389
390     private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
391             OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
392
393         Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
394         for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
395             mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
396         }
397     }
398 }