Remove SouthboundMapper and SouthboundConstants dependencies
[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.MdsalHelper;
19 import org.opendaylight.ovsdb.openstack.netvirt.api.Action;
20 import org.opendaylight.ovsdb.openstack.netvirt.api.OvsdbInventoryListener;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbTerminationPointAugmentation;
24 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
25 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
26 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
27 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
28 import org.opendaylight.yangtools.concepts.ListenerRegistration;
29 import org.opendaylight.yangtools.yang.binding.DataObject;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * MDSAL dataChangeListener for the OVSDB Southbound
36  *
37  * @author Sam Hague (shague@redhat.com)
38  */
39 public class OvsdbDataChangeListener implements DataChangeListener, AutoCloseable {
40     private static final Logger LOG = LoggerFactory.getLogger(OvsdbDataChangeListener.class);
41     private DataBroker dataBroker = null;
42     private ListenerRegistration<DataChangeListener> registration;
43
44     public OvsdbDataChangeListener (DataBroker dataBroker) {
45         LOG.info(">>>>> Registering OvsdbNodeDataChangeListener: dataBroker= {}", dataBroker);
46         this.dataBroker = dataBroker;
47         InstanceIdentifier<Node> path = InstanceIdentifier
48                 .create(NetworkTopology.class)
49                 .child(Topology.class, new TopologyKey(MdsalHelper.OVSDB_TOPOLOGY_ID))
50                 .child(Node.class);
51         registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
52                 DataChangeScope.SUBTREE);
53         LOG.info("netvirt OvsdbDataChangeListener: registration= {}", registration);
54     }
55
56     @Override
57     public void close () throws Exception {
58         registration.close();
59     }
60
61     @Override
62     public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
63         LOG.debug(">>>>> onDataChanged: {}", changes);
64         //TODO SB_MIGRATION: off load this process to execution service, blocking md-sal notification thread
65         // has performance impact on overall controller performance. With new notification broker
66         //it might create weird issues.
67         processOvsdbConnections(changes);
68         processOvsdbConnectionAttributeUpdates(changes);
69         processBridgeCreation(changes);
70         processBridgeUpdate(changes);
71         processPortCreation(changes);
72         processPortUpdate(changes);
73         processPortDeletion(changes);
74         processBridgeDeletion(changes);
75         processOvsdbDisconnect(changes);
76     }
77
78     private void processOvsdbConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
79         for (Map.Entry<InstanceIdentifier<?>, DataObject> created : changes.getCreatedData().entrySet()) {
80             if (created.getValue() instanceof OvsdbNodeAugmentation) {
81                 Node ovsdbNode = getNode(changes.getCreatedData(), created);
82                 LOG.info("Processing ovsdb connections : {}, ovsdbNode: {}", created, ovsdbNode);
83                 ovsdbUpdate(ovsdbNode, created.getValue(), OvsdbInventoryListener.OvsdbType.NODE, Action.ADD);
84             }
85         }
86     }
87
88     private void processOvsdbDisconnect(
89             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
90
91         for(InstanceIdentifier<?> removedOvsdbNode : changes.getRemovedPaths()) {
92             if(removedOvsdbNode.getTargetType().equals(OvsdbNodeAugmentation.class)){
93                 //Get top node to get details of all the bridge/termination point augmentation
94                 // in case we want to do any cleanup task while processing node disconnection
95                 Node parentNode = getNode(changes.getOriginalData(), removedOvsdbNode);
96                 if(parentNode == null){
97                     //Throwing this warning in case behavior of southbound plugin changes.
98                     LOG.warn("OvsdbNode's {} parent node details are not present in original data,"
99                             + " it should not happen", parentNode);
100                     continue;
101                 }
102                 //Fetch data of removed connection info from original data
103                 @SuppressWarnings("unchecked")
104                 OvsdbNodeAugmentation removedOvsdbNodeAugmentationData = getDataChanges(changes.getOriginalData(),
105                         (InstanceIdentifier<OvsdbNodeAugmentation>) removedOvsdbNode);
106
107                 LOG.debug("Process ovsdb node delete : {} ", removedOvsdbNode);
108                 ////Assuming Openvswitch type represent the ovsdb node connection and not OvsdbType.NODE
109
110                 ovsdbUpdate(parentNode, removedOvsdbNodeAugmentationData,
111                         OvsdbInventoryListener.OvsdbType.NODE, Action.DELETE);
112             }
113         }
114     }
115
116     private void processOvsdbConnectionAttributeUpdates(
117             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
118
119         for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedOvsdbNode : changes.getUpdatedData().entrySet()){
120             if(updatedOvsdbNode.getKey().getTargetType().equals(OvsdbNodeAugmentation.class)){
121                 LOG.info("Processing Ovsdb Node attributes update : {}",updatedOvsdbNode);
122                 /* XXX (NOTE): Till now we don't really need the old ovsdb connection attributes data before update.
123                  * I am passing the updated data of both Node and resource augmentation data (connection attributes).
124                  * If in future we need old OvsdbNodeAugmentation attributes data, we will extract it from
125                  * original data and pass it as a resourceAugmentationData.
126                  */
127                 Node parentNode  = getNode(changes.getUpdatedData(),updatedOvsdbNode);
128                 if(parentNode == null){
129                     // Logging this warning, to catch any change in southbound plugin's behavior.
130                     LOG.warn("Parent Node for OvsdbNodeAugmentation is not found. On OvsdbNodeAugmentation update "
131                             + "data store must provide the parent node update. This condition should not occur "
132                             + "with the existing models defined in southbound plugin." );
133                     continue;
134                 }
135                 LOG.debug("Process ovsdb connection  {} related update on Node : {}",
136                         updatedOvsdbNode.getValue(), parentNode);
137
138                 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
139                         OvsdbInventoryListener.OvsdbType.NODE, Action.UPDATE);
140             }
141         }
142     }
143
144     private void processPortCreation(
145             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
146         for(Map.Entry<InstanceIdentifier<?>, DataObject> newPort : changes.getCreatedData().entrySet()){
147             if(newPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
148                 LOG.info("Processing creation of new port : {}",newPort);
149                 //If user created termination point only, Node will get updated
150                 Node tpParentNode  = getNode(changes.getUpdatedData(), newPort);
151                 if(tpParentNode == null){
152                     //If user created port with the bridge itself, Node will be in created data
153                     tpParentNode = getNode(changes.getCreatedData(),newPort);
154                 }
155                 if(tpParentNode == null){
156                     // Logging this warning, to make sure we didn't change anything
157                     // in southbound plugin that changes this behavior.
158                     LOG.warn("Parent Node for port is not found. Port creation must create or "
159                             + "update the Node. This condition should not occure" );
160                     continue;
161                 }
162
163                 LOG.debug("Process new port {} creation on Node : {}", newPort.getValue(),tpParentNode);
164                 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
165             }
166         }
167     }
168
169     private void processPortDeletion(
170             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
171
172         for(InstanceIdentifier<?> removedPort : changes.getRemovedPaths()) {
173             if(removedPort.getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
174                 Node tpParentNode = getNode(changes.getOriginalData(), removedPort);
175                 if(tpParentNode == null){
176                     //Throwing this warning in case behavior of southbound plugin changes.
177                     LOG.warn("Port's {} parent node details are not present in original data, "
178                             + "it should not happen", removedPort);
179                     continue;
180                 }
181                 //Fetch data of removed port from original data
182                 @SuppressWarnings("unchecked")
183                 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
184                         (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
185
186                 LOG.debug("Process port {} deletion on Node : {}", removedPort,tpParentNode);
187                 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
188                         OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
189
190             }
191         }
192     }
193
194     private void processPortUpdate(
195             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
196
197         for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedPort : changes.getUpdatedData().entrySet()){
198             if(updatedPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
199                 LOG.info("Processing port update : {}",updatedPort);
200                 /* XXX (NOTE): Till now we don't really need the old termination point data before update.
201                  * I am passing the updated data of both Node and resource augmentation data (termination-point).
202                  * If in future we need old TerminationPointAugmentation data, we will extract it from
203                  * original data and pass it as a resourceAugmentationData.
204                  */
205                 Node tpParentNode  = getNode(changes.getUpdatedData(),updatedPort);
206                 if(tpParentNode == null){
207                     // Logging this warning, to catch any change in southbound plugin's behavior.
208                     LOG.warn("Parent Node for port is not found. On Port/Interface update data store"
209                             + " must provide the parent node update. This condition should not occure "
210                             + "with the existing models define in southbound plugin." );
211                     continue;
212                 }
213
214                 LOG.debug("Process port's {} update on Node : {}", updatedPort.getValue(),tpParentNode);
215                 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
216                         OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
217             }
218         }
219     }
220
221     private void processBridgeCreation(
222             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
223
224         for(Map.Entry<InstanceIdentifier<?>, DataObject> newBridge : changes.getCreatedData().entrySet()){
225             if(newBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
226                 LOG.info("Processing creation of new bridge : {}",newBridge);
227                 //Bridge augmentation happens directly on the Node so Node details should also exist in created data.
228                 Node bridgeParentNode  = getNode(changes.getCreatedData(),newBridge);
229                 if(bridgeParentNode == null){
230                     // Logging this warning, to catch any change in southbound plugin behavior
231                     LOG.warn("Parent Node for bridge is not found. Bridge creation must provide the Node "
232                             + "details in create Data Changes. This condition should not occure" );
233                     continue;
234                 }
235                 LOG.debug("Process new bridge {} creation on Node : {}", newBridge.getValue(),bridgeParentNode);
236                 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
237                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
238             }
239         }
240     }
241
242     private void processBridgeUpdate(
243             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
244
245         for (Map.Entry<InstanceIdentifier<?>, DataObject> updatedBridge : changes.getUpdatedData().entrySet()) {
246             if(updatedBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
247                 LOG.info("Processing update on a bridge : {}",updatedBridge);
248                 /* XXX (NOTE): Till now we don't really need the old bridge data before update.
249                  * I am passing the updated data of both Node and resource augmentation data.
250                  * If in future we need old bridgeAugmentationData, we will extract it from
251                  * original data and pass it as a resourceAugmentationData.
252                  */
253
254                 Node bridgeParentNode = getNode(changes.getUpdatedData(), updatedBridge);
255                 if(bridgeParentNode == null){
256                     // Logging this warning, to catch any change in southbound plugin behavior
257                     LOG.warn("Parent Node for bridge is not found. Bridge update must provide the Node "
258                             + "details in updated Data Changes. This condition should not occure" );
259                     continue;
260                 }
261                 LOG.debug("Process bridge {} update on Node : {}", updatedBridge.getValue(),bridgeParentNode);
262                 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
263                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
264             }
265         }
266     }
267
268     private void processBridgeDeletion(
269             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
270
271         for(InstanceIdentifier<?> removedBridge : changes.getRemovedPaths()) {
272             if(removedBridge.getTargetType().equals(OvsdbBridgeAugmentation.class)){
273                 Node bridgeParentNode = getNode(changes.getOriginalData(), removedBridge);
274                 if(bridgeParentNode == null){
275                     //Throwing this warning to catch the behavior change of southbound plugin.
276                     LOG.warn("Bridge's {} parent node details are not present in original data"
277                             + ", it should not happen", removedBridge);
278                     continue;
279                 }
280                 //Fetch data of removed bridge from original data
281                 @SuppressWarnings("unchecked")
282                 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
283                         (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
284
285                 LOG.debug("Process bridge {} deletion on Node : {}", removedBridge,bridgeParentNode);
286                 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
287                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
288             }
289         }
290     }
291
292     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,
293                          Map.Entry<InstanceIdentifier<?>, DataObject> change) {
294         InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
295         return (Node)changes.get(nodeInstanceIdentifier);
296     }
297
298     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
299         InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
300         return (Node)changes.get(nodeInstanceIdentifier);
301     }
302
303     private <T extends DataObject> T getDataChanges(
304             Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
305
306         for(Map.Entry<InstanceIdentifier<?>,DataObject> change : changes.entrySet()){
307             if(change.getKey().getTargetType().equals(path.getTargetType())){
308                 @SuppressWarnings("unchecked")
309                 T dataObject = (T) change.getValue();
310                 return dataObject;
311             }
312         }
313         return null;
314     }
315
316     private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
317             OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
318
319         Set<OvsdbInventoryListener> mdsalConsumerListeners = OvsdbInventoryServiceImpl.getMdsalConsumerListeners();
320         for (OvsdbInventoryListener mdsalConsumerListener : mdsalConsumerListeners) {
321             mdsalConsumerListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
322         }
323     }
324 }