Implement path protection for point to point intents

Change-Id: I3f3627e7c2a7e3ab017e46655692ab70fdeae413
diff --git a/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java b/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
index 9bfd199..14db547 100644
--- a/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/AddPointToPointIntentCommand.java
@@ -17,6 +17,7 @@
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -24,6 +25,7 @@
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.ProtectionConstraint;
 
 import java.util.List;
 
@@ -44,6 +46,11 @@
               required = true, multiValued = false)
     String egressDeviceString = null;
 
+    @Option(name = "-p", aliases = "--protect",
+            description = "Utilize path protection",
+            required = false, multiValued = false)
+    private boolean backup = false;
+
     @Override
     protected void execute() {
         IntentService service = get(IntentService.class);
@@ -56,6 +63,9 @@
         TrafficTreatment treatment = buildTrafficTreatment();
 
         List<Constraint> constraints = buildConstraints();
+        if (backup) {
+            constraints.add(new ProtectionConstraint());
+        }
 
         Intent intent = PointToPointIntent.builder()
                 .appId(appId())
diff --git a/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java b/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
index df90c41..921c279 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/FlowRuleIntent.java
@@ -35,6 +35,7 @@
 public class FlowRuleIntent extends Intent {
 
     private final Collection<FlowRule> flowRules;
+    private PathIntent.ProtectionType type;
 
     /**
      * Creates a flow rule intent with the specified flow rules and resources.
@@ -48,7 +49,19 @@
     }
 
     /**
-     * Creates an flow rule intent with the specified key, flow rules to be set, and
+     * Creates a flow rule intent with the specified flow rules, resources, and type.
+     *
+     * @param appId application id
+     * @param flowRules flow rules to be set
+     * @param resources network resource to be set
+     */
+    public FlowRuleIntent(ApplicationId appId, List<FlowRule> flowRules, Collection<NetworkResource> resources,
+                          PathIntent.ProtectionType type) {
+        this(appId, null, flowRules, resources, type);
+    }
+
+    /**
+     * Creates a flow rule intent with the specified key, flow rules to be set, and
      * required network resources.
      *
      * @param appId     application id
@@ -58,8 +71,32 @@
      */
     public FlowRuleIntent(ApplicationId appId, Key key, Collection<FlowRule> flowRules,
                           Collection<NetworkResource> resources) {
+        this(appId, key, flowRules, resources, PathIntent.ProtectionType.PRIMARY);
+    }
+
+    /**
+     * Creates a flow rule intent with the specified key, flow rules to be set, and
+     * required network resources.
+     *
+     * @param appId     application id
+     * @param key       key
+     * @param flowRules flow rules
+     * @param resources network resources
+     */
+    public FlowRuleIntent(ApplicationId appId, Key key, Collection<FlowRule> flowRules,
+                          Collection<NetworkResource> resources, PathIntent.ProtectionType primary) {
         super(appId, key, resources, DEFAULT_INTENT_PRIORITY);
         this.flowRules = ImmutableList.copyOf(checkNotNull(flowRules));
+        this.type = primary;
+    }
+
+    /**
+     * Creates a flow rule intent with all the same characteristics as the given
+     * one except for the flow rule type.
+     */
+    public FlowRuleIntent(FlowRuleIntent intent, PathIntent.ProtectionType type) {
+        this(intent.appId(), intent.key(), intent.flowRules(),
+              intent.resources(), type);
     }
 
     /**
@@ -68,6 +105,7 @@
     protected FlowRuleIntent() {
         super();
         this.flowRules = null;
+        this.type = PathIntent.ProtectionType.PRIMARY;
     }
 
     /**
@@ -84,6 +122,10 @@
         return true;
     }
 
+    public PathIntent.ProtectionType type() {
+        return type;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
diff --git a/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java b/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
index 63fff1d..f2a0e45 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/IntentCompiler.java
@@ -31,7 +31,7 @@
      * Compiles the specified intent into other intents.
      *
      * @param intent      intent to be compiled
-     * @param installable previously compilation result; optional
+     * @param installable previous compilation result; optional
      * @return list of resulting intents
      * @throws IntentException if issues are encountered while compiling the intent
      */
diff --git a/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java b/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
index b674c06..7ac22b3 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/PathIntent.java
@@ -36,10 +36,11 @@
 public class PathIntent extends ConnectivityIntent {
 
     private final Path path;
+    private ProtectionType type;
 
     /**
      * Creates a new point-to-point intent with the supplied ingress/egress
-     * ports and using the specified explicit path.
+     * ports and using the specified explicit path. Path is primary by default.
      *
      * @param appId     application identifier
      * @param key       intent key
@@ -57,10 +58,38 @@
                          Path path,
                          List<Constraint> constraints,
                          int priority) {
+        this(appId, key, selector, treatment, path, constraints, priority,
+             ProtectionType.PRIMARY);
+    }
+
+    /**
+     * Creates a new point-to-point intent with the supplied ingress/egress
+     * ports and using the specified explicit path, which can be classified
+     * as PRIMARY or BACKUP.
+     *
+     * @param appId     application identifier
+     * @param key       intent key
+     * @param selector  traffic selector
+     * @param treatment treatment
+     * @param path      traversed links
+     * @param constraints  optional list of constraints
+     * @param priority  priority to use for the generated flows
+     * @param type      PRIMARY or BACKUP
+     * @throws NullPointerException {@code path} is null
+     */
+    protected PathIntent(ApplicationId appId,
+                         Key key,
+                         TrafficSelector selector,
+                         TrafficTreatment treatment,
+                         Path path,
+                         List<Constraint> constraints,
+                         int priority,
+                         ProtectionType type) {
         super(appId, key, resources(path.links()), selector, treatment, constraints,
-                priority);
+              priority);
         PathIntent.validate(path.links());
         this.path = path;
+        this.type = type;
     }
 
     /**
@@ -69,6 +98,7 @@
     protected PathIntent() {
         super();
         this.path = null;
+        this.type = ProtectionType.PRIMARY;
     }
 
     /**
@@ -85,6 +115,7 @@
      */
     public static class Builder extends ConnectivityIntent.Builder {
         Path path;
+        ProtectionType type;
 
         protected Builder() {
             // Hide default constructor
@@ -131,6 +162,11 @@
             return this;
         }
 
+        public Builder setType(ProtectionType type) {
+            this.type = type;
+            return this;
+        }
+
         /**
          * Builds a path intent from the accumulated parameters.
          *
@@ -145,7 +181,8 @@
                     treatment,
                     path,
                     constraints,
-                    priority
+                    priority,
+                    type == null ? ProtectionType.PRIMARY : type
             );
         }
     }
@@ -183,6 +220,10 @@
         return path;
     }
 
+    public ProtectionType type() {
+        return type;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
@@ -195,7 +236,25 @@
                 .add("treatment", treatment())
                 .add("constraints", constraints())
                 .add("path", path)
+                .add("type", type)
                 .toString();
     }
 
+    // for path protection purposes
+    public enum ProtectionType {
+        /**
+         * Intent within primary path.
+         */
+        PRIMARY,
+        /**
+         * Intent within backup path.
+         */
+        BACKUP,
+        /**
+         * Intent whose flow rule serves as the fast failover
+         * between primary and backup paths.
+         */
+        FAILOVER
+    }
+
 }
diff --git a/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java b/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java
new file mode 100644
index 0000000..d4d2c74
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/intent/constraint/ProtectionConstraint.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.net.intent.constraint;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.ResourceContext;
+
+/**
+ * Constraint that determines whether to employ path protection.
+ */
+@Beta
+public class ProtectionConstraint implements Constraint {
+    // doesn't use LinkResourceService
+    @Override
+    public double cost(Link link, ResourceContext context) {
+        return 1;
+    }
+
+    // doesn't use LinkResourceService
+    @Override
+    public boolean validate(Path path, ResourceContext context) {
+        return true;
+    }
+
+    /**
+     * Determines whether to utilize path protection for the given intent.
+     *
+     * @param intent  intent to be inspected
+     * @return        whether the intent has a ProtectionConstraint
+     */
+    public static boolean requireProtectedPath(Intent intent) {
+        if (intent instanceof PointToPointIntent) {
+            PointToPointIntent pointToPointIntent = (PointToPointIntent) intent;
+            return pointToPointIntent.constraints().stream()
+                    .anyMatch(p -> p instanceof ProtectionConstraint);
+        }
+        return false;
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
index a87263a..d3db258 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
@@ -26,7 +26,7 @@
 public class PointToPointIntentTest extends ConnectivityIntentTest {
 
     /**
-     * Checks that the MultiPointToSinglePointIntent class is immutable.
+     * Checks that the PointToPointIntent class is immutable.
      */
     @Test
     public void checkImmutability() {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
index fc7e568..07759e3 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
@@ -28,8 +28,11 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.core.IdGenerator;
 import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentBatchDelegate;
 import org.onosproject.net.intent.IntentCompiler;
@@ -42,6 +45,8 @@
 import org.onosproject.net.intent.IntentStore;
 import org.onosproject.net.intent.IntentStoreDelegate;
 import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.impl.compiler.PointToPointIntentCompiler;
 import org.onosproject.net.intent.impl.phase.FinalIntentProcessPhase;
 import org.onosproject.net.intent.impl.phase.IntentProcessPhase;
 import org.osgi.service.component.ComponentContext;
@@ -123,6 +128,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService configService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
     private ExecutorService batchExecutor;
     private ExecutorService workerExecutor;
 
@@ -234,6 +242,15 @@
         checkNotNull(intent, INTENT_NULL);
         IntentData data = new IntentData(intent, IntentState.PURGE_REQ, null);
         store.addPending(data);
+
+        // remove associated group if there is one
+        if (intent instanceof PointToPointIntent) {
+            PointToPointIntent pointIntent = (PointToPointIntent) intent;
+            DeviceId deviceId = pointIntent.ingressPoint().deviceId();
+            GroupKey groupKey = PointToPointIntentCompiler.makeGroupKey(intent.id());
+            groupService.removeGroup(deviceId, groupKey,
+                                     intent.appId());
+        }
     }
 
     @Override
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
index 4b51714..6dd4ebd 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
@@ -20,6 +20,7 @@
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.DisjointPath;
 import org.onosproject.net.ElementId;
 import org.onosproject.net.Path;
 import org.onosproject.net.intent.ConnectivityIntent;
@@ -108,6 +109,29 @@
     }
 
     /**
+     * Computes a disjoint path between two ConnectPoints.
+     *
+     * @param intent intent on which behalf path is being computed
+     * @param one    start of the path
+     * @param two    end of the path
+     * @return DisjointPath         between the two
+     * @throws PathNotFoundException if two paths cannot be found
+     */
+    protected DisjointPath getDisjointPath(ConnectivityIntent intent,
+                           ElementId one, ElementId two) {
+        Set<DisjointPath> paths = pathService.getDisjointPaths(one, two, weight(intent.constraints()));
+        final List<Constraint> constraints = intent.constraints();
+        ImmutableList<DisjointPath> filtered = FluentIterable.from(paths)
+                .filter(path -> checkPath(path, constraints))
+                .toList();
+        if (filtered.isEmpty()) {
+            throw new PathNotFoundException(one, two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return filtered.iterator().next();
+    }
+
+    /**
      * Edge-weight capable of evaluating link cost using a set of constraints.
      */
     protected class ConstraintBasedLinkWeight implements LinkWeight {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
index ff69f29..fdcdd00 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PathIntentCompiler.java
@@ -82,7 +82,7 @@
         compile(this, intent, rules, devices);
 
 
-        return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources()));
+        return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources(), intent.type()));
     }
 
     @Override
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
index 28d7ea5..0de641a 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
@@ -18,17 +18,51 @@
 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.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.EdgeLink;
 import org.onosproject.net.Link;
 import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.intent.FlowRuleIntent;
 import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentId;
 import org.onosproject.net.intent.PathIntent;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.ProtectionConstraint;
+import org.onosproject.net.intent.impl.PathNotFoundException;
+import org.onosproject.net.link.LinkService;
 import org.onosproject.net.provider.ProviderId;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 
 import static java.util.Arrays.asList;
 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
@@ -45,6 +79,18 @@
             new ProviderId("core", "org.onosproject.core", true);
     // TODO: consider whether the default cost is appropriate or not
     public static final int DEFAULT_COST = 1;
+    protected static final int PRIORITY = Intent.DEFAULT_INTENT_PRIORITY;
+    protected boolean erasePrimary = false;
+    protected boolean eraseBackup = false;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
 
     @Activate
     public void activate() {
@@ -63,20 +109,160 @@
         ConnectPoint egressPoint = intent.egressPoint();
 
         if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
-            List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
-            return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST), intent));
+            return createZeroHopIntent(ingressPoint, egressPoint, intent);
         }
 
+        // proceed with no protected paths
+        if (!ProtectionConstraint.requireProtectedPath(intent)) {
+            return createUnprotectedIntent(ingressPoint, egressPoint, intent);
+        }
+
+        try {
+            // attempt to compute and implement backup path
+            return createProtectedIntent(ingressPoint, egressPoint, intent, installable);
+        } catch (PathNotFoundException e) {
+            // no disjoint path extant -- maximum one path exists between devices
+            return createSinglePathIntent(ingressPoint, egressPoint, intent, installable);
+        }
+    }
+
+    private List<Intent> createZeroHopIntent(ConnectPoint ingressPoint,
+                                             ConnectPoint egressPoint,
+                                             PointToPointIntent intent) {
+        List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
+        return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST),
+                                       intent, PathIntent.ProtectionType.PRIMARY));
+    }
+
+    private List<Intent> createUnprotectedIntent(ConnectPoint ingressPoint,
+                                                 ConnectPoint egressPoint,
+                                                 PointToPointIntent intent) {
         List<Link> links = new ArrayList<>();
         Path path = getPath(intent, ingressPoint.deviceId(),
-                egressPoint.deviceId());
+                            egressPoint.deviceId());
 
         links.add(createEdgeLink(ingressPoint, true));
         links.addAll(path.links());
         links.add(createEdgeLink(egressPoint, false));
 
         return asList(createPathIntent(new DefaultPath(PID, links, path.cost(),
-                                                       path.annotations()), intent));
+                                                       path.annotations()), intent,
+                                       PathIntent.ProtectionType.PRIMARY));
+    }
+
+    //FIXME: Compatibility with EncapsulationConstraint
+    private List<Intent> createProtectedIntent(ConnectPoint ingressPoint,
+                                               ConnectPoint egressPoint,
+                                               PointToPointIntent intent,
+                                               List<Intent> installable) {
+        DisjointPath path = getDisjointPath(intent, ingressPoint.deviceId(),
+                                            egressPoint.deviceId());
+
+        List<Intent> reusableIntents = null;
+        if (installable != null) {
+            reusableIntents = filterInvalidSubIntents(installable, intent);
+            if (reusableIntents.size() == installable.size()) {
+                // all old paths are still viable
+                return installable;
+            }
+        }
+
+        List<Intent> intentList = new ArrayList<>();
+
+        // primary path intent
+        List<Link> links = new ArrayList<>();
+        links.addAll(path.links());
+        links.add(createEdgeLink(egressPoint, false));
+
+        // backup path intent
+        List<Link> backupLinks = new ArrayList<>();
+        backupLinks.addAll(path.backup().links());
+        backupLinks.add(createEdgeLink(egressPoint, false));
+
+        /*
+         * One of the old paths is still entirely intact. This old path has
+         * already been made primary, so we must add a backup path intent
+         * and modify the failover group treatment accordingly.
+         */
+        if (reusableIntents != null && reusableIntents.size() > 1) {
+            /*
+             * Ensures that the egress port on source device is different than
+             * that of existing path so that failover group will be useful
+             * (would not be useful if both output ports in group bucket were
+             * the same). Does not necessarily ensure that the new backup path
+             * is entirely disjoint from the old path.
+             */
+            PortNumber primaryPort = getPrimaryPort(intent);
+            if (primaryPort != null && !links.get(0).src().port().equals(primaryPort)) {
+                reusableIntents.add(createPathIntent(new DefaultPath(PID, links,
+                                                                     path.cost(), path.annotations()),
+                                                     intent, PathIntent.ProtectionType.BACKUP));
+                updateFailoverGroup(intent, links);
+                return reusableIntents;
+
+            } else {
+                reusableIntents.add(createPathIntent(new DefaultPath(PID, backupLinks, path.backup().cost(),
+                                     path.backup().annotations()), intent, PathIntent.ProtectionType.BACKUP));
+                updateFailoverGroup(intent, backupLinks);
+                return reusableIntents;
+            }
+        }
+
+        intentList.add(createPathIntent(new DefaultPath(PID, links, path.cost(), path.annotations()),
+                                        intent, PathIntent.ProtectionType.PRIMARY));
+        intentList.add(createPathIntent(new DefaultPath(PID, backupLinks, path.backup().cost(),
+                                        path.backup().annotations()), intent, PathIntent.ProtectionType.BACKUP));
+
+        // Create fast failover flow rule intent or, if it already exists,
+        // add contents appropriately.
+        if (groupService.getGroup(ingressPoint.deviceId(),
+                                  makeGroupKey(intent.id())) == null) {
+            // manufactured fast failover flow rule intent
+            createFailoverTreatmentGroup(path.links(), path.backup().links(), intent);
+
+            FlowRuleIntent frIntent = new FlowRuleIntent(intent.appId(),
+                                                         createFailoverFlowRules(intent),
+                                                         asList(ingressPoint.deviceId()),
+                                                         PathIntent.ProtectionType.FAILOVER);
+            intentList.add(frIntent);
+        } else {
+            updateFailoverGroup(intent, links);
+            updateFailoverGroup(intent, backupLinks);
+        }
+
+        return intentList;
+    }
+
+    private List<Intent> createSinglePathIntent(ConnectPoint ingressPoint,
+                                                ConnectPoint egressPoint,
+                                                PointToPointIntent intent,
+                                                List<Intent> installable) {
+        List<Link> links = new ArrayList<>();
+        Path onlyPath = getPath(intent, ingressPoint.deviceId(),
+                                egressPoint.deviceId());
+
+        List<Intent> reusableIntents = null;
+        if (installable != null) {
+            reusableIntents = filterInvalidSubIntents(installable, intent);
+            if (reusableIntents.size() == installable.size()) {
+                // all old paths are still viable
+                return installable;
+            }
+        }
+
+        // If there exists a full path from old installable intents,
+        // return the intents that comprise it.
+        if (reusableIntents != null && reusableIntents.size() > 1) {
+            return reusableIntents;
+        } else {
+            links.add(createEdgeLink(ingressPoint, true));
+            links.addAll(onlyPath.links());
+            links.add(createEdgeLink(egressPoint, false));
+
+            return asList(createPathIntent(new DefaultPath(PID, links, onlyPath.cost(),
+                                                           onlyPath.annotations()),
+                                           intent, PathIntent.ProtectionType.PRIMARY));
+        }
     }
 
     /**
@@ -85,9 +271,11 @@
      *
      * @param path   path to create an intent for
      * @param intent original intent
+     * @param type   primary or backup
      */
     private Intent createPathIntent(Path path,
-                                    PointToPointIntent intent) {
+                                    PointToPointIntent intent,
+                                    PathIntent.ProtectionType type) {
         return PathIntent.builder()
                 .appId(intent.appId())
                 .selector(intent.selector())
@@ -95,7 +283,262 @@
                 .path(path)
                 .constraints(intent.constraints())
                 .priority(intent.priority())
+                .setType(type)
                 .build();
     }
 
+    /**
+     * Gets primary port number through failover group associated
+     * with this intent.
+     */
+    private PortNumber getPrimaryPort(PointToPointIntent intent) {
+        Group group = groupService.getGroup(intent.ingressPoint().deviceId(),
+                                            makeGroupKey(intent.id()));
+        PortNumber primaryPort = null;
+        if (group != null) {
+            List<GroupBucket> buckets = group.buckets().buckets();
+            Iterator<GroupBucket> iterator = buckets.iterator();
+            while (primaryPort == null && iterator.hasNext()) {
+                GroupBucket bucket = iterator.next();
+                Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
+                if (individualInstruction instanceof Instructions.OutputInstruction) {
+                    Instructions.OutputInstruction outInstruction =
+                            (Instructions.OutputInstruction) individualInstruction;
+                    PortNumber tempPortNum = outInstruction.port();
+                    Port port = deviceService.getPort(intent.ingressPoint().deviceId(),
+                                                      tempPortNum);
+                    if (port != null && port.isEnabled()) {
+                        primaryPort = tempPortNum;
+                    }
+                }
+            }
+        }
+        return primaryPort;
+    }
+
+    /**
+     * Creates group key unique to each intent.
+     */
+    public static GroupKey makeGroupKey(IntentId intentId) {
+        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+        buffer.putLong(intentId.fingerprint());
+        return new DefaultGroupKey(buffer.array());
+    }
+
+    /**
+     * Creates a new failover group with the initial ports of the links
+     * from the primary and backup path.
+     *
+     * @param links         links from the primary path
+     * @param backupLinks   links from the backup path
+     * @param intent        intent from which this call originates
+     */
+    private void createFailoverTreatmentGroup(List<Link> links,
+                                              List<Link> backupLinks,
+                                              PointToPointIntent intent) {
+
+        List<GroupBucket> buckets = new ArrayList<>();
+
+        TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
+        ConnectPoint src = links.get(0).src();
+        tBuilderIn.setOutput(src.port());
+
+        TrafficTreatment.Builder tBuilderIn2 = DefaultTrafficTreatment.builder();
+        ConnectPoint src2 = backupLinks.get(0).src();
+        tBuilderIn2.setOutput(src2.port());
+
+        buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null));
+        buckets.add(DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn2.build(), src2.port(), null));
+
+        GroupBuckets groupBuckets = new GroupBuckets(buckets);
+
+        GroupDescription groupDesc = new DefaultGroupDescription(src.deviceId(), Group.Type.FAILOVER,
+                                         groupBuckets, makeGroupKey(intent.id()), null, intent.appId());
+        groupService.addGroup(groupDesc);
+    }
+
+    /**
+     * Manufactures flow rule with treatment that is defined by failover
+     * group and traffic selector determined by ingress port of the intent.
+     *
+     * @param intent intent which is being compiled (for appId)
+     * @return       a list of a singular flow rule with fast failover
+     *               outport traffic treatment
+     */
+    private List<FlowRule> createFailoverFlowRules(PointToPointIntent intent) {
+        List<FlowRule> flowRules = new ArrayList<>();
+
+        ConnectPoint ingress = intent.ingressPoint();
+        DeviceId deviceId = ingress.deviceId();
+
+        // flow rule with failover traffic treatment
+        TrafficSelector trafficSelector = DefaultTrafficSelector.builder(intent.selector())
+                                                      .matchInPort(ingress.port()).build();
+
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder();
+        flowRules.add(flowRuleBuilder.withSelector(trafficSelector)
+                              .withTreatment(buildFailoverTreatment(deviceId, makeGroupKey(intent.id())))
+                              .fromApp(intent.appId())
+                              .makePermanent()
+                              .forDevice(deviceId)
+                              .withPriority(PRIORITY)
+                              .build());
+
+        return flowRules;
+    }
+
+    private TrafficTreatment buildFailoverTreatment(DeviceId srcDevice,
+                                                    GroupKey groupKey) {
+        Group group = groupService.getGroup(srcDevice, groupKey);
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        TrafficTreatment trafficTreatment = tBuilder.group(group.id()).build();
+        return trafficTreatment;
+    }
+
+    /**
+     * Deletes intents from the given list if the ports or links the intent
+     * relies on are no longer viable. The failover flow rule intent is never
+     * deleted -- only its contents are updated.
+     *
+     * @param oldInstallables  list of intents to examine
+     * @return                 list of reusable installable intents
+     */
+    private List<Intent> filterInvalidSubIntents(List<Intent> oldInstallables,
+                                                 PointToPointIntent pointIntent) {
+        List<Intent> intentList = new ArrayList<>();
+        intentList.addAll(oldInstallables);
+        erasePrimary = false;
+        eraseBackup = false;
+        if (intentList != null) {
+            Iterator<Intent> iterator = intentList.iterator();
+            while (iterator.hasNext() && !(erasePrimary && eraseBackup)) {
+                Intent intent = iterator.next();
+                intent.resources().forEach(resource -> {
+                    if (resource instanceof Link) {
+                        Link link = (Link) resource;
+                        if (link.state() == Link.State.INACTIVE) {
+                            setPathsToRemove(intent);
+                        } else if (link instanceof EdgeLink) {
+                            ConnectPoint connectPoint = (link.src().elementId() instanceof DeviceId)
+                                    ? link.src() : link.dst();
+                            Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+                            if (port == null || !port.isEnabled()) {
+                                setPathsToRemove(intent);
+                            }
+                        } else {
+                            Port port1 = deviceService.getPort(link.src().deviceId(), link.src().port());
+                            Port port2 = deviceService.getPort(link.dst().deviceId(), link.dst().port());
+                            if (port1 == null || !port1.isEnabled() || port2 == null || !port2.isEnabled()) {
+                                setPathsToRemove(intent);
+                            }
+                        }
+                    }
+                });
+            }
+            removeAndUpdateIntents(intentList, pointIntent);
+        }
+        return intentList;
+    }
+
+    /**
+     * Sets instance variables erasePrimary and eraseBackup. If erasePrimary,
+     * the primary path is no longer viable and related intents will be deleted.
+     * If eraseBackup, the backup path is no longer viable and related intents
+     * will be deleted.
+     *
+     * @param intent  intent whose resources are found to be disabled/inactive:
+     *                if intent is part of primary path, primary path set for removal;
+     *                if intent is part of backup path, backup path set for removal;
+     *                if bad intent is of type failover, the ingress point is down,
+     *                and both paths are rendered inactive.
+     * @return        true if both primary and backup paths are to be removed
+     */
+    private boolean setPathsToRemove(Intent intent) {
+        if (intent instanceof FlowRuleIntent) {
+            FlowRuleIntent frIntent = (FlowRuleIntent) intent;
+            PathIntent.ProtectionType type = frIntent.type();
+            if (type == PathIntent.ProtectionType.PRIMARY || type == PathIntent.ProtectionType.FAILOVER) {
+                erasePrimary = true;
+            }
+            if (type == PathIntent.ProtectionType.BACKUP || type == PathIntent.ProtectionType.FAILOVER) {
+                eraseBackup = true;
+            }
+        }
+        return erasePrimary && eraseBackup;
+    }
+
+    /**
+     * Removes intents from installables list, depending on the values
+     * of instance variables erasePrimary and eraseBackup. Flow rule intents
+     * that contain the manufactured fast failover flow rules are never deleted.
+     * The contents are simply modified as necessary. If cleanUpIntents size
+     * is greater than 1 (failover intent), then one whole path from previous
+     * installables must be still viable.
+     *
+     * @param cleanUpIntents   list of installable intents
+     */
+    private void removeAndUpdateIntents(List<Intent> cleanUpIntents,
+                                        PointToPointIntent pointIntent) {
+        ListIterator<Intent> iterator = cleanUpIntents.listIterator();
+        while (iterator.hasNext()) {
+            Intent cIntent = iterator.next();
+            if (cIntent instanceof FlowRuleIntent) {
+                FlowRuleIntent fIntent = (FlowRuleIntent) cIntent;
+                if (fIntent.type() == PathIntent.ProtectionType.PRIMARY && erasePrimary) {
+                    // remove primary path's flow rule intents
+                    iterator.remove();
+                } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && eraseBackup) {
+                    //remove backup path's flow rule intents
+                    iterator.remove();
+                } else if (fIntent.type() == PathIntent.ProtectionType.BACKUP && erasePrimary) {
+                    // promote backup path's flow rule intents to primary
+                    iterator.set(new FlowRuleIntent(fIntent, PathIntent.ProtectionType.PRIMARY));
+                }
+            }
+        }
+        // remove buckets whose watchports are disabled if the failover group exists
+        Group group = groupService.getGroup(pointIntent.ingressPoint().deviceId(),
+                                            makeGroupKey(pointIntent.id()));
+        if (group != null) {
+            updateFailoverGroup(pointIntent);
+        }
+    }
+
+    // Removes buckets whose treatments rely on disabled ports from the
+    // failover group.
+    private void updateFailoverGroup(PointToPointIntent pointIntent) {
+        DeviceId deviceId = pointIntent.ingressPoint().deviceId();
+        GroupKey groupKey = makeGroupKey(pointIntent.id());
+        Group group = groupService.getGroup(deviceId, groupKey);
+        Iterator<GroupBucket> groupIterator = group.buckets().buckets().iterator();
+        while (groupIterator.hasNext()) {
+            GroupBucket bucket = groupIterator.next();
+            Instruction individualInstruction = bucket.treatment().allInstructions().get(0);
+            if (individualInstruction instanceof Instructions.OutputInstruction) {
+                Instructions.OutputInstruction outInstruction =
+                        (Instructions.OutputInstruction) individualInstruction;
+                Port port = deviceService.getPort(deviceId, outInstruction.port());
+                if (port == null || !port.isEnabled()) {
+                    GroupBuckets removeBuckets = new GroupBuckets(Collections.singletonList(bucket));
+                    groupService.removeBucketsFromGroup(deviceId, groupKey,
+                                                        removeBuckets, groupKey,
+                                                        pointIntent.appId());
+                }
+            }
+        }
+    }
+
+    // Adds failover group bucket with treatment outport determined by the
+    // ingress point of the links.
+    private void updateFailoverGroup(PointToPointIntent intent, List<Link> links) {
+        GroupKey groupKey = makeGroupKey(intent.id());
+
+        TrafficTreatment.Builder tBuilderIn = DefaultTrafficTreatment.builder();
+        ConnectPoint src = links.get(0).src();
+        tBuilderIn.setOutput(src.port());
+        GroupBucket bucket = DefaultGroupBucket.createFailoverGroupBucket(tBuilderIn.build(), src.port(), null);
+        GroupBuckets addBuckets = new GroupBuckets(Collections.singletonList(bucket));
+
+        groupService.addBucketsToGroup(src.deviceId(), groupKey, addBuckets, groupKey, intent.appId());
+    }
 }