Refactor: split api from SONA simple fabric

Change-Id: Icbdb10b730af29057097b14df8ddc377f4107853
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
new file mode 100644
index 0000000..45a9fdd
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.ResourceGroup;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.SimpleFabricEvent;
+import org.onosproject.simplefabric.api.SimpleFabricListener;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * An implementation of L2NetworkOperationService.
+ * Handles the execution order of the L2 Network operations generated by the
+ * application.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricL2Forward {
+
+    public static final String BROADCAST = "BCAST";
+    public static final String UNICAST = "UNI";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    protected ApplicationId l2ForwardAppId;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SimpleFabricService simpleFabric;
+
+    public static final ImmutableList<Constraint> L2NETWORK_CONSTRAINTS =
+            ImmutableList.of(new PartialFailureConstraint());
+
+    private Map<Key, SinglePointToMultiPointIntent> bctIntentsMap = Maps.newConcurrentMap();
+    private Map<Key, MultiPointToSinglePointIntent> uniIntentsMap = Maps.newConcurrentMap();
+    private Set<Key> toBePurgedIntentKeys = new HashSet<>();
+
+    private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
+
+    @Activate
+    public void activate() {
+        l2ForwardAppId = coreService.registerApplication(simpleFabric.L2FORWARD_APP_ID);
+        log.info("simple fabric l2 forwaring starting with l2net app id {}", l2ForwardAppId.toString());
+
+        simpleFabric.addListener(simpleFabricListener);
+
+        refresh();
+        checkIntentsPurge();
+
+        log.info("simple fabric l2forward started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("simple fabric l2forward stopping");
+
+        simpleFabric.removeListener(simpleFabricListener);
+
+        for (Intent intent : bctIntentsMap.values()) {
+            intentService.withdraw(intent);
+            toBePurgedIntentKeys.add(intent.key());
+        }
+        for (Intent intent : uniIntentsMap.values()) {
+            intentService.withdraw(intent);
+            toBePurgedIntentKeys.add(intent.key());
+        }
+        for (Key key : toBePurgedIntentKeys) {
+            Intent intentToPurge = intentService.getIntent(key);
+            if (intentToPurge != null) {
+                intentService.purge(intentToPurge);
+            }
+        }
+
+        // do not set clear for switch compatibility
+        //bctIntentsMap.clear();
+        //uniIntentsMap.clear();
+
+        log.info("simple fabric l2forward stopped");
+    }
+
+    private void refresh() {
+        log.debug("simple fabric l2forward refresh");
+
+        Map<Key, SinglePointToMultiPointIntent> newBctIntentsMap = Maps.newConcurrentMap();
+        Map<Key, MultiPointToSinglePointIntent> newUniIntentsMap = Maps.newConcurrentMap();
+
+        for (L2Network l2Network : simpleFabric.getL2Networks()) {
+            // scans all l2network regardless of dirty flag
+            // if l2Network.l2Forward == false or number of interfaces() < 2, no Intents generated
+            for (SinglePointToMultiPointIntent intent : buildBrcIntents(l2Network)) {
+                newBctIntentsMap.put(intent.key(), intent);
+            }
+            for (MultiPointToSinglePointIntent intent : buildUniIntents(l2Network, hostsFromL2Network(l2Network))) {
+                newUniIntentsMap.put(intent.key(), intent);
+            }
+            if (l2Network.dirty()) {
+                l2Network.setDirty(false);
+            }
+        }
+
+        boolean bctUpdated = false;
+        for (SinglePointToMultiPointIntent intent : bctIntentsMap.values()) {
+            SinglePointToMultiPointIntent newIntent = newBctIntentsMap.get(intent.key());
+            if (newIntent == null) {
+                log.info("simple fabric l2forward withdraw broadcast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.add(intent.key());
+                intentService.withdraw(intent);
+                bctUpdated = true;
+            }
+        }
+        for (SinglePointToMultiPointIntent intent : newBctIntentsMap.values()) {
+            SinglePointToMultiPointIntent oldIntent = bctIntentsMap.get(intent.key());
+            if (oldIntent == null ||
+                    !oldIntent.filteredEgressPoints().equals(intent.filteredEgressPoints()) ||
+                    !oldIntent.filteredIngressPoint().equals(intent.filteredIngressPoint()) ||
+                    !oldIntent.selector().equals(intent.selector()) ||
+                    !oldIntent.treatment().equals(intent.treatment()) ||
+                    !oldIntent.constraints().equals(intent.constraints())) {
+                log.info("simple fabric l2forward submit broadcast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.remove(intent.key());
+                intentService.submit(intent);
+                bctUpdated = true;
+            }
+        }
+
+        boolean uniUpdated = false;
+        for (MultiPointToSinglePointIntent intent : uniIntentsMap.values()) {
+            MultiPointToSinglePointIntent newIntent = newUniIntentsMap.get(intent.key());
+            if (newIntent == null) {
+                log.info("simple fabric l2forward withdraw unicast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.add(intent.key());
+                intentService.withdraw(intent);
+                uniUpdated = true;
+            }
+        }
+        for (MultiPointToSinglePointIntent intent : newUniIntentsMap.values()) {
+            MultiPointToSinglePointIntent oldIntent = uniIntentsMap.get(intent.key());
+            if (oldIntent == null ||
+                    !oldIntent.filteredEgressPoint().equals(intent.filteredEgressPoint()) ||
+                    !oldIntent.filteredIngressPoints().equals(intent.filteredIngressPoints()) ||
+                    !oldIntent.selector().equals(intent.selector()) ||
+                    !oldIntent.treatment().equals(intent.treatment()) ||
+                    !oldIntent.constraints().equals(intent.constraints())) {
+                log.info("simple fabric l2forward submit unicast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.remove(intent.key());
+                intentService.submit(intent);
+                uniUpdated = true;
+            }
+        }
+
+        if (bctUpdated) {
+            bctIntentsMap = newBctIntentsMap;
+        }
+        if (uniUpdated) {
+            uniIntentsMap = newUniIntentsMap;
+        }
+    }
+
+    private void checkIntentsPurge() {
+        // check intents to be purge
+        if (!toBePurgedIntentKeys.isEmpty()) {
+            Set<Key> purgedKeys = new HashSet<>();
+            for (Key key : toBePurgedIntentKeys) {
+                Intent intentToPurge = intentService.getIntent(key);
+                if (intentToPurge == null) {
+                    log.info("simple fabric l2forward purged intent: key={}", key.toString());
+                    purgedKeys.add(key);
+                } else {
+                    switch (intentService.getIntentState(key)) {
+                    case FAILED:
+                    case WITHDRAWN:
+                        log.info("simple fabric l2forward try to purge intent: key={}", key.toString());
+                        intentService.purge(intentToPurge);
+                        break;
+                    case INSTALL_REQ:
+                    case INSTALLED:
+                    case INSTALLING:
+                    case RECOMPILING:
+                    case COMPILING:
+                        log.warn("simple fabric l2forward withdraw intent to purge: key={}", key);
+                        intentService.withdraw(intentToPurge);
+                        break;
+                    case WITHDRAW_REQ:
+                    case WITHDRAWING:
+                    case PURGE_REQ:
+                    case CORRUPT:
+                    default:
+                        // no action
+                        break;
+                    }
+                }
+            }
+            toBePurgedIntentKeys.removeAll(purgedKeys);
+        }
+    }
+
+    // Generates Unicast Intents and broadcast Intents for the L2 Network.
+
+    private Set<Intent> generateL2NetworkIntents(L2Network l2Network) {
+        return new ImmutableSet.Builder<Intent>()
+                .addAll(buildBrcIntents(l2Network))
+                .addAll(buildUniIntents(l2Network, hostsFromL2Network(l2Network)))
+                .build();
+    }
+
+    // Build Boadcast Intents for a L2 Network.
+    private Set<SinglePointToMultiPointIntent> buildBrcIntents(L2Network l2Network) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        if (interfaces.size() < 2 || !l2Network.l2Forward() || !l2Network.l2Broadcast()) {
+            return ImmutableSet.of();
+        }
+        Set<SinglePointToMultiPointIntent> brcIntents = Sets.newHashSet();
+        ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+
+        // Generates broadcast Intents from any network interface to other
+        // network interface from the L2 Network.
+        interfaces
+            .forEach(src -> {
+            FilteredConnectPoint srcFcp = buildFilteredConnectedPoint(src);
+            Set<FilteredConnectPoint> dstFcps = interfaces.stream()
+                    .filter(iface -> !iface.equals(src))
+                    .map(this::buildFilteredConnectedPoint)
+                    .collect(Collectors.toSet());
+            Key key = buildKey(l2Network.name(), "BCAST", srcFcp.connectPoint(), MacAddress.BROADCAST);
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthDst(MacAddress.BROADCAST)
+                    .build();
+            SinglePointToMultiPointIntent.Builder intentBuilder = SinglePointToMultiPointIntent.builder()
+                    .appId(l2ForwardAppId)
+                    .key(key)
+                    .selector(selector)
+                    .filteredIngressPoint(srcFcp)
+                    .filteredEgressPoints(dstFcps)
+                    .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+                    .priority(SimpleFabricService.PRI_L2NETWORK_BROADCAST)
+                    .resourceGroup(resourceGroup);
+            brcIntents.add(intentBuilder.build());
+        });
+        return brcIntents;
+    }
+
+    // Builds unicast Intents for a L2 Network.
+    private Set<MultiPointToSinglePointIntent> buildUniIntents(L2Network l2Network, Set<Host> hosts) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        if (!l2Network.l2Forward() || interfaces.size() < 2) {
+            return ImmutableSet.of();
+        }
+        Set<MultiPointToSinglePointIntent> uniIntents = Sets.newHashSet();
+        ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+        hosts.forEach(host -> {
+            FilteredConnectPoint hostFcp = buildFilteredConnectedPoint(host);
+            Set<FilteredConnectPoint> srcFcps = interfaces.stream()
+                    .map(this::buildFilteredConnectedPoint)
+                    .filter(fcp -> !fcp.equals(hostFcp))
+                    .collect(Collectors.toSet());
+            Key key = buildKey(l2Network.name(), "UNI", hostFcp.connectPoint(), host.mac());
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthDst(host.mac()).build();
+            MultiPointToSinglePointIntent.Builder intentBuilder = MultiPointToSinglePointIntent.builder()
+                    .appId(l2ForwardAppId)
+                    .key(key)
+                    .selector(selector)
+                    .filteredIngressPoints(srcFcps)
+                    .filteredEgressPoint(hostFcp)
+                    .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+                    .priority(SimpleFabricService.PRI_L2NETWORK_UNICAST)
+                    .resourceGroup(resourceGroup);
+            uniIntents.add(intentBuilder.build());
+        });
+
+        return uniIntents;
+    }
+
+    // Intent generate utilities
+
+    private Set<Host> hostsFromL2Network(L2Network l2Network) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        return interfaces.stream()
+                .map(this::hostsFromInterface)
+                .flatMap(Collection::stream)
+                .collect(Collectors.toSet());
+    }
+
+    private Set<Host> hostsFromInterface(Interface iface) {
+        return hostService.getConnectedHosts(iface.connectPoint())
+                .stream()
+                .filter(host -> host.vlan().equals(iface.vlan()))
+                .collect(Collectors.toSet());
+    }
+
+    private Key buildKey(String l2NetworkName, String type, ConnectPoint cPoint, MacAddress dstMac) {
+        return Key.of(l2NetworkName + "-" + type + "-" + cPoint.toString() + "-" + dstMac, l2ForwardAppId);
+    }
+
+    private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encapsulation) {
+        if (!encapsulation.equals(EncapsulationType.NONE)) {
+            List<Constraint> newConstraints = new ArrayList<>(constraints);
+            constraints.stream()
+                .filter(c -> c instanceof EncapsulationConstraint)
+                .forEach(newConstraints::remove);
+            newConstraints.add(new EncapsulationConstraint(encapsulation));
+            return ImmutableList.copyOf(newConstraints);
+        }
+        return constraints;
+    }
+
+    private FilteredConnectPoint buildFilteredConnectedPoint(Interface iface) {
+        Objects.requireNonNull(iface);
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+        if (iface.vlan() != null && !iface.vlan().equals(VlanId.NONE)) {
+            trafficSelector.matchVlanId(iface.vlan());
+        }
+        return new FilteredConnectPoint(iface.connectPoint(), trafficSelector.build());
+    }
+
+    protected FilteredConnectPoint buildFilteredConnectedPoint(Host host) {
+        Objects.requireNonNull(host);
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+        if (host.vlan() != null && !host.vlan().equals(VlanId.NONE)) {
+            trafficSelector.matchVlanId(host.vlan());
+        }
+        return new FilteredConnectPoint(host.location(), trafficSelector.build());
+    }
+
+    // Dump command handler
+    private void dump(String subject, PrintStream out) {
+        if ("intents".equals(subject)) {
+            out.println("L2Forward Broadcast Intents:\n");
+            for (SinglePointToMultiPointIntent intent: bctIntentsMap.values()) {
+                out.println("    " + intent.key().toString()
+                          + ": " + intent.selector().criteria()
+                          + ", [" + intent.filteredIngressPoint().connectPoint()
+                          + "] -> " + intent.filteredEgressPoints().stream()
+                                      .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet()));
+            }
+            out.println("");
+            out.println("L2Forward Unicast Intents:\n");
+            for (MultiPointToSinglePointIntent intent: uniIntentsMap.values()) {
+                out.println("    " + intent.key().toString()
+                          + ": " + intent.selector().criteria()
+                          + ", [" + intent.filteredIngressPoints().stream()
+                                    .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet())
+                          + "] -> " + intent.filteredEgressPoint().connectPoint());
+            }
+            out.println("");
+            out.println("L2Forward Intents to Be Purged:\n");
+            for (Key key: toBePurgedIntentKeys) {
+                out.println("    " + key.toString());
+            }
+            out.println("");
+        }
+    }
+
+    // Listener
+    private class InternalSimpleFabricListener implements SimpleFabricListener {
+        @Override
+        public void event(SimpleFabricEvent event) {
+            switch (event.type()) {
+            case SIMPLE_FABRIC_UPDATED:
+                refresh();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_IDLE:
+                refresh();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_DUMP:
+                dump(event.subject(), event.out());
+                break;
+            default:
+                // NOTE: nothing to do on SIMPLE_FABRIC_FLUSH
+                break;
+            }
+        }
+    }
+
+}