bug 7599 avoid mdsal read
[ovsdb.git] / hwvtepsouthbound / hwvtepsouthbound-impl / src / main / java / org / opendaylight / ovsdb / hwvtepsouthbound / HwvtepDataChangeListener.java
1 /*
2  * Copyright (c) 2015, 2017 Ericsson India Global Services Pvt Ltd. 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.hwvtepsouthbound;
10
11 import java.net.ConnectException;
12 import java.net.UnknownHostException;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
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.DataObjectModification.ModificationType;
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.binding.api.ReadWriteTransaction;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.ovsdb.hwvtepsouthbound.transact.HwvtepOperationalState;
29 import org.opendaylight.ovsdb.hwvtepsouthbound.transact.TransactCommandAggregator;
30 import org.opendaylight.ovsdb.lib.OvsdbClient;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.HwvtepGlobalAugmentation;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.hwvtep.global.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.yangtools.concepts.ListenerRegistration;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.common.base.Optional;
43
44 public class HwvtepDataChangeListener implements ClusteredDataTreeChangeListener<Node>, AutoCloseable {
45
46     private ListenerRegistration<HwvtepDataChangeListener> registration;
47     private HwvtepConnectionManager hcm;
48     private DataBroker db;
49     private static final Logger LOG = LoggerFactory.getLogger(HwvtepDataChangeListener.class);
50
51     HwvtepDataChangeListener(DataBroker db, HwvtepConnectionManager hcm) {
52         LOG.info("Registering HwvtepDataChangeListener");
53         this.db = db;
54         this.hcm = hcm;
55         registerListener(db);
56     }
57
58     private void registerListener(final DataBroker db) {
59         final DataTreeIdentifier<Node> treeId =
60                         new DataTreeIdentifier<Node>(LogicalDatastoreType.CONFIGURATION, getWildcardPath());
61         try {
62             LOG.trace("Registering on path: {}", treeId);
63             registration = db.registerDataTreeChangeListener(treeId, HwvtepDataChangeListener.this);
64         } catch (final Exception e) {
65             LOG.warn("HwvtepDataChangeListener registration failed", e);
66             //TODO: Should we throw an exception here?
67         }
68     }
69
70     @Override
71     public void close() throws Exception {
72         if(registration != null) {
73             registration.close();
74         }
75     }
76
77     @Override
78     public void onDataTreeChanged(Collection<DataTreeModification<Node>> changes) {
79         LOG.trace("onDataTreeChanged: {}", changes);
80
81         /* TODO:
82          * Currently only handling changes to Global.
83          * Rest will be added later.
84          */
85         connect(changes);
86         
87         updateConnections(changes);
88         
89         updateData(changes);
90         
91         disconnect(changes);
92         /*
93         for (DataTreeModification<Node> change : changes) {
94             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
95             final DataObjectModification<Node> mod = change.getRootNode();
96                 switch (mod.getModificationType()) {
97                 case DELETE:
98                     LOG.trace("Data deleted: {}", mod.getDataBefore());
99                     //disconnect(mod);
100                     break;
101                 case SUBTREE_MODIFIED:
102                     LOG.trace("Data modified: {} to {}", mod.getDataBefore(),mod.getDataAfter());
103                     updateConnections(mod);
104                     break;
105                 case WRITE:
106                     if (mod.getDataBefore() == null) {
107                         LOG.trace("Data added: {}", mod.getDataAfter());
108                         connect(mod.getDataAfter());
109                     } else {
110                         LOG.trace("Data modified: {} to {}", mod.getDataBefore(),mod.getDataAfter());
111                         updateConnections(mod);
112                     }
113                     break;
114                 default:
115                     throw new IllegalArgumentException("Unhandled modification type " + mod.getModificationType());
116                 }
117         }
118         */
119     }
120
121     private void connect(Collection<DataTreeModification<Node>> changes) {
122         for (DataTreeModification<Node> change : changes) {
123             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
124             final DataObjectModification<Node> mod = change.getRootNode();
125             Node node = getCreated(mod);
126             if (node != null) {
127                 HwvtepGlobalAugmentation hwvtepGlobal = node.getAugmentation(HwvtepGlobalAugmentation.class);
128                 // We can only connect if user configured connection info
129                 if (hwvtepGlobal != null && hwvtepGlobal.getConnectionInfo() != null) {
130                     ConnectionInfo connection = hwvtepGlobal.getConnectionInfo();
131                     InstanceIdentifier<Node> iid = hcm.getInstanceIdentifier(connection);
132                     if (iid != null) {
133                         LOG.warn("Connection to device {} already exists. Plugin does not allow multiple connections "
134                                         + "to same device, hence dropping the request {}", connection, hwvtepGlobal);
135                     } else {
136                         try {
137                             OvsdbClient client = hcm.connect(key, hwvtepGlobal);
138                         } catch (UnknownHostException | ConnectException e) {
139                             LOG.warn("Failed to connect to HWVTEP node", e);
140                         }
141                     }
142                 }
143             }
144         }
145     }
146
147     private void updateConnections(Collection<DataTreeModification<Node>> changes) {
148         for (DataTreeModification<Node> change : changes) {
149             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
150             final DataObjectModification<Node> mod = change.getRootNode();
151             Node updated = getUpdated(mod);
152             if (updated != null) {
153                 Node original = getOriginal(mod);
154                 HwvtepGlobalAugmentation hgUpdated = updated.getAugmentation(HwvtepGlobalAugmentation.class);
155                 HwvtepGlobalAugmentation hgOriginal = original.getAugmentation(HwvtepGlobalAugmentation.class);
156                 // Check if user has updated connection information
157                 if (hgUpdated != null && hgOriginal != null && hgUpdated.getConnectionInfo() != null
158                                 && !hgUpdated.getConnectionInfo().equals(hgOriginal.getConnectionInfo())) {
159                     OvsdbClient client = hcm.getClient(hgUpdated.getConnectionInfo());
160                     if (client == null) {
161                         try {
162                             hcm.disconnect(hgOriginal);
163                             hcm.stopConnectionReconciliationIfActive(key, hgOriginal);
164                             OvsdbClient newClient = hcm.connect(key, hgUpdated);
165                             if (newClient == null) {
166                                 hcm.reconcileConnection(key, hgUpdated);
167                             }
168                         } catch (UnknownHostException | ConnectException e) {
169                             LOG.warn("Failed to update connection on HWVTEP Node", e);
170                         }
171                     }
172                 }
173             }
174         }
175     }
176
177     private void updateData(Collection<DataTreeModification<Node>> changes) {
178         /* TODO:
179          * Get connection instances for each change
180          * Update data for each connection
181          * Requires Command patterns. TBD.
182          */
183         for (Entry<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> changesEntry :
184                 changesByConnectionInstance(changes).entrySet()) {
185             HwvtepConnectionInstance connectionInstance = changesEntry.getKey();
186             connectionInstance.transact(new TransactCommandAggregator(
187                 new HwvtepOperationalState(db, connectionInstance, changesEntry.getValue()),changesEntry.getValue()));
188             connectionInstance.getDeviceInfo().onConfigDataAvailable();
189         }
190     }
191
192     private void disconnect(Collection<DataTreeModification<Node>> changes) {
193         for (DataTreeModification<Node> change : changes) {
194             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
195             final DataObjectModification<Node> mod = change.getRootNode();
196             Node deleted = getRemoved(mod);
197             if (deleted != null) {
198                 HwvtepGlobalAugmentation hgDeleted = deleted.getAugmentation(HwvtepGlobalAugmentation.class);
199                 if (hgDeleted != null) {
200                     try {
201                         hcm.disconnect(hgDeleted);
202                         hcm.stopConnectionReconciliationIfActive(key, hgDeleted);
203                     } catch (UnknownHostException e) {
204                         LOG.warn("Failed to disconnect HWVTEP Node", e);
205                     }
206                 }
207             }
208         }
209     }
210
211     private Node getCreated(DataObjectModification<Node> mod) {
212         if((mod.getModificationType() == ModificationType.WRITE)
213                         && (mod.getDataBefore() == null)){
214             return mod.getDataAfter();
215         }
216         return null;
217     }
218
219     private Node getRemoved(DataObjectModification<Node> mod) {
220         if(mod.getModificationType() == ModificationType.DELETE){
221             return mod.getDataBefore();
222         }
223         return null;
224     }
225
226     private Node getUpdated(DataObjectModification<Node> mod) {
227         Node node = null;
228         switch(mod.getModificationType()) {
229             case SUBTREE_MODIFIED:
230                 node = mod.getDataAfter();
231                 break;
232             case WRITE:
233                 if(mod.getDataBefore() !=  null) {
234                     node = mod.getDataAfter();
235                 }
236                 break;
237             default:
238                 break;
239         }
240         return node;
241     }
242
243     private Node getOriginal(DataObjectModification<Node> mod) {
244         Node node = null;
245         switch(mod.getModificationType()) {
246             case SUBTREE_MODIFIED:
247                 node = mod.getDataBefore();
248                 break;
249             case WRITE:
250                 if(mod.getDataBefore() !=  null) {
251                     node = mod.getDataBefore();
252                 }
253                 break;
254             case DELETE:
255                 node = mod.getDataBefore();
256                 break;
257             default:
258                 break;
259         }
260         return node;
261     }
262
263     private InstanceIdentifier<Node> getWildcardPath() {
264         InstanceIdentifier<Node> path = InstanceIdentifier
265                         .create(NetworkTopology.class)
266                         .child(Topology.class, new TopologyKey(HwvtepSouthboundConstants.HWVTEP_TOPOLOGY_ID))
267                         .child(Node.class);
268         return path;
269     }
270
271     private Map<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> changesByConnectionInstance(
272             Collection<DataTreeModification<Node>> changes) {
273         Map<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> result =
274                 new HashMap<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>>();
275         for (DataTreeModification<Node> change : changes) {
276             final DataObjectModification<Node> mod = change.getRootNode();
277             //From original node to get connection instance
278             Node node = mod.getDataBefore()!=null ? mod.getDataBefore() : mod.getDataAfter();
279             HwvtepConnectionInstance connection = hcm.getConnectionInstanceFromNodeIid(
280                     change.getRootPath().getRootIdentifier());
281             if (connection != null) {
282                 if (!result.containsKey(connection)) {
283                     List<DataTreeModification<Node>> tempChanges= new ArrayList<DataTreeModification<Node>>();
284                     tempChanges.add(change);
285                     result.put(connection, tempChanges);
286                 } else {
287                     result.get(connection).add(change);
288                 }
289             } else {
290                 LOG.warn("Failed to get the connection of changed node: {}", node.getKey().getNodeId().getValue());
291             }
292         }
293         LOG.trace("Connection Change Map: {}", result);
294         return result;
295     }
296 }