Fix use of raw Collections.EMPTY_{LIST,SET}
[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 import java.util.concurrent.ExecutionException;
20 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
21 import org.opendaylight.mdsal.binding.api.DataBroker;
22 import org.opendaylight.mdsal.binding.api.DataObjectModification;
23 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
24 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.mdsal.binding.api.DataTreeModification;
26 import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
27 import org.opendaylight.mdsal.common.api.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.NodeId;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
38 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
39 import org.opendaylight.yangtools.concepts.ListenerRegistration;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 public class HwvtepDataChangeListener implements ClusteredDataTreeChangeListener<Node>, AutoCloseable {
45
46     private ListenerRegistration<HwvtepDataChangeListener> registration;
47     private final HwvtepConnectionManager hcm;
48     private final 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();
56     }
57
58     private void registerListener() {
59         final DataTreeIdentifier<Node> treeId =
60                 DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, getWildcardPath());
61
62         LOG.trace("Registering on path: {}", treeId);
63         registration = db.registerDataTreeChangeListener(treeId, HwvtepDataChangeListener.this);
64     }
65
66     @Override
67     public void close() {
68         if (registration != null) {
69             registration.close();
70         }
71     }
72
73     @Override
74     public void onDataTreeChanged(Collection<DataTreeModification<Node>> changes) {
75         LOG.trace("onDataTreeChanged: {}", changes);
76
77         /* TODO:
78          * Currently only handling changes to Global.
79          * Rest will be added later.
80          */
81         connect(changes);
82
83         updateConnections(changes);
84
85         updateData(changes);
86
87         disconnect(changes);
88
89         disconnectViaCli(changes);
90     }
91
92     private void connect(Collection<DataTreeModification<Node>> changes) {
93         for (DataTreeModification<Node> change : changes) {
94             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
95             final DataObjectModification<Node> mod = change.getRootNode();
96             Node node = getCreated(mod);
97             if (node != null) {
98                 HwvtepGlobalAugmentation hwvtepGlobal = node.augmentation(HwvtepGlobalAugmentation.class);
99                 // We can only connect if user configured connection info
100                 if (hwvtepGlobal != null && hwvtepGlobal.getConnectionInfo() != null) {
101                     ConnectionInfo connection = hwvtepGlobal.getConnectionInfo();
102                     InstanceIdentifier<Node> iid = hcm.getInstanceIdentifier(connection);
103                     if (iid != null) {
104                         LOG.warn("Connection to device {} already exists. Plugin does not allow multiple connections "
105                                         + "to same device, hence dropping the request {}", connection, hwvtepGlobal);
106                     } else {
107                         try {
108                             hcm.connect(key, hwvtepGlobal);
109                         } catch (UnknownHostException | ConnectException e) {
110                             LOG.warn("Failed to connect to HWVTEP node", e);
111                         }
112                     }
113                 }
114             }
115         }
116     }
117
118     private void updateConnections(Collection<DataTreeModification<Node>> changes) {
119         for (DataTreeModification<Node> change : changes) {
120             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
121             final DataObjectModification<Node> mod = change.getRootNode();
122             Node updated = getUpdated(mod);
123             if (updated != null) {
124                 Node original = getOriginal(mod);
125                 HwvtepGlobalAugmentation hgUpdated = updated.augmentation(HwvtepGlobalAugmentation.class);
126                 HwvtepGlobalAugmentation hgOriginal = original.augmentation(HwvtepGlobalAugmentation.class);
127                 // Check if user has updated connection information
128                 if (hgUpdated != null && hgOriginal != null && hgUpdated.getConnectionInfo() != null
129                                 && !hgUpdated.getConnectionInfo().equals(hgOriginal.getConnectionInfo())) {
130                     OvsdbClient client = hcm.getClient(hgUpdated.getConnectionInfo());
131                     if (client == null) {
132                         try {
133                             hcm.disconnect(hgOriginal);
134                             hcm.stopConnectionReconciliationIfActive(key, hgOriginal);
135                             OvsdbClient newClient = hcm.connect(key, hgUpdated);
136                             if (newClient == null) {
137                                 hcm.reconcileConnection(key, hgUpdated);
138                             }
139                         } catch (UnknownHostException | ConnectException e) {
140                             LOG.warn("Failed to update connection on HWVTEP Node", e);
141                         }
142                     }
143                 }
144             }
145         }
146     }
147
148     private void updateData(Collection<DataTreeModification<Node>> changes) {
149         /* TODO:
150          * Get connection instances for each change
151          * Update data for each connection
152          * Requires Command patterns. TBD.
153          */
154         for (Entry<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> changesEntry :
155                 changesByConnectionInstance(changes).entrySet()) {
156             HwvtepConnectionInstance connectionInstance = changesEntry.getKey();
157             connectionInstance.transact(new TransactCommandAggregator(
158                 new HwvtepOperationalState(db, connectionInstance, changesEntry.getValue()),changesEntry.getValue()));
159             connectionInstance.getDeviceInfo().onConfigDataAvailable();
160         }
161     }
162
163     private void disconnect(Collection<DataTreeModification<Node>> changes) {
164         for (DataTreeModification<Node> change : changes) {
165             final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
166             final DataObjectModification<Node> mod = change.getRootNode();
167             Node deleted = getRemoved(mod);
168             if (deleted != null) {
169                 HwvtepGlobalAugmentation hgDeleted = deleted.augmentation(HwvtepGlobalAugmentation.class);
170                 if (hgDeleted != null) {
171                     try {
172                         hcm.disconnect(hgDeleted);
173                         hcm.stopConnectionReconciliationIfActive(key, hgDeleted);
174                     } catch (UnknownHostException e) {
175                         LOG.warn("Failed to disconnect HWVTEP Node", e);
176                     }
177                 }
178             }
179         }
180     }
181
182     private static Node getCreated(DataObjectModification<Node> mod) {
183         if (mod.getModificationType() == ModificationType.WRITE && mod.getDataBefore() == null) {
184             return mod.getDataAfter();
185         }
186         return null;
187     }
188
189     private static Node getRemoved(DataObjectModification<Node> mod) {
190         if (mod.getModificationType() == ModificationType.DELETE) {
191             return mod.getDataBefore();
192         }
193         return null;
194     }
195
196     private static Node getUpdated(DataObjectModification<Node> mod) {
197         Node node = null;
198         switch (mod.getModificationType()) {
199             case SUBTREE_MODIFIED:
200                 node = mod.getDataAfter();
201                 break;
202             case WRITE:
203                 if (mod.getDataBefore() != null) {
204                     node = mod.getDataAfter();
205                 }
206                 break;
207             default:
208                 break;
209         }
210         return node;
211     }
212
213     private static Node getOriginal(DataObjectModification<Node> mod) {
214         Node node = null;
215         switch (mod.getModificationType()) {
216             case SUBTREE_MODIFIED:
217             case DELETE:
218                 node = mod.getDataBefore();
219                 break;
220             case WRITE:
221                 if (mod.getDataBefore() != null) {
222                     node = mod.getDataBefore();
223                 }
224                 break;
225             default:
226                 break;
227         }
228         return node;
229     }
230
231     private static InstanceIdentifier<Node> getWildcardPath() {
232         return InstanceIdentifier.create(NetworkTopology.class)
233                         .child(Topology.class, new TopologyKey(HwvtepSouthboundConstants.HWVTEP_TOPOLOGY_ID))
234                         .child(Node.class);
235     }
236
237     private Map<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> changesByConnectionInstance(
238             Collection<DataTreeModification<Node>> changes) {
239         Map<HwvtepConnectionInstance, Collection<DataTreeModification<Node>>> result = new HashMap<>();
240         for (DataTreeModification<Node> change : changes) {
241             final DataObjectModification<Node> mod = change.getRootNode();
242             //From original node to get connection instance
243             Node node = mod.getDataBefore() != null ? mod.getDataBefore() : mod.getDataAfter();
244             HwvtepConnectionInstance connection = hcm.getConnectionInstanceFromNodeIid(
245                     change.getRootPath().getRootIdentifier());
246             if (connection != null) {
247                 if (!result.containsKey(connection)) {
248                     List<DataTreeModification<Node>> tempChanges = new ArrayList<>();
249                     tempChanges.add(change);
250                     result.put(connection, tempChanges);
251                 } else {
252                     result.get(connection).add(change);
253                 }
254             } else {
255                 LOG.warn("Failed to get the connection of changed node: {}", node.key().getNodeId().getValue());
256             }
257         }
258         LOG.trace("Connection Change Map: {}", result);
259         return result;
260     }
261
262     @SuppressWarnings("checkstyle:IllegalCatch")
263     private void disconnectViaCli(Collection<DataTreeModification<Node>> changes) {
264         for (DataTreeModification<Node> change : changes) {
265             String nodeId = change.getRootPath().getRootIdentifier().firstKeyOf(Node.class).getNodeId().getValue();
266             if (!nodeId.contains("/disconnect")) {
267                 continue;
268             }
269             int reconcileIndex = nodeId.indexOf("/disconnect");
270             String globalNodeId = nodeId.substring(0, reconcileIndex);
271             InstanceIdentifier<Node> globalNodeIid = change.getRootPath()
272                 .getRootIdentifier().firstIdentifierOf(Topology.class)
273                 .child(Node.class, new NodeKey(new NodeId(globalNodeId)));
274             HwvtepConnectionInstance connectionInstance = hcm.getConnectionInstanceFromNodeIid(globalNodeIid);
275             if (connectionInstance != null) {
276                 LOG.error("Disconnecting from controller {}", nodeId);
277                 new Thread(() -> {
278                     ReadWriteTransaction tx = db.newReadWriteTransaction();
279                     tx.delete(LogicalDatastoreType.CONFIGURATION, change.getRootPath().getRootIdentifier());
280                     try {
281                         tx.commit().get();
282                     } catch (ExecutionException | InterruptedException e) {
283                         LOG.error("Failed to delete the node {}", change.getRootPath().getRootIdentifier());
284                     }
285                 }).start();
286                 try {
287                     connectionInstance.disconnect();
288                 } catch (Exception e) {
289                     LOG.debug("Failed to disconnect");
290                 }
291             }
292         }
293     }
294 }