a078e2c6ab52113a10b8b6660186a44d47fa2143
[groupbasedpolicy.git] / neutron-ovsdb / src / main / java / org / opendaylight / groupbasedpolicy / neutron / ovsdb / TerminationPointDataChangeListener.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, 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.groupbasedpolicy.neutron.ovsdb;
10
11 import static com.google.common.base.Preconditions.checkNotNull;
12 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.NodeDataChangeListener.getProviderMapping;
13 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.NodeDataChangeListener.processNodeNotification;
14 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.EndpointHelper.lookupEndpoint;
15 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.EndpointHelper.updateEndpointRemoveLocation;
16 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.EndpointHelper.updateEndpointWithLocation;
17 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper.checkOfOverlayConfig;
18 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper.getInventoryNodeConnectorIdString;
19 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper.getInventoryNodeIdString;
20 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper.removeTunnelsOfOverlayConfig;
21 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper.updateOfOverlayConfig;
22 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.NeutronHelper.getEpKeyFromNeutronMapper;
23 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.OvsdbHelper.createTunnelPort;
24 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.OvsdbHelper.getManagerNode;
25 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.OvsdbHelper.getOvsdbBridgeFromTerminationPoint;
26 import static org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.OvsdbHelper.getTopologyNode;
27 import static org.opendaylight.groupbasedpolicy.util.DataStoreHelper.readFromDs;
28
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Map.Entry;
33
34 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
35 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
36 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
37 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
38 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
39 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
40 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
41 import org.opendaylight.ovsdb.southbound.SouthboundConstants;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.Uuid;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.EndpointService;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.Endpoint;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.EndpointKey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbTerminationPointAugmentation;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.port._interface.attributes.InterfaceExternalIds;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
55 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPoint;
56 import org.opendaylight.yangtools.concepts.ListenerRegistration;
57 import org.opendaylight.yangtools.yang.binding.DataObject;
58 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.google.common.base.Optional;
63
64 public class TerminationPointDataChangeListener implements DataChangeListener, AutoCloseable {
65
66     private static final String NEUTRON_EXTERNAL_ID_KEY = "iface-id";
67     private final ListenerRegistration<DataChangeListener> registration;
68     private final DataBroker dataBroker;
69     private final EndpointService epService;
70     private static final Logger LOG = LoggerFactory.getLogger(TerminationPointDataChangeListener.class);
71     private final List<AbstractTunnelType> requiredTunnelTypes;
72
73     public TerminationPointDataChangeListener(DataBroker dataBroker, EndpointService epService) {
74         this.dataBroker = checkNotNull(dataBroker);
75         this.epService = checkNotNull(epService);
76         InstanceIdentifier<OvsdbTerminationPointAugmentation> iid = InstanceIdentifier.create(NetworkTopology.class)
77             .child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID))
78             .child(Node.class)
79             .child(TerminationPoint.class)
80             .augmentation(OvsdbTerminationPointAugmentation.class);
81         registration = dataBroker.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, iid, this,
82                 DataChangeScope.ONE);
83         requiredTunnelTypes = createSupportedTunnelsList();
84     }
85
86     private List<AbstractTunnelType> createSupportedTunnelsList() {
87         List<AbstractTunnelType> required = new ArrayList<AbstractTunnelType>();
88         required.add(new VxlanTunnelType());
89         required.add(new VxlanGpeTunnelType());
90         return Collections.unmodifiableList(required);
91     }
92
93     @Override
94     public void close() throws Exception {
95         registration.close();
96     }
97
98     @Override
99     public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
100
101         /*
102          * TerminationPoint notifications with OVSDB augmentations
103          * vSwitch ports. Iterate through the list of new ports.
104          */
105         for (Entry<InstanceIdentifier<?>, DataObject> entry : change.getCreatedData().entrySet()) {
106             if (entry.getValue() instanceof OvsdbTerminationPointAugmentation) {
107                 OvsdbTerminationPointAugmentation ovsdbTp = (OvsdbTerminationPointAugmentation) entry.getValue();
108                 InstanceIdentifier<OvsdbTerminationPointAugmentation> ovsdbTpIid = (InstanceIdentifier<OvsdbTerminationPointAugmentation>) entry.getKey();
109                 OvsdbBridgeAugmentation ovsdbBridge = getOvsdbBridgeFromTerminationPoint(ovsdbTpIid, dataBroker);
110                 processOvsdbBridge(ovsdbBridge, ovsdbTp, ovsdbTpIid);
111             }
112         }
113
114         /*
115          * Updates
116          */
117         for (Entry<InstanceIdentifier<?>, DataObject> entry : change.getUpdatedData().entrySet()) {
118             if (entry.getValue() instanceof OvsdbTerminationPointAugmentation) {
119                 OvsdbTerminationPointAugmentation ovsdbTp = (OvsdbTerminationPointAugmentation) entry.getValue();
120                 InstanceIdentifier<OvsdbTerminationPointAugmentation> ovsdbTpIid = (InstanceIdentifier<OvsdbTerminationPointAugmentation>) entry.getKey();
121                 OvsdbBridgeAugmentation ovsdbBridge = getOvsdbBridgeFromTerminationPoint(ovsdbTpIid, dataBroker);
122                 processOvsdbBridge(ovsdbBridge, ovsdbTp, ovsdbTpIid);
123             }
124         }
125
126         /*
127          * Deletions
128          */
129         for (InstanceIdentifier<?> iid : change.getRemovedPaths()) {
130             DataObject old = change.getOriginalData().get(iid);
131             if (old instanceof OvsdbTerminationPointAugmentation) {
132                 OvsdbTerminationPointAugmentation ovsdbTp = (OvsdbTerminationPointAugmentation) old;
133                 @SuppressWarnings("unchecked")
134                 InstanceIdentifier<OvsdbTerminationPointAugmentation> ovsdbTpIid = (InstanceIdentifier<OvsdbTerminationPointAugmentation>) iid;
135                 OvsdbBridgeAugmentation ovsdbBridge = getOvsdbBridgeFromTerminationPoint(ovsdbTpIid, dataBroker);
136                 if (ovsdbBridge != null) {
137                     processRemovedTp(ovsdbBridge, ovsdbTp, ovsdbTpIid);
138                 }
139             }
140         }
141     }
142
143     private void processOvsdbBridge(OvsdbBridgeAugmentation ovsdbBridge, OvsdbTerminationPointAugmentation ovsdbTp,
144             InstanceIdentifier<OvsdbTerminationPointAugmentation> ovsdbTpIid) {
145
146         checkNotNull(ovsdbBridge);
147         if (ovsdbBridge.getBridgeName().getValue().equals(ovsdbTp.getName())) {
148             LOG.debug("Termination Point {} same as Bridge {}. Not processing", ovsdbTp.getName(),
149                     ovsdbBridge.getBridgeName().getValue());
150             return;
151         }
152
153         String nodeIdString = getInventoryNodeIdString(ovsdbBridge, ovsdbTpIid, dataBroker);
154         if (nodeIdString == null) {
155             LOG.debug("nodeIdString for TerminationPoint {} was null", ovsdbTp);
156             return;
157         }
158         String nodeConnectorIdString = getInventoryNodeConnectorIdString(nodeIdString, ovsdbTp, ovsdbTpIid, dataBroker);
159         if (nodeConnectorIdString == null) {
160             LOG.debug("nodeConnectorIdString for TerminationPoint {} was null", ovsdbTp);
161             return;
162         }
163
164         InstanceIdentifier<Node> nodeIid = ovsdbTpIid.firstIdentifierOf(Node.class);
165         String externalId = getNeutronPortUuid(ovsdbTp);
166         Endpoint ep = null;
167         IpAddress hostIp = getIpFromOvsdb(ovsdbBridge);
168
169         /*
170          * Ports created by Nova have an external_id field
171          * in them, which is the Neutron port UUID. If a port
172          * has an external_id, get the EndpointKey for the
173          * Neutron port UUID from neutron-mapper, then look
174          * up the Endpoint in the Endpoint Registry using
175          * that key an update it with the location information
176          * (NodeId and NodeConnectorId from the inventory model)
177          * and the port name, constructed using the port UUID.
178          */
179
180         if (externalId != null) {
181             EndpointKey epKey = getEpKeyFromNeutronMapper(new Uuid(externalId), dataBroker);
182             if (epKey == null) {
183                 LOG.debug("TerminationPoint {} with external ID {} is not in Neutron Map", ovsdbTp, externalId);
184                 return;
185             }
186             ReadOnlyTransaction transaction = dataBroker.newReadOnlyTransaction();
187             ep = lookupEndpoint(epKey, transaction);
188             if (ep == null) {
189                 LOG.warn(
190                         "TerminationPoint {} with external ID {} is in Neutron Map, but corresponding Endpoint {} isn't in Endpoint Repository",
191                         ovsdbTp, externalId, epKey);
192                 return;
193             }
194             /*
195              * Look up the Node in Inventory that corresponds to the
196              * Topology Node that owns this Termination Point (port),
197              * and see if it already is configured with a complete
198              * OfOverlay augmentation. If it hasn't, go see if the
199              * tunnel ports exist, and if not, go and create them.
200              */
201             if (checkOfOverlayConfig(nodeIdString, requiredTunnelTypes, dataBroker) != true) {
202                 checkNotNull(nodeIid);
203                 /*
204                  * Check to see if we need to create a
205                  * tunnel port on the parent node
206                  */
207                 createTunnelPorts(nodeIid, dataBroker);
208             }
209         } else {
210             LOG.debug("TerminationPoint {} had no external ID, not processing for external ID.", ovsdbTp);
211
212         }
213
214         /*
215          * Check if Neutron External port was announed in Node before TerminationPoint it refers to
216          * was actually instantiated. This may or may not have external information in the future,
217          * hence
218          * not process as IF/ELSE externalID.
219          */
220         ReadOnlyTransaction transaction = dataBroker.newReadOnlyTransaction();
221         Optional<Node> node = readFromDs(LogicalDatastoreType.OPERATIONAL, nodeIid, transaction);
222         if (node.isPresent() && node.get().getAugmentation(OvsdbNodeAugmentation.class) != null) {
223             OvsdbNodeAugmentation ovsdbNodeAug = node.get().getAugmentation(OvsdbNodeAugmentation.class);
224             if (getProviderMapping(ovsdbNodeAug) != null) {
225                 processNodeNotification(ovsdbNodeAug);
226
227             }
228         } else {
229         }
230         /*
231          * This may be a notification for a tunnel we just created.
232          * In that case, we need to update the Inventory Node's OfOverlay
233          * augmentation with missing information
234          */
235         AbstractTunnelType tunnel = getTunnelType(ovsdbTp, requiredTunnelTypes);
236         if (tunnel != null) {
237             updateOfOverlayConfig(hostIp, nodeIdString, nodeConnectorIdString, tunnel, dataBroker);
238         }
239         if (externalId != null) {
240             ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
241             updateEndpointWithLocation(ep, nodeIdString, nodeConnectorIdString, rwTx);
242         }
243     }
244
245     /**
246      * If removed termination point was a tunnel port,
247      * removes attached tunnels (namely Vxlan-type) from OVSDB bridge;
248      * else removes location info from TP
249      *
250      * @param ovsdbBridge {@link OvsdbBridgeAugmentation}
251      * @param ovsdbTp {@link OvsdbTerminationPointAugmentation}
252      * @param ovsdbTpIid termination point's IID {@link InstanceIdentifier}
253      */
254     private void processRemovedTp(OvsdbBridgeAugmentation ovsdbBridge, OvsdbTerminationPointAugmentation ovsdbTp,
255             InstanceIdentifier<OvsdbTerminationPointAugmentation> ovsdbTpIid) {
256
257         checkNotNull(ovsdbBridge);
258         if (ovsdbBridge.getBridgeName().getValue().equals(ovsdbTp.getName())) {
259             LOG.debug("Termination Point {} same as Bridge {}. Not processing.", ovsdbTp.getName(),
260                     ovsdbBridge.getBridgeName().getValue());
261             return;
262         }
263
264         String nodeIdString = getInventoryNodeIdString(ovsdbBridge, ovsdbTpIid, dataBroker);
265         if (nodeIdString == null) {
266             LOG.debug("nodeIdString for TerminationPoint {} was null.", ovsdbTp);
267             return;
268         }
269
270         if (isTunnelPort(ovsdbTp, requiredTunnelTypes)) {
271             removeTunnelsOfOverlayConfig(nodeIdString, requiredTunnelTypes, dataBroker);
272         } else {
273             deleteLocationForTp(ovsdbTp);
274         }
275     }
276
277     /**
278      * Delete location on EP for given TP
279      *
280      * @param ovsdbTp {@link OvsdbTerminationPointAugmentation}
281      */
282     private void deleteLocationForTp(OvsdbTerminationPointAugmentation ovsdbTp) {
283         String externalId = getNeutronPortUuid(ovsdbTp);
284         if (externalId != null) {
285             EndpointKey epKey = getEpKeyFromNeutronMapper(new Uuid(externalId), dataBroker);
286             if (epKey == null) {
287                 LOG.debug("TerminationPoint {} with external ID {} is not in Neutron Map.", ovsdbTp, externalId);
288                 return;
289             }
290             ReadOnlyTransaction readOnlyTransaction = dataBroker.newReadOnlyTransaction();
291             Endpoint ep = lookupEndpoint(epKey, readOnlyTransaction);
292             readOnlyTransaction.close();
293             if (ep == null) {
294                 LOG.warn(
295                         "TerminationPoint {} with external ID {} is in Neutron Map, but corresponding Endpoint {} isn't in Endpoint Repository.",
296                         ovsdbTp, externalId, epKey);
297                 return;
298             }
299             updateEndpointRemoveLocation(ep, dataBroker.newReadWriteTransaction());
300         } else {
301             LOG.debug("TerminationPoint {} has no external ID, not processing.", ovsdbTp);
302         }
303     }
304
305     /**
306      * Check to see if the {@link OvsdbTerminationPointAugmentation} is also a Tunnel port that we
307      * care about.
308      *
309      * @param ovsdbTp {@link OvsdbTerminationPointAugmentation}
310      * @param requiredTunnelTypes {@link List} of tunnel types
311      */
312     private static AbstractTunnelType getTunnelType(OvsdbTerminationPointAugmentation ovsdbTp,
313             List<AbstractTunnelType> requiredTunnelTypes) {
314         if (ovsdbTp.getInterfaceType() != null) {
315             for (AbstractTunnelType tunnelType : requiredTunnelTypes) {
316                 if (tunnelType.isValidTunnelPort(ovsdbTp)) {
317                     return tunnelType;
318                 }
319             }
320         }
321         return null;
322     }
323
324     /*
325      * Check to see if the {@link OvsdbTerminationPointAugmentation}
326      * is also a Tunnel port that we care about.
327      *
328      * @param ovsdbTp {@link OvsdbTerminationPointAugmentation}
329      *
330      * @param requiredTunnelTypes {@link List} of tunnel types
331      *
332      * @return true if it's a required tunnel port, false if it isn't
333      */
334     private boolean isTunnelPort(OvsdbTerminationPointAugmentation ovsdbTp, List<AbstractTunnelType> requiredTunnelTypes) {
335         if (ovsdbTp.getInterfaceType() != null) {
336             for (AbstractTunnelType tunnelType : requiredTunnelTypes) {
337                 if (tunnelType.isValidTunnelPort(ovsdbTp)) {
338                     return true;
339                 }
340             }
341         }
342         return false;
343     }
344
345     /**
346      * Get the Neutron Port UUID from an {@link OvsdbTerminationPointAugmentation}.
347      * The Neutron Port UUID is stored as an "external-id" in the termination point.
348      *
349      * @param ovsdbTp The OVSDB Termination Point augmentation
350      * @return The String representation of the Neutron Port UUID, null if not present
351      */
352     private String getNeutronPortUuid(OvsdbTerminationPointAugmentation ovsdbTp) {
353         if (ovsdbTp.getInterfaceExternalIds() == null) {
354             return null;
355         }
356         for (InterfaceExternalIds id : ovsdbTp.getInterfaceExternalIds()) {
357             if (id.getExternalIdKey() != null && id.getExternalIdKey().equals(NEUTRON_EXTERNAL_ID_KEY)) {
358
359                 if (id.getExternalIdValue() != null) {
360                     return id.getExternalIdValue();
361                 }
362             }
363         }
364         return null;
365     }
366
367     /**
368      * Check to see if all tunnel ports are present, and if not,
369      * create them.
370      *
371      * @param nodeIid {@link InstanceIdentifier}
372      * @param dataBroker {@link DataBroker}
373      */
374     private void createTunnelPorts(InstanceIdentifier<Node> nodeIid, DataBroker dataBroker) {
375
376         Node node = getTopologyNode(nodeIid, dataBroker);
377         checkNotNull(node);
378
379         if (node.getAugmentation(OvsdbBridgeAugmentation.class) == null) {
380             LOG.trace("Node {} is not an OVSDB manageable node", nodeIid);
381             return;
382         }
383
384         /*
385          * See if this Topology Node has the required tunnel ports,
386          * and if not, go and create them
387          */
388         for (AbstractTunnelType tunnelType : requiredTunnelTypes) {
389             boolean tunnelPresent = false;
390             for (TerminationPoint tp : node.getTerminationPoint()) {
391                 OvsdbTerminationPointAugmentation tpAug = tp.getAugmentation(OvsdbTerminationPointAugmentation.class);
392
393                 checkNotNull(tpAug);
394
395                 if (tunnelType.isValidTunnelPort(tpAug)) {
396                     tunnelPresent = true;
397                     break;
398                 }
399             }
400             if (!tunnelPresent) {
401                 createTunnelPort(nodeIid, node, tunnelType, dataBroker);
402             }
403         }
404     }
405
406     /**
407      * Get the IP address of the host that owns the {@link OvsdbBridgeAugmentation}.
408      *
409      * @param ovsdbBridge The OVSDB bridge node
410      * @return The IP address of the host that the bridge is on
411      */
412     private IpAddress getIpFromOvsdb(OvsdbBridgeAugmentation ovsdbBridge) {
413         /*
414          * The manager Node referenced by this node has the
415          * IP address.
416          */
417         OvsdbNodeAugmentation managerNode = getManagerNode(ovsdbBridge, dataBroker);
418
419         if (managerNode == null)
420             return null;
421
422         if (managerNode.getConnectionInfo() != null) {
423             return managerNode.getConnectionInfo().getRemoteIp();
424         }
425         return null;
426     }
427 }