2 * Copyright (c) 2015 Red Hat, Inc. and others. All rights reserved.
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
8 package org.opendaylight.ovsdb.openstack.netvirt.impl;
12 import java.util.concurrent.ExecutorService;
13 import java.util.concurrent.Executors;
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;
37 * MDSAL dataChangeListener for the OVSDB Southbound
39 * @author Sam Hague (shague@redhat.com)
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);
47 public OvsdbDataChangeListener (DataBroker dataBroker) {
48 this.dataBroker = dataBroker;
52 InstanceIdentifier<Node> path = InstanceIdentifier
53 .create(NetworkTopology.class)
54 .child(Topology.class, new TopologyKey(MdsalHelper.OVSDB_TOPOLOGY_ID))
56 registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, path, this,
57 DataChangeScope.SUBTREE);
58 LOG.info("netvirt OvsdbDataChangeListener: dataBroker= {}, registration= {}",
59 dataBroker, registration);
64 public void close () throws Exception {
66 executorService.shutdown();
70 public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
72 executorService.submit(new Runnable(){
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);
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);
100 private void processOvsdbDisconnect(
101 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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);
114 //Fetch data of removed connection info from original data
115 @SuppressWarnings("unchecked")
116 OvsdbNodeAugmentation removedOvsdbNodeAugmentationData = getDataChanges(changes.getOriginalData(),
117 (InstanceIdentifier<OvsdbNodeAugmentation>) removedOvsdbNode);
119 LOG.trace("processOvsdbDisconnect: {} ", removedOvsdbNode);
120 ////Assuming Openvswitch type represent the ovsdb node connection and not OvsdbType.NODE
122 ovsdbUpdate(parentNode, removedOvsdbNodeAugmentationData,
123 OvsdbInventoryListener.OvsdbType.NODE, Action.DELETE);
128 private void processOvsdbConnectionAttributeUpdates(
129 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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.
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." );
147 LOG.trace("processOvsdbConnectionAttributeUpdates <{}> related update on Node: <{}>",
148 updatedOvsdbNode.getValue(), parentNode);
150 ovsdbUpdate(parentNode, updatedOvsdbNode.getValue(),
151 OvsdbInventoryListener.OvsdbType.NODE, Action.UPDATE);
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);
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." );
175 LOG.trace("processPortCreation <{}> creation on Node <{}>", newPort.getValue(), tpParentNode);
176 ovsdbUpdate(tpParentNode, newPort.getValue(),OvsdbInventoryListener.OvsdbType.PORT, Action.ADD);
181 private void processPortDeletion(
182 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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);
193 //Fetch data of removed port from original data
194 @SuppressWarnings("unchecked")
195 OvsdbTerminationPointAugmentation removedTPAugmentationData = getDataChanges(changes.getOriginalData(),
196 (InstanceIdentifier<OvsdbTerminationPointAugmentation>)removedPort);
198 LOG.trace("processPortDeletion <{}> deletion on Node <{}>", removedPort, tpParentNode);
199 ovsdbUpdate(tpParentNode, removedTPAugmentationData,
200 OvsdbInventoryListener.OvsdbType.PORT, Action.DELETE);
205 private void processPortUpdate(
206 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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.
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." );
225 LOG.trace("processPortUpdate <{}> update on Node <{}>", updatedPort.getValue(), tpParentNode);
226 ovsdbUpdate(tpParentNode, updatedPort.getValue(),
227 OvsdbInventoryListener.OvsdbType.PORT, Action.UPDATE);
232 private void processBridgeCreation(
233 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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." );
246 LOG.trace("processBridgeCreation <{}> creation on Node <{}>", newBridge.getValue(), bridgeParentNode);
247 ovsdbUpdate(bridgeParentNode, newBridge.getValue(),
248 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.ADD);
253 private void processBridgeUpdate(
254 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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.
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" );
272 LOG.trace("processBridgeUpdate <{}> update on Node <{}>", updatedBridge.getValue(), bridgeParentNode);
273 ovsdbUpdate(bridgeParentNode, updatedBridge.getValue(),
274 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.UPDATE);
279 private void processBridgeDeletion(
280 AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> changes) {
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);
291 //Fetch data of removed bridge from original data
292 @SuppressWarnings("unchecked")
293 OvsdbBridgeAugmentation removedBridgeAugmentationData = getDataChanges(changes.getOriginalData(),
294 (InstanceIdentifier<OvsdbBridgeAugmentation>) removedBridge);
296 LOG.debug("processBridgeDeletion <{}> deletion on Node <{}>", removedBridge,bridgeParentNode);
297 ovsdbUpdate(bridgeParentNode, removedBridgeAugmentationData,
298 OvsdbInventoryListener.OvsdbType.BRIDGE, Action.DELETE);
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);
309 private Node getNode(Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<?> path) {
310 InstanceIdentifier<Node> nodeInstanceIdentifier = path.firstIdentifierOf(Node.class);
311 return (Node)changes.get(nodeInstanceIdentifier);
314 private <T extends DataObject> T getDataChanges(
315 Map<InstanceIdentifier<?>, DataObject> changes,InstanceIdentifier<T> path){
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();
327 private void ovsdbUpdate(Node node, DataObject resourceAugmentationDataChanges,
328 OvsdbInventoryListener.OvsdbType ovsdbType, Action action) {
330 Set<OvsdbInventoryListener> ovsdbInventoryListeners = OvsdbInventoryServiceImpl.getOvsdbInventoryListeners();
331 for (OvsdbInventoryListener ovsdbInventoryListener : ovsdbInventoryListeners) {
332 ovsdbInventoryListener.ovsdbUpdate(node, resourceAugmentationDataChanges, ovsdbType, action);
336 private void triggerUpdates() {
337 Set<OvsdbInventoryListener> ovsdbInventoryListeners = OvsdbInventoryServiceImpl.getOvsdbInventoryListeners();
338 for (OvsdbInventoryListener ovsdbInventoryListener : ovsdbInventoryListeners) {
339 ovsdbInventoryListener.triggerUpdates();