18452b36e2fbc4b5f864f35e46dfa1ee572e9f63
[ovsdb.git] / southbound / southbound-impl / src / main / java / org / opendaylight / ovsdb / southbound / OvsdbDataTreeChangeListener.java
1 /*
2  * Copyright © 2016 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
9 package org.opendaylight.ovsdb.southbound;
10
11 import java.net.UnknownHostException;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Map.Entry;
17
18 import javax.annotation.Nonnull;
19
20 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
21 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
22 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
23 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
24 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
26 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
27 import org.opendaylight.ovsdb.lib.OvsdbClient;
28 import org.opendaylight.ovsdb.southbound.ovsdb.transact.BridgeOperationalState;
29 import org.opendaylight.ovsdb.southbound.ovsdb.transact.TransactCommandAggregator;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ConnectionInfo;
33 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPoint;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.binding.Augmentation;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Data-tree change listener for OVSDB.
46  */
47 public class OvsdbDataTreeChangeListener implements ClusteredDataTreeChangeListener<Node>, AutoCloseable {
48
49     /** Our registration. */
50     private final ListenerRegistration<DataTreeChangeListener<Node>> registration;
51
52     /** The connection manager. */
53     private final OvsdbConnectionManager cm;
54
55     /** The data broker. */
56     private final DataBroker db;
57
58     /** Logger. */
59     private static final Logger LOG = LoggerFactory.getLogger(OvsdbDataTreeChangeListener.class);
60
61     /**
62      * Create an instance and register the listener.
63      *
64      * @param db The data broker.
65      * @param cm The connection manager.
66      */
67     OvsdbDataTreeChangeListener(DataBroker db, OvsdbConnectionManager cm) {
68         this.cm = cm;
69         this.db = db;
70         InstanceIdentifier<Node> path = InstanceIdentifier
71                 .create(NetworkTopology.class)
72                 .child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID))
73                 .child(Node.class);
74         DataTreeIdentifier<Node> dataTreeIdentifier =
75                 new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, path);
76         registration = db.registerDataTreeChangeListener(dataTreeIdentifier, this);
77         LOG.info("OVSDB topology listener has been registered.");
78     }
79
80     @Override
81     public void close() {
82         registration.close();
83         LOG.info("OVSDB topology listener has been closed.");
84     }
85
86     @Override
87     public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Node>> changes) {
88         LOG.trace("onDataTreeChanged: {}", changes);
89
90         // Connect first if necessary
91         connect(changes);
92
93         // Update connections if necessary
94         updateConnections(changes);
95
96         // Update the actual data
97         updateData(changes);
98
99         // Disconnect if necessary
100         disconnect(changes);
101
102         LOG.trace("onDataTreeChanged: exit");
103     }
104
105     private void connect(@Nonnull Collection<DataTreeModification<Node>> changes) {
106         for (DataTreeModification<Node> change : changes) {
107             if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.WRITE || change
108                     .getRootNode().getModificationType() == DataObjectModification.ModificationType.SUBTREE_MODIFIED) {
109                 DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
110                         change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
111                 if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() == null
112                         && ovsdbNodeModification.getDataAfter() != null
113                         && ovsdbNodeModification.getDataAfter().getConnectionInfo() != null) {
114                     OvsdbNodeAugmentation ovsdbNode = ovsdbNodeModification.getDataAfter();
115                     ConnectionInfo key = ovsdbNode.getConnectionInfo();
116                     InstanceIdentifier<Node> iid = cm.getInstanceIdentifier(key);
117                     if ( iid != null) {
118                         LOG.warn("Connection to device {} already exists. Plugin does not allow multiple connections "
119                                 + "to same device, hence dropping the request {}", key, ovsdbNode);
120                     } else {
121                         try {
122                             InstanceIdentifier<Node> instanceIdentifier = change.getRootPath().getRootIdentifier();
123                             cm.connect(instanceIdentifier, ovsdbNode);
124                             LOG.info("OVSDB node has been connected: {}",ovsdbNode);
125                         } catch (UnknownHostException e) {
126                             LOG.warn("Failed to connect to ovsdbNode", e);
127                         }
128                     }
129                 }
130             }
131         }
132     }
133
134     private void disconnect(@Nonnull Collection<DataTreeModification<Node>> changes) {
135         for (DataTreeModification<Node> change : changes) {
136             if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.DELETE) {
137                 DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
138                         change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
139                 if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() != null) {
140                     OvsdbNodeAugmentation ovsdbNode = ovsdbNodeModification.getDataBefore();
141                     ConnectionInfo key = ovsdbNode.getConnectionInfo();
142                     InstanceIdentifier<Node> iid = cm.getInstanceIdentifier(key);
143                     try {
144                         cm.disconnect(ovsdbNode);
145                         LOG.info("OVSDB node has been disconnected:{}", ovsdbNode);
146                         cm.stopConnectionReconciliationIfActive(iid.firstIdentifierOf(Node.class), ovsdbNode);
147                     } catch (UnknownHostException e) {
148                         LOG.warn("Failed to disconnect ovsdbNode", e);
149                     }
150                 }
151             }
152         }
153     }
154
155     private void updateConnections(@Nonnull Collection<DataTreeModification<Node>> changes) {
156         for (DataTreeModification<Node> change : changes) {
157             if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.WRITE || change
158                     .getRootNode().getModificationType() == DataObjectModification.ModificationType.SUBTREE_MODIFIED) {
159                 DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
160                         change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
161                 if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() != null
162                         && ovsdbNodeModification.getDataAfter() != null
163                         && ovsdbNodeModification.getDataAfter().getConnectionInfo() != null) {
164                     OvsdbClient client = cm.getClient(ovsdbNodeModification.getDataAfter().getConnectionInfo());
165                     if (client == null) {
166                         if (ovsdbNodeModification.getDataBefore() != null) {
167                             try {
168                                 cm.disconnect(ovsdbNodeModification.getDataBefore());
169                                 cm.connect(change.getRootPath().getRootIdentifier(), ovsdbNodeModification
170                                         .getDataAfter());
171                             } catch (UnknownHostException e) {
172                                 LOG.warn("Error disconnecting from or connecting to ovsdbNode", e);
173                             }
174                         }
175                     }
176                 }
177             }
178         }
179     }
180
181     private void updateData(@Nonnull Collection<DataTreeModification<Node>> changes) {
182         for (Entry<InstanceIdentifier<Node>, OvsdbConnectionInstance> connectionInstanceEntry :
183                 connectionInstancesFromChanges(changes).entrySet()) {
184             OvsdbConnectionInstance connectionInstance = connectionInstanceEntry.getValue();
185             connectionInstance.transact(new TransactCommandAggregator(),
186                     new BridgeOperationalState(db, changes), changes);
187         }
188     }
189
190     private Map<InstanceIdentifier<Node>, OvsdbConnectionInstance> connectionInstancesFromChanges(
191             @Nonnull Collection<DataTreeModification<Node>> changes) {
192         Map<InstanceIdentifier<Node>,OvsdbConnectionInstance> result =
193                 new HashMap<>();
194         for (DataTreeModification<Node> change : changes) {
195             OvsdbConnectionInstance client = null;
196             Node node = change.getRootNode().getDataAfter() != null?
197                     change.getRootNode().getDataAfter() : change.getRootNode().getDataBefore();
198             if (node != null) {
199                 InstanceIdentifier<Node> nodeIid;
200                 Augmentation nodeAug = node.getAugmentation(OvsdbNodeAugmentation.class) !=null?
201                         node.getAugmentation(OvsdbNodeAugmentation.class):node.getAugmentation(OvsdbBridgeAugmentation.class);
202
203                 if(nodeAug instanceof OvsdbNodeAugmentation) {
204                     OvsdbNodeAugmentation ovsdbNode = (OvsdbNodeAugmentation) nodeAug;
205                     if(ovsdbNode.getConnectionInfo() != null) {
206                         client = cm.getConnectionInstance(ovsdbNode.getConnectionInfo());
207                     }else {
208                         client = cm.getConnectionInstance(SouthboundMapper.createInstanceIdentifier(node.getNodeId()));
209                     }
210                 }
211                 if(nodeAug instanceof OvsdbBridgeAugmentation) {
212                     OvsdbBridgeAugmentation bridgeAugmentation = (OvsdbBridgeAugmentation)nodeAug;
213                     if(bridgeAugmentation.getManagedBy() != null) {
214                         nodeIid = (InstanceIdentifier<Node>) bridgeAugmentation.getManagedBy().getValue();
215                         client = cm.getConnectionInstance(nodeIid);
216                     }
217                 }
218                 if( client == null ) {
219                     //Try getting from change root identifier
220                     client = cm.getConnectionInstance(change.getRootPath().getRootIdentifier());
221                 }
222             } else {
223                 LOG.warn("Following change don't have after/before data {}", change);
224             }
225             if (client != null) {
226                 LOG.debug("Found client for {}", node);
227                     /*
228                      * As of now data change sets are processed by single thread, so we can assume that device will
229                      * be connected and ownership will be decided before sending any instructions down to the device.
230                      * Note:Processing order in onDataChange() method should not change. If processing is changed to
231                      * use multiple thread, we might need to take care of corner cases, where ownership is not decided
232                      * but transaction are ready to go to switch. In that scenario, either we need to queue those task
233                      * till ownership is decided for that specific device.
234                      * Given that each DataChangeNotification is notified through separate thread, so we are already
235                      * multi threaded and i don't see any need to further parallelism per DataChange
236                      * notifications processing.
237                      */
238                 if ( cm.getHasDeviceOwnership(client.getMDConnectionInfo())) {
239                     LOG.debug("*This* instance of southbound plugin is an owner of the device {}", node);
240                     result.put(change.getRootPath().getRootIdentifier(), client);
241                 } else {
242                     LOG.debug("*This* instance of southbound plugin is *not* an owner of the device {}", node);
243                 }
244             } else {
245                 LOG.debug("Did not find client for {}", node);
246             }
247         }
248         return result;
249     }
250 }