/* * Copyright (c) 2016 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.vpp.mapper.processors; import java.util.Collections; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.TransactionChain; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; import org.opendaylight.groupbasedpolicy.util.DataStoreHelper; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.UniqueId; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.Mappings; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.GbpByNeutronMappings; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.BaseEndpointsByPorts; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPort; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.neutron.gbp.mapper.rev150513.mappings.gbp.by.neutron.mappings.base.endpoints.by.ports.BaseEndpointByPortKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.Config; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.ExcludeFromPolicy; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.ExcludeFromPolicyBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.LoopbackCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCase; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.TapCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425._interface.attributes._interface.type.choice.VhostUserCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpoint; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.vpp_renderer.rev160425.config.VppEndpointKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.PortBindingExtension; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.binding.rev150712.binding.attributes.VifDetails; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.Routers; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.Router; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.l3.rev150712.routers.attributes.routers.RouterKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.port.attributes.FixedIps; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.Ports; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.PortKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.Subnets; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.Subnet; import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.subnets.rev150712.subnets.attributes.subnets.SubnetKey; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.InstanceIdentifierBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Strings; public class PortHandler implements TransactionChainListener { private static final Logger LOG = LoggerFactory.getLogger(PortHandler.class); private static final String COMPUTE_OWNER = "compute"; private static final String DHCP_OWNER = "dhcp"; static final String ROUTER_OWNER = "network:router_interface"; private static final String[] SUPPORTED_DEVICE_OWNERS = {COMPUTE_OWNER, DHCP_OWNER, ROUTER_OWNER}; private static final String VHOST_USER = "vhostuser"; private static final String UNBOUND = "unbound"; private static final String VPP_INTERFACE_NAME_PREFIX = "neutron_port_"; private static final String TAP_PORT_NAME_PREFIX = "tap"; private static final String RT_PORT_NAME_PREFIX = "qr-"; private static final String VHOST_SOCKET_KEY = "vhostuser_socket"; static final String DEFAULT_NODE = "default"; private final NodeId routingNode; private BindingTransactionChain transactionChain; private DataBroker dataBroker; PortHandler(DataBroker dataBroker, NodeId routingNodeId) { this.dataBroker = dataBroker; this.routingNode = routingNodeId; transactionChain = this.dataBroker.createTransactionChain(this); } void processCreated(Port port) { ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); Optional<BaseEndpointByPort> optBaseEpByPort = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL, createBaseEpByPortIid(port.getUuid()), rTx); rTx.close(); if (!optBaseEpByPort.isPresent()) { return; } processCreatedData(port, optBaseEpByPort.get()); } void processCreated(BaseEndpointByPort bebp) { ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); Optional<Port> optPort = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, createPortIid(bebp.getPortId()), rTx); rTx.close(); if (!optPort.isPresent()) { return; } processCreatedData(optPort.get(), bebp); } @VisibleForTesting void processCreatedData(Port port, BaseEndpointByPort bebp) { if (isValidVhostUser(port) // this is a hack for vpp router port // Openstack does not send binding details yet || isValidVppRouterPort(port)) { VppEndpoint vppEp = buildVppEndpoint(port, bebp); if (vppEp == null) { LOG.warn("Cannot create vpp-endpoint from neutron port {}", port); return; } writeVppEndpoint(createVppEndpointIid(vppEp.getKey()), vppEp); LOG.debug("Created vpp-endpoint {}", vppEp); } } private boolean isValidVhostUser(Port port) { PortBindingExtension portBindingExt = port.getAugmentation(PortBindingExtension.class); if (portBindingExt != null) { String vifType = portBindingExt.getVifType(); String deviceOwner = port.getDeviceOwner(); if (vifType != null && deviceOwner != null) { if (vifType.contains(VHOST_USER)) { for (String supportedDeviceOwner : SUPPORTED_DEVICE_OWNERS) { if (deviceOwner.contains(supportedDeviceOwner)) { return true; } } } } } return false; } void processUpdated(Port original, Port delta) { if (!isUpdateNeeded(original, delta)){ LOG.trace("Port update skipped, port didn`t change. before {}, after: {}" , original, delta); return; } LOG.trace("Updating port before: {}, after: {}" , original, delta); if (isValidVhostUser(original)) { ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); Optional<BaseEndpointByPort> optBebp = DataStoreHelper.readFromDs(LogicalDatastoreType.OPERATIONAL, createBaseEpByPortIid(original.getUuid()), rTx); rTx.close(); if (!optBebp.isPresent()) { return; } LOG.trace("Updating port - deleting old port {}" , optBebp.get().getPortId()); processDeleted(optBebp.get()); } LOG.trace("Updating port - creating new port {}" , delta.getUuid()); processCreated(delta); } private boolean isUpdateNeeded(final Port oldPort, final Port newPort) { //TODO fix this to better support update of ports for VPP final PortBindingExtension oldPortAugmentation = oldPort.getAugmentation(PortBindingExtension.class); final PortBindingExtension newPortAugmentation = newPort.getAugmentation(PortBindingExtension.class); if (newPortAugmentation == null) { LOG.trace("Port {} is no longer a vhost type port, updating port..."); return true; } final String oldDeviceOwner = oldPort.getDeviceOwner(); final String oldVifType = oldPortAugmentation.getVifType(); final String newDeviceOwner = newPort.getDeviceOwner(); final String newVifType = newPortAugmentation.getVifType(); // TODO potential bug here // Temporary change for Openstack Mitaka: If old neutron-binding:vif-type is vhost, new one is unbound and // device owner is ROUTER_OWNER, skip update. Openstack (or ml2) sometimes sends router update messages in // incorrect order which causes unwanted port removal if (oldVifType.equals(VHOST_USER) && newVifType.equals(UNBOUND) && oldDeviceOwner != null && ROUTER_OWNER.equals(oldDeviceOwner) && ROUTER_OWNER.equals(newDeviceOwner)) { LOG.warn("Port vif-type was updated from vhost to unbound. This update is currently disabled and will be skipped"); return false; } if (newVifType != null && !newVifType.equals(oldVifType)) { LOG.trace("Vif type changed, old: {} new {}", oldVifType, newVifType); return true; } final List<VifDetails> vifDetails = oldPortAugmentation.getVifDetails(); if (!oldPortAugmentation.getHostId().equals(newPortAugmentation.getHostId()) || nullToEmpty(vifDetails).size() != nullToEmpty(newPortAugmentation.getVifDetails()).size()) { return true; } for (VifDetails vifDetail : nullToEmpty(vifDetails)) { //check if vhostuser_socket, vhostuser_mode and port_filter are changed if (!newPortAugmentation.getVifDetails().contains(vifDetail)) return true; } return false; } void processDeleted(BaseEndpointByPort bebp) { LOG.trace("Deleting vpp-endpoint by BaseEndpointByPort {}" , bebp); VppEndpointKey vppEpKey = new VppEndpointKey(bebp.getAddress(), bebp.getAddressType(), bebp.getContextId(), bebp.getContextType()); InstanceIdentifier<VppEndpoint> vppEpIid = createVppEndpointIid(vppEpKey); ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); Optional<VppEndpoint> readVppEp = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, vppEpIid, rTx); rTx.close(); if (readVppEp.isPresent()) { writeVppEndpoint(vppEpIid, null); LOG.debug("Deleted vpp-endpoint {}", vppEpKey); } } private synchronized void writeVppEndpoint(InstanceIdentifier<VppEndpoint> vppEpIid, VppEndpoint vppEp) { WriteTransaction wTx = transactionChain.newWriteOnlyTransaction(); if (vppEp != null) { wTx.put(LogicalDatastoreType.CONFIGURATION, vppEpIid, vppEp, true); } else { wTx.delete(LogicalDatastoreType.CONFIGURATION, vppEpIid); } wTx.submit(); } @VisibleForTesting VppEndpoint buildVppEndpoint(Port port, BaseEndpointByPort bebp) { PortBindingExtension portBinding = port.getAugmentation(PortBindingExtension.class); VppEndpointBuilder vppEpBuilder = new VppEndpointBuilder().setDescription("neutron port") .setContextId(bebp.getContextId()) .setContextType(bebp.getContextType()) .setAddress(bebp.getAddress()) .setAddressType(bebp.getAddressType()) .setVppInterfaceName(VPP_INTERFACE_NAME_PREFIX + bebp.getPortId().getValue()) .setVppNodeId(new NodeId(portBinding.getHostId())); if (port.getDeviceOwner().contains(COMPUTE_OWNER)) { vppEpBuilder.setInterfaceTypeChoice( new VhostUserCaseBuilder().setSocket(getSocketFromPortBinding(portBinding)).build()); } else if (port.getDeviceOwner().contains(DHCP_OWNER) && port.getMacAddress() != null) { TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue())) .setName(createPortName(port.getUuid())) .build(); vppEpBuilder.setInterfaceTypeChoice(tapCase); } else if (isValidQRouterPort(port)) { TapCase tapCase = new TapCaseBuilder().setPhysicalAddress(new PhysAddress(port.getMacAddress().getValue())) .setName(createQRouterPortName(port.getUuid())) .build(); vppEpBuilder.setInterfaceTypeChoice(tapCase); vppEpBuilder.addAugmentation(ExcludeFromPolicy.class, new ExcludeFromPolicyBuilder().setExcludeFromPolicy(true).build()); } else if (isValidVppRouterPort(port)) { if (!DEFAULT_NODE.equals(routingNode.getValue())) { LOG.warn( "Host-id changed by ODL for port {}. This is a supplementary workaround for choosing a routing node.", port); vppEpBuilder.setVppNodeId(routingNode); } else if (port.getDeviceId() != null) { LOG.debug("Resolving host-id for unbound router port {}", port.getUuid()); ReadOnlyTransaction readTx = dataBroker.newReadOnlyTransaction(); Optional<Ports> optPorts = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.builder(Neutron.class).child(Ports.class).build(), readTx); readTx.close(); if (optPorts.isPresent() && optPorts.get().getPort() != null) { java.util.Optional<Port> optPortOnTheSameNode = optPorts.get() .getPort() .stream() .filter(p -> !p.getUuid().equals(port.getUuid())) .filter(p -> p.getAugmentation(PortBindingExtension.class) != null) .filter(p -> p.getDeviceOwner().contains(DHCP_OWNER)) .findFirst(); if (optPortOnTheSameNode.isPresent()) { PortBindingExtension binding = optPortOnTheSameNode.get().getAugmentation(PortBindingExtension.class); if (binding != null && binding.getHostId() != null) { vppEpBuilder.setVppNodeId(new NodeId(binding.getHostId())); } else { LOG.warn("Cannot resolve location of router-port {}", port.getUuid()); return null; } } } } vppEpBuilder.addAugmentation(ExcludeFromPolicy.class, new ExcludeFromPolicyBuilder().setExcludeFromPolicy(true).build()); vppEpBuilder.setInterfaceTypeChoice(getLoopbackCase(port)); } return vppEpBuilder.build(); } private String getSocketFromPortBinding(@Nonnull PortBindingExtension portBindingExtension) { List<VifDetails> vifDetails = nullToEmpty(portBindingExtension.getVifDetails()); for (VifDetails detail : vifDetails) { if (VHOST_SOCKET_KEY.equalsIgnoreCase(detail.getDetailsKey())) { return detail.getValue(); } } return null; } private LoopbackCase getLoopbackCase(Port port) { LoopbackCaseBuilder loopbackCase = new LoopbackCaseBuilder() .setPhysAddress(new PhysAddress(port.getMacAddress().getValue())); Optional<FixedIps> fixedIpsOptional = resolveFirstFixedIps(port); if(fixedIpsOptional.isPresent() && fixedIpsOptional.get().getIpAddress() != null){ loopbackCase.setIpAddress(fixedIpsOptional.get().getIpAddress()); ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); Optional<Subnet> subnetOptional = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.builder(Neutron.class) .child(Subnets.class) .child(Subnet.class, new SubnetKey(fixedIpsOptional.get().getSubnetId())) .build(), rTx); if (subnetOptional.isPresent()) { Ipv4Prefix ipv4Prefix = subnetOptional.get().getCidr().getIpv4Prefix(); loopbackCase.setIpPrefix(new IpPrefix(ipv4Prefix)); } else { LOG.warn("IpPrefix for loopback port: {} was not set.", port); } if (loopbackCase.getIpAddress() != null && loopbackCase.getIpPrefix() != null) { loopbackCase.setBvi(true); LOG.trace("Creating loopback BVI interface: {} for VPP router port: {}.", loopbackCase, port); } } else { LOG.warn("IpAddress for loopback port: {} was not set.", port); } return loopbackCase.build(); } /** * If Qrouter (L3 Agent) is in use, any of Openstack neutron routers is not going be mapped * to ODL neutron. */ private boolean isValidQRouterPort(Port port) { Optional<Router> optRouter = getRouterOptional(port); return !optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER) && port.getMacAddress() != null; } private boolean isValidVppRouterPort(Port port) { Optional<Router> optRouter = getRouterOptional(port); return optRouter.isPresent() && port.getDeviceOwner().contains(ROUTER_OWNER) && port.getMacAddress() != null; } private Optional<Router> getRouterOptional(Port port) { if (Strings.isNullOrEmpty(port.getDeviceId())) { return Optional.absent(); } RouterKey routerKey = null; try { routerKey = new RouterKey(new Uuid(port.getDeviceId())); } catch (IllegalArgumentException e) { // port.getDeviceId() may not match Uuid.PATTERN_CONSTANTS return Optional.absent(); } ReadOnlyTransaction rTx = transactionChain.newReadOnlyTransaction(); InstanceIdentifier<Router> routerIid = InstanceIdentifier.builder(Neutron.class) .child(Routers.class) .child(Router.class, routerKey) .build(); Optional<Router> optRouter = DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, routerIid, rTx); rTx.close(); return optRouter; } public static Optional<FixedIps> resolveFirstFixedIps(Port port) { List<FixedIps> fixedIps = port.getFixedIps(); if (fixedIps != null && !fixedIps.isEmpty()) { return Optional.of(fixedIps.get(0)); } return Optional.absent(); } private String createPortName(Uuid portUuid) { String tapPortName; String uuid = portUuid.getValue(); if (uuid != null && uuid.length() >= 12) { tapPortName = TAP_PORT_NAME_PREFIX + uuid.substring(0, 11); } else { tapPortName = TAP_PORT_NAME_PREFIX + uuid; } return tapPortName; } private String createQRouterPortName(Uuid portUuid) { String tapPortName; String uuid = portUuid.getValue(); if (uuid != null && uuid.length() >= 12) { tapPortName = RT_PORT_NAME_PREFIX + uuid.substring(0, 11); } else { tapPortName = RT_PORT_NAME_PREFIX + uuid; } return tapPortName; } private InstanceIdentifier<VppEndpoint> createVppEndpointIid(VppEndpointKey vppEpKey) { return InstanceIdentifier.builder(Config.class).child(VppEndpoint.class, vppEpKey).build(); } private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(Uuid uuid) { return createBaseEpByPortIid(new UniqueId(uuid.getValue())); } private InstanceIdentifier<BaseEndpointByPort> createBaseEpByPortIid(UniqueId uuid) { return InstanceIdentifier.builder(Mappings.class) .child(GbpByNeutronMappings.class) .child(BaseEndpointsByPorts.class) .child(BaseEndpointByPort.class, new BaseEndpointByPortKey(uuid)) .build(); } InstanceIdentifier<Port> createWildcartedPortIid() { return portsIid().child(Port.class).build(); } private InstanceIdentifier<Port> createPortIid(UniqueId uuid) { return portsIid().child(Port.class, new PortKey(new Uuid(uuid.getValue()))).build(); } private InstanceIdentifierBuilder<Ports> portsIid() { return InstanceIdentifier.builder(Neutron.class).child(Ports.class); } @Override public void onTransactionChainFailed(TransactionChain<?, ?> chain, AsyncTransaction<?, ?> transaction, Throwable cause) { LOG.error("Transaction chain failed. {} \nTransaction which caused the chain to fail {}", cause.getMessage(), transaction, cause); transactionChain.close(); transactionChain = dataBroker.createTransactionChain(this); } @Override public void onTransactionChainSuccessful(TransactionChain<?, ?> chain) { LOG.trace("Transaction chain was successful. {}", chain); } private <T> List<T> nullToEmpty(@Nullable List<T> list) { return list == null ? Collections.emptyList() : list; } }