Merge "Avoid calling the String constructor explicitly"
[netvirt.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 import java.util.concurrent.ExecutorService;
13 import java.util.concurrent.Executors;
14
15 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
16 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
17 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
18 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
19 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
20 import org.opendaylight.ovsdb.openstack.netvirt.MdsalHelper;
21 import org.opendaylight.ovsdb.openstack.netvirt.api.Action;
22 import org.opendaylight.ovsdb.openstack.netvirt.api.OvsdbInventoryListener;
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 final ExecutorService executorService = Executors.newFixedThreadPool(1);
46
47     public OvsdbDataChangeListener (DataBroker dataBroker) {
48         this.dataBroker = dataBroker;
49     }
50
51     public void start() {
52         InstanceIdentifier<Node> path = InstanceIdentifier
53                 .create(NetworkTopology.class)
54                 .child(Topology.class, new TopologyKey(MdsalHelper.OVSDB_TOPOLOGY_ID))
55                 .child(Node.class);
56         registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
57                 DataChangeScope.SUBTREE);
58         LOG.info("netvirt OvsdbDataChangeListener: dataBroker= {}, registration= {}",
59                 dataBroker, registration);
60         triggerUpdates();
61     }
62
63     @Override
64     public void close () throws Exception {
65         registration.close();
66         executorService.shutdown();
67     }
68
69     @Override
70     public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
71
72         executorService.submit(new Runnable(){
73
74             @Override
75             public void run() {
76                 LOG.trace(">>>>> onDataChanged: {}", changes);
77                 processOvsdbConnections(changes);
78                 processOvsdbConnectionAttributeUpdates(changes);
79                 processBridgeCreation(changes);
80                 processBridgeUpdate(changes);
81                 processPortCreation(changes);
82                 processPortUpdate(changes);
83                 processPortDeletion(changes);
84                 processBridgeDeletion(changes);
85                 processOvsdbDisconnect(changes);
86             }
87         });
88     }
89
90     private void processOvsdbConnections(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
91         for (Map.Entry<InstanceIdentifier<?>, DataObject> created : changes.getCreatedData().entrySet()) {
92             if (created.getValue() instanceof OvsdbNodeAugmentation) {
93                 Node ovsdbNode = getNode(changes.getCreatedData(), created);
94                 LOG.trace("processOvsdbConnections: <{}>, ovsdbNode: <{}>", created, ovsdbNode);
95                 ovsdbUpdate(ovsdbNode, created.getValue(), OvsdbInventoryListener.OvsdbType.NODE, Action.ADD);
96             }
97         }
98     }
99
100     private void processOvsdbDisconnect(
101             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
102
103         for(InstanceIdentifier<?> removedOvsdbNode : changes.getRemovedPaths()) {
104             if(removedOvsdbNode.getTargetType().equals(OvsdbNodeAugmentation.class)){
105                 //Get top node to get details of all the bridge/termination point augmentation
106                 // in case we want to do any cleanup task while processing node disconnection
107                 Node parentNode = getNode(changes.getOriginalData(), removedOvsdbNode);
108                 if(parentNode == null){
109                     //Throwing this warning in case behavior of southbound plugin changes.
110                     LOG.warn("OvsdbNode's {} parent node details are not present in original data,"
111                             + " it should not happen", parentNode);
112                     continue;
113                 }
114                 //Fetch data of removed connection info from original data
115                 @SuppressWarnings("unchecked")
116                 OvsdbNodeAugmentation removedOvsdbNodeAugmentationData = getDataChanges(changes.getOriginalData(),
117                         (InstanceIdentifier<OvsdbNodeAugmentation>) removedOvsdbNode);
118
119                 LOG.trace("processOvsdbDisconnect: {} ", removedOvsdbNode);
120                 ////Assuming Openvswitch type represent the ovsdb node connection and not OvsdbType.NODE
121
122                 ovsdbUpdate(parentNode, removedOvsdbNodeAugmentationData,
123                         OvsdbInventoryListener.OvsdbType.NODE, Action.DELETE);
124             }
125         }
126     }
127
128     private void processOvsdbConnectionAttributeUpdates(
129             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
130
131         for(Map.Entry<InstanceIdentifier<?>, DataObject> updatedOvsdbNode : changes.getUpdatedData().entrySet()){
132             if(updatedOvsdbNode.getKey().getTargetType().equals(OvsdbNodeAugmentation.class)){
133                 LOG.trace("processOvsdbConnectionAttributeUpdates: {}", updatedOvsdbNode);
134                 /* XXX (NOTE): Till now we don't really need the old ovsdb connection attributes data before update.
135                  * I am passing the updated data of both Node and resource augmentation data (connection attributes).
136                  * If in future we need old OvsdbNodeAugmentation attributes data, we will extract it from
137                  * original data and pass it as a resourceAugmentationData.
138                  */
139                 Node parentNode  = getNode(changes.getUpdatedData(), updatedOvsdbNode);
140                 if (parentNode == null) {
141                     // Logging this warning, to catch any change in southbound plugin's behavior.
142                     LOG.warn("Parent Node for OvsdbNodeAugmentation is not found. On OvsdbNodeAugmentation update "
143                             + "data store must provide the parent node update. This condition should not occur "
144                             + "with the existing models defined in southbound plugin." );
145                     continue;
146                 }
147                 LOG.trace("processOvsdbConnectionAttributeUpdates <{}> related update on Node: <{}>",
148                         updatedOvsdbNode.getValue(), parentNode);
149
150                 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
151                         OvsdbInventoryListener.OvsdbType.NODE, Action.UPDATE);
152             }
153         }
154     }
155
156     private void processPortCreation(
157             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
158         for(Map.Entry<InstanceIdentifier<?>, DataObject> newPort : changes.getCreatedData().entrySet()){
159             if(newPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
160                 //LOG.trace("processPortCreation: {}", newPort);
161                 //If user created termination point only, Node will get updated
162                 Node tpParentNode  = getNode(changes.getUpdatedData(), newPort);
163                 if(tpParentNode == null){
164                     //If user created port with the bridge itself, Node will be in created data
165                     tpParentNode = getNode(changes.getCreatedData(),newPort);
166                 }
167                 if(tpParentNode == null){
168                     // Logging this warning, to make sure we didn't change anything
169                     // in southbound plugin that changes this behavior.
170                     LOG.warn("Parent Node for port is not found. Port creation must create or "
171                             + "update the Node. This condition should not occur." );
172                     continue;
173                 }
174
175                 LOG.trace("processPortCreation <{}> creation on Node <{}>", newPort.getValue(), tpParentNode);
176                 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
177             }
178         }
179     }
180
181     private void processPortDeletion(
182             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
183
184         for(InstanceIdentifier<?> removedPort : changes.getRemovedPaths()) {
185             if(removedPort.getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
186                 Node tpParentNode = getNode(changes.getOriginalData(), removedPort);
187                 if(tpParentNode == null){
188                     //Throwing this warning in case behavior of southbound plugin changes.
189                     LOG.warn("Port's {} parent node details are not present in original data, "
190                             + "it should not happen", removedPort);
191                     continue;
192                 }
193                 //Fetch data of removed port from original data
194                 @SuppressWarnings("unchecked")
195                 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
196                         (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
197
198                 LOG.trace("processPortDeletion <{}> deletion on Node <{}>", removedPort, tpParentNode);
199                 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
200                         OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
201             }
202         }
203     }
204
205     private void processPortUpdate(
206             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
207
208         for (Map.Entry<InstanceIdentifier<?>, DataObject> updatedPort : changes.getUpdatedData().entrySet()){
209             if (updatedPort.getKey().getTargetType().equals(OvsdbTerminationPointAugmentation.class)){
210                 //LOG.trace("processPortUpdate: <{}>", updatedPort);
211                 /* XXX (NOTE): Till now we don't really need the old termination point data before update.
212                  * I am passing the updated data of both Node and resource augmentation data (termination-point).
213                  * If in future we need old TerminationPointAugmentation data, we will extract it from
214                  * original data and pass it as a resourceAugmentationData.
215                  */
216                 Node tpParentNode  = getNode(changes.getUpdatedData(),updatedPort);
217                 if (tpParentNode == null){
218                     // Logging this warning, to catch any change in southbound plugin's behavior.
219                     LOG.warn("Parent Node for port is not found. On Port/Interface update data store"
220                             + " must provide the parent node update. This condition should not occure "
221                             + "with the existing models define in southbound plugin." );
222                     continue;
223                 }
224
225                 LOG.trace("processPortUpdate <{}> update on Node <{}>", updatedPort.getValue(), tpParentNode);
226                 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
227                         OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
228             }
229         }
230     }
231
232     private void processBridgeCreation(
233             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
234
235         for(Map.Entry<InstanceIdentifier<?>, DataObject> newBridge : changes.getCreatedData().entrySet()){
236             if(newBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
237                 //LOG.trace("processBridgeCreation <{}>", newBridge);
238                 //Bridge augmentation happens directly on the Node so Node details should also exist in created data.
239                 Node bridgeParentNode  = getNode(changes.getCreatedData(),newBridge);
240                 if(bridgeParentNode == null){
241                     // Logging this warning, to catch any change in southbound plugin behavior
242                     LOG.warn("Parent Node for bridge is not found. Bridge creation must provide the Node "
243                             + "details in create Data Changes. This condition should not occur." );
244                     continue;
245                 }
246                 LOG.trace("processBridgeCreation <{}> creation on Node <{}>", newBridge.getValue(), bridgeParentNode);
247                 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
248                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
249             }
250         }
251     }
252
253     private void processBridgeUpdate(
254             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
255
256         for (Map.Entry<InstanceIdentifier<?>, DataObject> updatedBridge : changes.getUpdatedData().entrySet()) {
257             if(updatedBridge.getKey().getTargetType().equals(OvsdbBridgeAugmentation.class)){
258                 //LOG.trace("processBridgeUpdate <{}>", updatedBridge);
259                 /* XXX (NOTE): Till now we don't really need the old bridge data before update.
260                  * I am passing the updated data of both Node and resource augmentation data.
261                  * If in future we need old bridgeAugmentationData, we will extract it from
262                  * original data and pass it as a resourceAugmentationData.
263                  */
264
265                 Node bridgeParentNode = getNode(changes.getUpdatedData(), updatedBridge);
266                 if(bridgeParentNode == null){
267                     // Logging this warning, to catch any change in southbound plugin behavior
268                     LOG.warn("Parent Node for bridge is not found. Bridge update must provide the Node "
269                             + "details in updated Data Changes. This condition should not occure" );
270                     continue;
271                 }
272                 LOG.trace("processBridgeUpdate <{}> update on Node <{}>", updatedBridge.getValue(), bridgeParentNode);
273                 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
274                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
275             }
276         }
277     }
278
279     private void processBridgeDeletion(
280             AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
281
282         for(InstanceIdentifier<?> removedBridge : changes.getRemovedPaths()) {
283             if(removedBridge.getTargetType().equals(OvsdbBridgeAugmentation.class)){
284                 Node bridgeParentNode = getNode(changes.getOriginalData(), removedBridge);
285                 if(bridgeParentNode == null){
286                     //Throwing this warning to catch the behavior change of southbound plugin.
287                     LOG.warn("Bridge's {} parent node details are not present in original data"
288                             + ", it should not happen", removedBridge);
289                     continue;
290                 }
291                 //Fetch data of removed bridge from original data
292                 @SuppressWarnings("unchecked")
293                 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
294                         (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
295
296                 LOG.debug("processBridgeDeletion <{}> deletion on Node <{}>", removedBridge,bridgeParentNode);
297                 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
298                         OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
299             }
300         }
301     }
302
303     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,
304                          Map.Entry<InstanceIdentifier<?>, DataObject> change) {
305         InstanceIdentifier<Node> nodeInstanceIdentifier = change.getKey().firstIdentifierOf(Node.class);
306         return (Node)changes.get(nodeInstanceIdentifier);
307     }
308
309     private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
310         InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
311         return (Node)changes.get(nodeInstanceIdentifier);
312     }
313
314     private <T extends DataObject> T getDataChanges(
315             Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
316
317         for(Map.Entry<InstanceIdentifier<?>,DataObject> change : changes.entrySet()){
318             if(change.getKey().getTargetType().equals(path.getTargetType())){
319                 @SuppressWarnings("unchecked")
320                 T dataObject = (T) change.getValue();
321                 return dataObject;
322             }
323         }
324         return null;
325     }
326
327     private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
328             OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
329
330         Set<OvsdbInventoryListener> ovsdbInventoryListeners = OvsdbInventoryServiceImpl.getOvsdbInventoryListeners();
331         for (OvsdbInventoryListener ovsdbInventoryListener : ovsdbInventoryListeners) {
332             ovsdbInventoryListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
333         }
334     }
335
336     private void triggerUpdates() {
337         Set<OvsdbInventoryListener> ovsdbInventoryListeners = OvsdbInventoryServiceImpl.getOvsdbInventoryListeners();
338         for (OvsdbInventoryListener ovsdbInventoryListener : ovsdbInventoryListeners) {
339             ovsdbInventoryListener.triggerUpdates();
340         }
341     }
342 }