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