Bug 3378 - ovsdb netvirt needs help in getting mac for a given ip in br-ex
[netvirt.git] / openstack / net-virt-providers / src / main / java / org / opendaylight / ovsdb / openstack / netvirt / providers / openflow13 / services / arp / GatewayMacResolverService.java
diff --git a/openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/arp/GatewayMacResolverService.java b/openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/arp/GatewayMacResolverService.java
new file mode 100644 (file)
index 0000000..095b287
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2015 Brocade Communications 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.ovsdb.openstack.netvirt.providers.openflow13.services.arp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.math.BigInteger;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
+import org.opendaylight.openflowplugin.api.OFConstants;
+import org.opendaylight.ovsdb.openstack.netvirt.api.GatewayMacResolver;
+import org.opendaylight.ovsdb.openstack.netvirt.providers.ConfigInterface;
+import org.opendaylight.ovsdb.openstack.netvirt.providers.NetvirtProvidersProvider;
+import org.opendaylight.ovsdb.openstack.netvirt.providers.openflow13.AbstractServiceInstance;
+import org.opendaylight.ovsdb.openstack.netvirt.providers.openflow13.Service;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActions;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatch;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.ArpMatch;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.JdkFutureAdapters;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ *
+ * @author Anil Vishnoi (avishnoi@Brocade.com)
+ *
+ */
+public class GatewayMacResolverService extends AbstractServiceInstance
+                                        implements ConfigInterface, GatewayMacResolver,PacketProcessingListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(GatewayMacResolverService.class);
+    private static final short TABEL_FOR_ARP_FLOW = 0;
+    private static final String ARP_REPLY_TO_CONTROLLER_FLOW_NAME = "GatewayArpReplyRouter";
+    private static final int ARP_REPLY_TO_CONTROLLER_FLOW_PRIORITY = 10000;
+    private static final Instruction SEND_TO_CONTROLLER_INSTRUCTION;
+    private ArpSender arpSender;
+    private SalFlowService flowService;
+    private final AtomicLong flowCookie = new AtomicLong();
+    private final ConcurrentHashMap<Ipv4Address, ArpResolverMetadata> arpRemoveFlowInputAndL3EpKeyById =
+            new ConcurrentHashMap<Ipv4Address, ArpResolverMetadata>();
+    private final int ARP_WATCH_BROTHERS = 10;
+    private final int WAIT_CYCLES = 3;
+    private final int PER_CYCLE_WAIT_DURATION = 1000;
+    private final ListeningExecutorService arpWatcherWall = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(ARP_WATCH_BROTHERS));
+    private AtomicBoolean initializationDone = new AtomicBoolean(false);
+
+    static {
+        ApplyActions applyActions = new ApplyActionsBuilder().setAction(
+                ImmutableList.of(ArpFlowFactory.createSendToControllerAction(0))).build();
+        SEND_TO_CONTROLLER_INSTRUCTION = new InstructionBuilder().setOrder(0)
+            .setInstruction(new ApplyActionsCaseBuilder().setApplyActions(applyActions).build())
+            .build();
+    }
+
+    public GatewayMacResolverService(){
+        super(Service.GATEWAY_RESOLVER);
+    }
+
+    public GatewayMacResolverService(Service service){
+        super(service);
+    }
+
+    private void init(){
+        if(!initializationDone.get()){
+            initializationDone.set(true);
+            ProviderContext providerContext = NetvirtProvidersProvider.getProviderContext();
+            checkNotNull(providerContext);
+            PacketProcessingService packetProcessingService = providerContext.getRpcService(PacketProcessingService.class);
+            if (packetProcessingService != null) {
+                LOG.debug("{} was found.", PacketProcessingService.class.getSimpleName());
+                this.arpSender = new ArpSender(packetProcessingService);
+            } else {
+                LOG.error("Missing service {}", PacketProcessingService.class.getSimpleName());
+                this.arpSender = null;
+            }
+            flowService = providerContext.getRpcService(SalFlowService.class);
+        }
+    }
+    /**
+     * Method do following actions:
+     * 1. Install flow to direct ARP response packet to controller
+     * 2. Send ARP request packet out on all port of the given External network bridge.
+     * 3. Cache the flow that need to be removed once ARP resolution is done.
+     * 4. Return listenable future so that user can add callback to get the MacAddress
+     * @param externalNetworkBridgeDpid Broadcast ARP request packet on this bridge
+     * @param gatewayIp IP address for which MAC need to be resolved
+     * @param sourceIpAddress Source Ip address for the ARP request packet
+     * @param sourceMacAddress Source Mac address for the ARP request packet
+     * @param periodicRefresh Enable/Disable periodic refresh of the Gateway Mac address
+     * NOTE:Periodic refresh is not supported yet.
+     * @param gatewayIp  Resolve MAC address of this Gateway Ip
+     * @return Future<MacAddress> Future object
+     */
+    @Override
+    public ListenableFuture<MacAddress> resolveMacAddress( final Long externalNetworkBridgeDpid, final Ipv4Address gatewayIp,
+            final Ipv4Address sourceIpAddress, final MacAddress sourceMacAddress, final Boolean periodicRefresh){
+        Preconditions.checkNotNull(sourceIpAddress);
+        Preconditions.checkNotNull(sourceMacAddress);
+        Preconditions.checkNotNull(gatewayIp);
+
+        LOG.info("Trigger Mac resolution for gateway {}, using source ip {} and mac {}",
+                gatewayIp.getValue(),sourceIpAddress.getValue(),sourceMacAddress.getValue());
+
+        init();
+        if(arpRemoveFlowInputAndL3EpKeyById.containsKey(gatewayIp)){
+            if(arpRemoveFlowInputAndL3EpKeyById.get(gatewayIp).getGatewayMacAddress() != null){
+                return arpWatcherWall.submit(new Callable<MacAddress>(){
+
+                    @Override
+                    public MacAddress call() throws Exception {
+                        return arpRemoveFlowInputAndL3EpKeyById.get(gatewayIp).getGatewayMacAddress();
+                    }
+                });
+            }
+        }else{
+            arpRemoveFlowInputAndL3EpKeyById.put(gatewayIp,
+                    new ArpResolverMetadata(null, gatewayIp,periodicRefresh));
+        }
+
+        final ArpMessageAddress senderAddress = new ArpMessageAddress(sourceMacAddress,sourceIpAddress);
+
+        final String nodeName = OPENFLOW + externalNetworkBridgeDpid;
+
+        final Node externalNetworkBridge = getOpenFlowNode(nodeName);
+        if(externalNetworkBridge == null){
+            LOG.error("MAC address for gateway {} can not be resolved, because external bridge {} "
+                    + "is not connected to controller.",gatewayIp.getValue(),externalNetworkBridgeDpid );
+            return null;
+        }
+        //Build arp reply router flow
+        final Flow arpReplyToControllerFlow = createArpReplyToControllerFlow(senderAddress, gatewayIp);
+
+
+        final InstanceIdentifier<Node> nodeIid = InstanceIdentifier.builder(Nodes.class)
+                .child(Node.class, externalNetworkBridge.getKey())
+                .build();
+        final InstanceIdentifier<Flow> flowIid = createFlowIid(arpReplyToControllerFlow, nodeIid);
+        final NodeRef nodeRef = new NodeRef(nodeIid);
+
+        //Install flow
+        Future<RpcResult<AddFlowOutput>> addFlowResult = flowService.addFlow(new AddFlowInputBuilder(
+                arpReplyToControllerFlow).setFlowRef(new FlowRef(flowIid)).setNode(nodeRef).build());
+        //wait for flow installation
+        Futures.addCallback(JdkFutureAdapters.listenInPoolThread(addFlowResult),
+                new FutureCallback<RpcResult<AddFlowOutput>>() {
+
+            @Override
+            public void onSuccess(RpcResult<AddFlowOutput> result) {
+                if (!result.isSuccessful()) {
+                    LOG.warn("Flow to route ARP Reply to Controller is not installed successfully : {} \nErrors: {}", flowIid,result.getErrors());
+                    return;
+                }
+                LOG.debug("Flow to route ARP Reply to Controller installed successfully : {}", flowIid);
+
+                //cache metadata
+                arpRemoveFlowInputAndL3EpKeyById.get(gatewayIp).setFlowToRemove(
+                        new RemoveFlowInputBuilder(arpReplyToControllerFlow).setNode(nodeRef).build());
+
+                //Broadcast ARP request packets
+                for (NodeConnector egressNc : externalNetworkBridge.getNodeConnector()) {
+                    KeyedInstanceIdentifier<NodeConnector, NodeConnectorKey> egressNcIid = nodeIid.child(
+                            NodeConnector.class, new NodeConnectorKey(egressNc.getId()));
+                    ListenableFuture<RpcResult<Void>> futureSendArpResult = arpSender.sendArp(
+                            senderAddress, gatewayIp, egressNcIid);
+                    Futures.addCallback(futureSendArpResult, logResult(gatewayIp, egressNcIid));
+                }
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                LOG.warn("ARP Reply to Controller flow was not created: {}", flowIid, t);
+            }
+            }
+        );
+        //Wait for MacAddress population in cache
+        return waitForMacAddress(gatewayIp);
+    }
+
+    private ListenableFuture<MacAddress> waitForMacAddress(final Ipv4Address gatewayIp){
+
+        return arpWatcherWall.submit(new Callable<MacAddress>(){
+
+            @Override
+            public MacAddress call() throws Exception {
+                for(int cycle = 0;cycle < WAIT_CYCLES;cycle++){
+                    //Sleep before checking mac address, so meanwhile ARP request packets
+                    // will be broadcasted on the bridge.
+                    Thread.sleep(PER_CYCLE_WAIT_DURATION);
+                    ArpResolverMetadata arpResolverMetadata = arpRemoveFlowInputAndL3EpKeyById.get(gatewayIp);
+                    if(arpResolverMetadata != null && arpResolverMetadata.getGatewayMacAddress() != null){
+                        if(!arpResolverMetadata.isPeriodicRefresh()){
+                            return arpRemoveFlowInputAndL3EpKeyById.remove(gatewayIp).getGatewayMacAddress();
+                        }
+                        return arpResolverMetadata.getGatewayMacAddress();
+                    }
+                }
+                return null;
+            }
+        });
+    }
+    private static @Nullable Ipv4Address getIPv4Addresses(IpAddress ipAddress) {
+        if (ipAddress.getIpv4Address() == null) {
+            return null;
+        }
+        return ipAddress.getIpv4Address();
+    }
+
+    private Flow createArpReplyToControllerFlow(final ArpMessageAddress senderAddress, final Ipv4Address ipForRequestedMac) {
+        checkNotNull(senderAddress);
+        checkNotNull(ipForRequestedMac);
+        FlowBuilder arpFlow = new FlowBuilder().setTableId(TABEL_FOR_ARP_FLOW)
+            .setFlowName(ARP_REPLY_TO_CONTROLLER_FLOW_NAME)
+            .setPriority(ARP_REPLY_TO_CONTROLLER_FLOW_PRIORITY)
+            .setBufferId(OFConstants.OFP_NO_BUFFER)
+            .setIdleTimeout(0)
+            .setHardTimeout(0)
+            .setCookie(new FlowCookie(BigInteger.valueOf(flowCookie.incrementAndGet())))
+            .setFlags(new FlowModFlags(false, false, false, false, false));
+
+        EthernetMatch ethernetMatch = ArpFlowFactory.createEthernetMatch(senderAddress.getHardwareAddress());
+        ArpMatch arpMatch = ArpFlowFactory.createArpMatch(senderAddress, ipForRequestedMac);
+        Match match = new MatchBuilder().setEthernetMatch(ethernetMatch).setLayer3Match(arpMatch).build();
+        arpFlow.setMatch(match);
+        arpFlow.setInstructions(new InstructionsBuilder().setInstruction(
+                ImmutableList.of(SEND_TO_CONTROLLER_INSTRUCTION)).build());
+        arpFlow.setId(createFlowId(senderAddress, ipForRequestedMac));
+        return arpFlow.build();
+    }
+
+    private FlowId createFlowId(ArpMessageAddress senderAddress, Ipv4Address ipForRequestedMac) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(ARP_REPLY_TO_CONTROLLER_FLOW_NAME);
+        sb.append("|").append(ipForRequestedMac.getValue());
+        return new FlowId(sb.toString());
+    }
+
+    private static InstanceIdentifier<Flow> createFlowIid(Flow flow, InstanceIdentifier<Node> nodeIid) {
+        return nodeIid.builder()
+            .augmentation(FlowCapableNode.class)
+            .child(Table.class, new TableKey(flow.getTableId()))
+            .child(Flow.class, new FlowKey(flow.getId()))
+            .build();
+    }
+
+    private FutureCallback<RpcResult<Void>> logResult(final Ipv4Address tpa,
+            final KeyedInstanceIdentifier<NodeConnector, NodeConnectorKey> egressNcIid) {
+        return new FutureCallback<RpcResult<Void>>() {
+
+            @Override
+            public void onSuccess(RpcResult<Void> result) {
+                LOG.debug("ARP Request for IP {} was sent from {}.", tpa.getValue(), egressNcIid);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                LOG.warn("ARP Request for IP {} was NOT sent from {}.", tpa.getValue(), egressNcIid);
+            }
+        };
+    }
+
+    @Override
+    public void onPacketReceived(PacketReceived potentialArp) {
+        Arp arp = null;
+        try {
+            arp = ArpResolverUtils.getArpFrom(potentialArp);
+        } catch (Exception e) {
+            LOG.trace(
+                    "Failed to decode potential ARP packet. This could occur when other than ARP packet was received.",
+                    e);
+            return;
+        }
+        if (arp.getOperation() != ArpOperation.REPLY.intValue()) {
+            LOG.trace("Packet is not ARP REPLY packet.");
+            return;
+        }
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("ARP REPLY received - {}", ArpUtils.getArpToStringFormat(arp));
+        }
+        NodeKey nodeKey = potentialArp.getIngress().getValue().firstKeyOf(Node.class, NodeKey.class);
+        if (nodeKey == null) {
+            LOG.info("Unknown source node of ARP packet: {}", potentialArp);
+            return;
+        }
+        Ipv4Address gatewayIpAddress = ArpUtils.bytesToIp(arp.getSenderProtocolAddress());
+        MacAddress gatewayMacAddress = ArpUtils.bytesToMac(arp.getSenderHardwareAddress());
+        ArpResolverMetadata removeFlowInputAndL3EpKey = arpRemoveFlowInputAndL3EpKeyById.get(gatewayIpAddress);
+        if(removeFlowInputAndL3EpKey != null){
+            removeFlowInputAndL3EpKey.setGatewayMacAddress(gatewayMacAddress);
+            flowService.removeFlow(removeFlowInputAndL3EpKey.getFlowToRemove());
+        }
+    }
+
+    @Override
+    public void setDependencies(BundleContext bundleContext,
+            ServiceReference serviceReference) {
+        super.setDependencies(bundleContext.getServiceReference(GatewayMacResolver.class.getName()), this);
+
+    }
+
+    @Override
+    public void setDependencies(Object impl) {}
+
+    @Override
+    public void stopPeriodicReferesh(Ipv4Address gatewayIp) {
+        init();
+        arpRemoveFlowInputAndL3EpKeyById.remove(gatewayIp);
+    }
+
+}