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