/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.groupbasedpolicy.neutron.ovsdb; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.annotation.Nonnull; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.InventoryHelper; import org.opendaylight.groupbasedpolicy.neutron.ovsdb.util.NeutronOvsdbIidFactory; import org.opendaylight.groupbasedpolicy.util.DataTreeChangeHandler; import org.opendaylight.ovsdb.southbound.SouthboundConstants; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.ovsdb.params.rev160812.IntegrationBridgeSetting; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.DatapathTypeSystem; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentationBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeName; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeProtocolOpenflow13; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbTerminationPointAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.bridge.attributes.ControllerEntry; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.bridge.attributes.ControllerEntryBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.bridge.attributes.ProtocolEntryBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ManagedNodeEntry; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ManagerEntry; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.OpenvswitchOtherConfigs; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.OpenvswitchOtherConfigsKey; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.node.TerminationPoint; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OvsdbNodeListener extends DataTreeChangeHandler { static final String BRIDGE_SEPARATOR = "/bridge/"; static final String NEUTRON_PROVIDER_MAPPINGS_KEY = "provider_mappings"; private static final Logger LOG = LoggerFactory.getLogger(OvsdbNodeListener.class); private static final String OF_SEPARATOR = ":"; private static final String OF_INVENTORY_PREFIX = "openflow"; private static IntegrationBridgeSetting intBrSettings; private final Map providerPortNameByBridgeRef = new HashMap<>(); private final Map, NeutronBridgeWithExtPort> bridgeByNodeIid = new HashMap<>(); public OvsdbNodeListener(DataBroker dataProvider, IntegrationBridgeSetting brSettings) { super(dataProvider); intBrSettings = brSettings; this.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.create(NetworkTopology.class) .child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID)) .child(Node.class))); } @Override protected void onWrite(DataObjectModification rootNode, InstanceIdentifier rootIdentifier) { Node node = rootNode.getDataAfter(); OvsdbNodeAugmentation ovsdbNode = node.getAugmentation(OvsdbNodeAugmentation.class); if (ovsdbNode != null) { LOG.trace("OVSDB node created: {} \n {}", rootIdentifier, node); DataObjectModification ovsOtherConfigModification = getProviderMappingsModification(rootNode); boolean integrationBridgePresent = false; if (isProviderPortNameChanged(ovsOtherConfigModification) && ovsdbNode.getManagedNodeEntry() != null) { String newProviderPortName = getProviderPortName(ovsOtherConfigModification.getDataAfter()); LOG.debug("provider_mappings created {} on node {}", newProviderPortName, node.getNodeId().getValue()); for (ManagedNodeEntry mngdNodeEntry : ovsdbNode.getManagedNodeEntry()) { OvsdbBridgeRef bridgeRef = mngdNodeEntry.getBridgeRef(); providerPortNameByBridgeRef.put(bridgeRef, newProviderPortName); LOG.trace("Added Provider port name {} by OVSDB bridge ref {}", newProviderPortName, mngdNodeEntry.getBridgeRef()); NodeKey managedNodeKey = bridgeRef.getValue().firstKeyOf(Node.class); if (intBrSettings != null && managedNodeKey.getNodeId().getValue() .equals(intBrSettings.getName())) { integrationBridgePresent = true; } } } if (intBrSettings != null && !integrationBridgePresent) { final Node bridge = createBridge(rootIdentifier, managerToControllerEntries(ovsdbNode.getManagerEntry()), intBrSettings.getName()); InstanceIdentifier bridgeNodeIid = NeutronOvsdbIidFactory.nodeIid( rootIdentifier.firstKeyOf(Topology.class).getTopologyId(), bridge.getNodeId()); WriteTransaction writeTx = dataProvider.newWriteOnlyTransaction(); writeTx.merge(LogicalDatastoreType.CONFIGURATION, bridgeNodeIid, bridge, true); Futures.addCallback(writeTx.submit(), new FutureCallback() { @Override public void onSuccess(Void result) { LOG.info("Bridge {} written to datastore." + bridge.getNodeId().getValue()); } @Override public void onFailure(Throwable throwable) { LOG.error("Failed to write bridge {}. Message: {}" + bridge.getNodeId().getValue(), throwable.getMessage()); } }, MoreExecutors.directExecutor()); } } OvsdbBridgeAugmentation ovsdbBridge = node.getAugmentation(OvsdbBridgeAugmentation.class); if (ovsdbBridge != null) { LOG.trace("OVSDB bridge created: {} \n {}", rootIdentifier, node); Set> ovsdbTpModifications = getOvsdbTpModifications(rootNode); NodeId ofNodeId = buildOfNodeId(ovsdbBridge); if (!ovsdbTpModifications.isEmpty() && ofNodeId != null) { NeutronBridgeWithExtPort bridge = getBridge(rootIdentifier); bridge.ofNodeId = ofNodeId; LOG.trace("OF node {} representing OVSDB bridge {}", ofNodeId.getValue(), node.getNodeId().getValue()); } for (DataObjectModification ovsdbTpModification : ovsdbTpModifications) { OvsdbTerminationPointAugmentation newOvsdbTp = ovsdbTpModification.getDataAfter(); if (ovsdbBridge.getBridgeName().getValue().equals(newOvsdbTp.getName())) { LOG.trace("Termination Point {} same as Bridge {}. Not processing", newOvsdbTp.getName(), ovsdbBridge.getBridgeName().getValue()); continue; } String portName = newOvsdbTp.getName(); Long ofport = newOvsdbTp.getOfport(); if (isOfportOrNameChanged(ovsdbTpModification) && portName != null && ofport != null) { NeutronBridgeWithExtPort bridge = getBridge(rootIdentifier); bridge.ofportByName.put(ofport, portName); LOG.trace("OVSDB termination point with ofport {} and port-name {} created.", ofport, portName); // port name is same as provider port name so the termination point represents // external port if (portName.equals(providerPortNameByBridgeRef.get(new OvsdbBridgeRef(rootIdentifier)))) { NodeConnectorId ofNcId = buildOfNodeConnectorId(newOvsdbTp, ofNodeId); bridge.externalIfaces.add(ofNcId); InventoryHelper.addOfOverlayExternalPort(bridge.ofNodeId, ofNcId, dataProvider); LOG.debug("Added of-overlay external-interface {} to node {}", ofNcId.getValue(), bridge.ofNodeId); traceBridge(rootIdentifier); } } } } } @Override protected void onDelete(DataObjectModification rootNode, InstanceIdentifier rootIdentifier) { LOG.trace("Not implemented - OVSDB element deleted: {} \n {}", rootIdentifier, rootNode.getDataBefore()); } @Override protected void onSubtreeModified(DataObjectModification rootNode, InstanceIdentifier rootIdentifier) { Node node = rootNode.getDataAfter(); OvsdbBridgeAugmentation ovsdbBridge = node.getAugmentation(OvsdbBridgeAugmentation.class); if (ovsdbBridge != null) { LOG.trace("OVSDB bridge updated: {} \n before {} \n after {}", rootIdentifier, rootNode.getDataBefore(), rootNode.getDataAfter()); Set> ovsdbTpModifications = getOvsdbTpModifications(rootNode); NodeId ofNodeId = buildOfNodeId(ovsdbBridge); if (!ovsdbTpModifications.isEmpty() && ofNodeId != null) { NeutronBridgeWithExtPort bridge = getBridge(rootIdentifier); if (bridge.ofNodeId != null && !bridge.ofNodeId.equals(ofNodeId)) { LOG.debug("OVSDB bridge {} has changed datapath-id. \n Old: {} \n New: {}", node.getNodeId().getValue(), bridge.ofNodeId.getValue(), ofNodeId.getValue()); bridge.ofNodeId = ofNodeId; } } for (DataObjectModification ovsdbTpModification : ovsdbTpModifications) { OvsdbTerminationPointAugmentation newOvsdbTp = ovsdbTpModification.getDataAfter(); if (newOvsdbTp == null) { LOG.trace("Termination Point is null. Not processing"); continue; } if (ovsdbBridge.getBridgeName().getValue().equals(newOvsdbTp.getName())) { LOG.trace("Termination Point {} same as Bridge {}. Not processing", newOvsdbTp.getName(), ovsdbBridge.getBridgeName().getValue()); continue; } String portName = newOvsdbTp.getName(); Long ofport = newOvsdbTp.getOfport(); if (isOfportOrNameChanged(ovsdbTpModification) && portName != null && ofport != null) { NeutronBridgeWithExtPort bridge = getBridge(rootIdentifier); bridge.ofportByName.put(ofport, portName); LOG.trace("OVSDB termination point with ofport {} and port-name {} created.", ofport, portName); // port name is same as provider port name so the termination point represents // external port if (portName.equals(providerPortNameByBridgeRef.get(new OvsdbBridgeRef(rootIdentifier)))) { NodeConnectorId ofNcId = buildOfNodeConnectorId(newOvsdbTp, ofNodeId); bridge.externalIfaces.add(ofNcId); InventoryHelper.addOfOverlayExternalPort(bridge.ofNodeId, ofNcId, dataProvider); LOG.debug("Added of-overlay external-interface {} to node {}", ofNcId.getValue(), bridge.ofNodeId); traceBridge(rootIdentifier); } } } } } private NeutronBridgeWithExtPort getBridge(InstanceIdentifier nodeIid) { NeutronBridgeWithExtPort bridge = bridgeByNodeIid.get(nodeIid); if (bridge == null) { bridge = new NeutronBridgeWithExtPort(); bridgeByNodeIid.put(nodeIid, bridge); } return bridge; } @SuppressWarnings("unchecked") private static Set> getOvsdbTpModifications( DataObjectModification rootNode) { Set> modifications = new HashSet<>(); for (DataObjectModification modifiedChild : rootNode.getModifiedChildren()) { if (TerminationPoint.class.isAssignableFrom(modifiedChild.getDataType())) { DataObjectModification modifiedAugmentation = ((DataObjectModification) modifiedChild) .getModifiedAugmentation(OvsdbTerminationPointAugmentation.class); if (modifiedAugmentation != null) { modifications.add(modifiedAugmentation); } } } return modifications; } private static boolean isOfportOrNameChanged( DataObjectModification ovsdbTpModification) { if (ovsdbTpModification == null) { return false; } OvsdbTerminationPointAugmentation oldTp = ovsdbTpModification.getDataBefore(); OvsdbTerminationPointAugmentation newTp = ovsdbTpModification.getDataAfter(); if (oldTp != null && newTp != null) { if (oldTp.getOfport() != null && newTp.getOfport() != null && !Objects.equals(oldTp.getOfport(), newTp.getOfport())) { return true; } if (!(Strings.nullToEmpty(oldTp.getName())).equals(Strings.nullToEmpty(newTp.getName()))) { return true; } } if (isOfportOrNameNotNull(oldTp)) { return true; } if (isOfportOrNameNotNull(newTp)) { return true; } return false; } private static boolean isOfportOrNameNotNull(OvsdbTerminationPointAugmentation tp) { if (tp != null) { if (tp.getOfport() != null) { return true; } if (tp.getName() != null) { return true; } } return false; } private static DataObjectModification getProviderMappingsModification( DataObjectModification rootNode) { DataObjectModification modifiedOvsdbNode = rootNode.getModifiedAugmentation(OvsdbNodeAugmentation.class); if (modifiedOvsdbNode == null) { return null; } return modifiedOvsdbNode.getModifiedChildListItem(OpenvswitchOtherConfigs.class, new OpenvswitchOtherConfigsKey(NEUTRON_PROVIDER_MAPPINGS_KEY)); } private static boolean isProviderPortNameChanged(DataObjectModification ovsConfig) { if (ovsConfig == null) { return false; } OpenvswitchOtherConfigs oldConfig = ovsConfig.getDataBefore(); OpenvswitchOtherConfigs newConfig = ovsConfig.getDataAfter(); if (oldConfig != null && newConfig != null) { if (!(Strings.nullToEmpty(oldConfig.getOtherConfigValue()) .equals(Strings.nullToEmpty(newConfig.getOtherConfigValue())))) { return true; } } else if (oldConfig != null && !Strings.isNullOrEmpty(oldConfig.getOtherConfigValue())) { return true; } else if (newConfig != null && !Strings.isNullOrEmpty(newConfig.getOtherConfigValue())) { return true; } return false; } private static @Nonnull String getProviderPortName(OpenvswitchOtherConfigs config) { if (NEUTRON_PROVIDER_MAPPINGS_KEY.equals(config.getOtherConfigKey()) && config.getOtherConfigValue() != null) { String otherConfig = config.getOtherConfigValue(); String[] elements = otherConfig.split(":"); if (elements.length == 2) { return elements[1]; } } return ""; } /** * Extracts IP address from URI. * * @param uri in format protocol:ip:port * @return IPv4 or IPv6 address as {@link String}. */ private static @Nonnull String getIpAddrFromUri(Uri uri) { String otherConfig = uri.getValue(); String[] elements = otherConfig.split(":"); // IPv6 expression also contains colons if (elements.length < 3) { return ""; } StringBuilder sb = new StringBuilder(); // first (protocol) and last (port) elements are filtered for (int i = 1; i < elements.length - 1; i++) { sb.append(elements[i]); } return sb.toString(); } private List managerToControllerEntries(List managerEntries) { return Lists.transform(managerEntries, new Function() { @Override public ControllerEntry apply(ManagerEntry managerEntry) { String ipAddr = getIpAddrFromUri(managerEntry.getTarget()); Uri uri = new Uri(intBrSettings.getOpenflowProtocol() + OF_SEPARATOR + ipAddr + OF_SEPARATOR + intBrSettings.getOpenflowPort()); return new ControllerEntryBuilder().setTarget(new Uri(uri)).build(); } }); } private Node createBridge(InstanceIdentifier managedByIid, List controllerEntries, String bridgeName) { OvsdbBridgeAugmentation br = new OvsdbBridgeAugmentationBuilder() .setBridgeName(new OvsdbBridgeName(bridgeName)) .setManagedBy(new OvsdbNodeRef(managedByIid)) .setControllerEntry(controllerEntries) .setDatapathType(DatapathTypeSystem.class) .setProtocolEntry( ImmutableList.of(new ProtocolEntryBuilder().setProtocol( OvsdbBridgeProtocolOpenflow13.class).build())) .build(); NodeKey managerNodeKey = managedByIid.firstKeyOf(Node.class); return new NodeBuilder().setNodeId( new org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId( managerNodeKey.getNodeId().getValue() + BRIDGE_SEPARATOR + bridgeName)) .addAugmentation(OvsdbBridgeAugmentation.class, br) .build(); } private static NodeId buildOfNodeId(OvsdbBridgeAugmentation ovsdbBridge) { if (ovsdbBridge.getDatapathId() == null) { return null; } Long macLong = InventoryHelper.getLongFromDpid(ovsdbBridge.getDatapathId().getValue()); return new NodeId(OF_INVENTORY_PREFIX + OF_SEPARATOR + String.valueOf(macLong)); } private static NodeConnectorId buildOfNodeConnectorId(OvsdbTerminationPointAugmentation terminationPoint, NodeId nodeId) { if (terminationPoint.getOfport() == null) { return null; } return new NodeConnectorId(nodeId.getValue() + OF_SEPARATOR + String.valueOf(terminationPoint.getOfport())); } private void traceBridge(InstanceIdentifier identifier) { if (LOG.isTraceEnabled()) { NeutronBridgeWithExtPort bridge = bridgeByNodeIid.get(identifier); if (bridge == null) { LOG.trace("Bridge does not exist: {}", identifier); return; } String providerPortName = providerPortNameByBridgeRef.get(new OvsdbBridgeRef(identifier)); LOG.trace("State of bridge:\n ID: {} \n providerPortName: {} \n {}", identifier, providerPortName, bridge); } } private class NeutronBridgeWithExtPort { NodeId ofNodeId; Set externalIfaces = new HashSet<>(); Map ofportByName = new HashMap<>(); @Override public String toString() { return "NeutronBridgeWithExtPort:\n ofNodeId=" + ofNodeId + "\n externalIfaces=" + externalIfaces + ",\n ofportByName=" + ofportByName; } } }