Extend EncapsulationConstraint to include a suggested value of the Identifier.
The suggested identifier is used on each link, if available.
In case it is not available on one or more links, the identifier is selected with the active policy.

Patch 2: addressed comments by Pier.

Patch 3: addressed comments by Pier.

Tested with VLAN ids and MPLS labels.

Change-Id: Icdf901ef8d0786b2061d6be4db511cb89d26ddfd
diff --git a/core/api/src/main/java/org/onosproject/net/intent/constraint/EncapsulationConstraint.java b/core/api/src/main/java/org/onosproject/net/intent/constraint/EncapsulationConstraint.java
index 3dd9ec5..f5be3ed 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/constraint/EncapsulationConstraint.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/constraint/EncapsulationConstraint.java
@@ -17,10 +17,13 @@
 package org.onosproject.net.intent.constraint;
 
 
+import org.onlab.util.Identifier;
 import org.onosproject.net.EncapsulationType;
 import org.onosproject.net.Link;
 import org.onosproject.net.intent.ResourceContext;
 
+import java.util.Optional;
+
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -30,6 +33,7 @@
 public class EncapsulationConstraint extends BooleanConstraint {
 
     private EncapsulationType encapType;
+    private Optional<Identifier<?>> suggestedIdentifier;
 
     /**
      * Creates a new encapsulation constraint.
@@ -39,8 +43,20 @@
     public EncapsulationConstraint(EncapsulationType encapType) {
         checkNotNull(encapType, "EncapsulationType cannot be null");
         this.encapType = encapType;
+        this.suggestedIdentifier = Optional.empty();
     }
 
+    /**
+     * Creates a new encapsulation constraint with suggested identifier.
+     *
+     * @param encapType the encapsulation type {@link EncapsulationType}
+     * @param identifier the suggested identifier
+     */
+    public EncapsulationConstraint(EncapsulationType encapType, Identifier<?> identifier) {
+        checkNotNull(encapType, "EncapsulationType cannot be null");
+        this.encapType = encapType;
+        this.suggestedIdentifier = Optional.of(identifier);
+    }
 
     // doesn't use LinkResourceService
     @Override
@@ -60,6 +76,18 @@
         return encapType;
     }
 
+    /**
+     * Returns the suggested identifier.
+     *
+     * @return suggestedIdentifier
+     */
+    public Optional<Identifier<?>> suggestedIdentifier() {
+        if (suggestedIdentifier.isPresent()) {
+            return suggestedIdentifier;
+        }
+        return Optional.empty();
+    }
+
     @Override
     public int hashCode() {
         return encapType.hashCode();
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
index 7e0e660..82889f9 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.SetMultimap;
+import org.onosproject.net.resource.impl.LabelAllocator;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -47,7 +48,6 @@
 import org.onosproject.net.intent.PathIntent;
 import org.onosproject.net.intent.constraint.EncapsulationConstraint;
 import org.onosproject.net.resource.ResourceService;
-import org.onosproject.net.resource.impl.LabelAllocator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -110,9 +110,9 @@
         computePorts(intent, inputPorts, outputPorts);
 
         if (encapConstraint.isPresent()) {
-            labels = labelAllocator.assignLabelToPorts(intent.links(),
-                                                       intent.key(),
-                                                       encapConstraint.get().encapType());
+            labels = labelAllocator.assignLabelToPorts(intent.links(), intent.key(),
+                                                       encapConstraint.get().encapType(),
+                                                       encapConstraint.get().suggestedIdentifier());
         }
 
         ImmutableList.Builder<Intent> intentList = ImmutableList.builder();
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentFlowObjectiveCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentFlowObjectiveCompiler.java
index 870473c..bc51852 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentFlowObjectiveCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentFlowObjectiveCompiler.java
@@ -123,7 +123,8 @@
         if (encapConstraint.isPresent()) {
             labels = labelAllocator.assignLabelToPorts(intent.links(),
                                                        intent.key(),
-                                                       encapConstraint.get().encapType());
+                                                       encapConstraint.get().encapType(),
+                                                       encapConstraint.get().suggestedIdentifier());
         }
 
         ImmutableList.Builder<Intent> intentList = ImmutableList.builder();
diff --git a/core/net/src/main/java/org/onosproject/net/resource/impl/LabelAllocator.java b/core/net/src/main/java/org/onosproject/net/resource/impl/LabelAllocator.java
index bc41fb1..0b356b6 100644
--- a/core/net/src/main/java/org/onosproject/net/resource/impl/LabelAllocator.java
+++ b/core/net/src/main/java/org/onosproject/net/resource/impl/LabelAllocator.java
@@ -44,6 +44,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -242,6 +243,36 @@
         return ids;
     }
 
+    // Implements suggestedIdentifier behavior
+    private Map<LinkKey, Identifier<?>> suggestedIdentifierBehavior(Set<LinkKey> links,
+                                                           EncapsulationType type,
+                                                           Identifier<?> suggested) {
+        // Init step
+        Map<LinkKey, Identifier<?>> ids = Maps.newHashMap();
+        Set<Identifier<?>> candidates;
+        Identifier<?> selected = null;
+
+        // Iterates for each link selecting a label in the candidate set
+        // Select the suggested if available on the whole path
+        for (LinkKey link : links) {
+            // Get candidates set for the current link
+            candidates = getCandidates(link, type);
+
+            // Select the suggested if included in the candidates
+            // Otherwise select an other label for the current link
+            if (candidates.contains(suggested)) {
+                selected = suggested;
+            } else {
+                // If candidates is empty or does not contain suggested
+                log.warn("Suggested label {} is not available on link {}", suggested, link);
+                return Collections.emptyMap();
+            }
+            // Selected is associated to link
+            ids.put(link, selected);
+        }
+        return ids;
+    }
+
     // Implements NO_SWAP behavior
     private Map<LinkKey, Identifier<?>> noSwapBehavior(Set<LinkKey> links, EncapsulationType type) {
         // Init steps
@@ -306,9 +337,21 @@
      * @param  type the encapsulation type
      * @return the mappings between key and id
      */
-    private Map<LinkKey, Identifier<?>> findAvailableIDs(Set<LinkKey> links, EncapsulationType type) {
+    private Map<LinkKey, Identifier<?>> findAvailableIDs(Set<LinkKey> links,
+                                                         EncapsulationType type,
+                                                         Optional<Identifier<?>> suggestedIdentifier) {
         // Init step
         Map<LinkKey, Identifier<?>> ids;
+
+        //Use suggested identifier if possible
+        if (suggestedIdentifier.isPresent()) {
+            ids = suggestedIdentifierBehavior(links, type, suggestedIdentifier.get());
+
+            if (!ids.isEmpty()) {
+                return ids;
+            }
+        }
+
         // Performs label selection according to the defined optimization behavior
         switch (optLabelSelection) {
             // No swapping of the labels
@@ -367,6 +410,54 @@
      * @param links the links where labels will be allocated
      * @param resourceConsumer the resource consumer
      * @param type the encapsulation type
+     * @param suggestedIdentifier used if available
+     * @return the list of links and associated labels
+     */
+    public Map<LinkKey, Identifier<?>> assignLabelToLinks(Set<Link> links,
+                                                          ResourceConsumer resourceConsumer,
+                                                          EncapsulationType type,
+                                                          Optional<Identifier<?>> suggestedIdentifier) {
+        // To preserve order of the links. This is important for MIN_SWAP behavior
+        Set<LinkKey> linkRequest = links.stream()
+                .map(LinkKey::linkKey)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+
+        Map<LinkKey, Identifier<?>> availableIds = findAvailableIDs(linkRequest, type, suggestedIdentifier);
+        if (availableIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        Set<Resource> resources = availableIds.entrySet().stream()
+                .flatMap(x -> Stream.of(
+                        Resources.discrete(
+                                x.getKey().src().deviceId(),
+                                x.getKey().src().port(),
+                                x.getValue()
+                        ).resource(),
+                        Resources.discrete(
+                                x.getKey().dst().deviceId(),
+                                x.getKey().dst().port(),
+                                x.getValue()
+                        ).resource()
+                ))
+                .collect(Collectors.toSet());
+
+        List<ResourceAllocation> allocations = resourceService.allocate(resourceConsumer,
+                ImmutableList.copyOf(resources));
+
+        if (allocations.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        return ImmutableMap.copyOf(availableIds);
+    }
+
+    /**
+     * Allocates labels and associates them to links.
+     *
+     * @param links the links where labels will be allocated
+     * @param resourceConsumer the resource consumer
+     * @param type the encapsulation type
      * @return the list of links and associated labels
      */
     public Map<LinkKey, Identifier<?>> assignLabelToLinks(Set<Link> links,
@@ -377,7 +468,7 @@
                 .map(LinkKey::linkKey)
                 .collect(Collectors.toCollection(LinkedHashSet::new));
 
-        Map<LinkKey, Identifier<?>> availableIds = findAvailableIDs(linkRequest, type);
+        Map<LinkKey, Identifier<?>> availableIds = findAvailableIDs(linkRequest, type, Optional.empty());
         if (availableIds.isEmpty()) {
             return Collections.emptyMap();
         }
@@ -414,14 +505,17 @@
      * @param links the links on which labels will be reserved
      * @param resourceConsumer the resource consumer
      * @param type the encapsulation type
+     * @param suggestedIdentifier used if available
      * @return the list of ports and associated labels
      */
     public Map<ConnectPoint, Identifier<?>> assignLabelToPorts(Set<Link> links,
                                                                ResourceConsumer resourceConsumer,
-                                                               EncapsulationType type) {
+                                                               EncapsulationType type,
+                                                               Optional<Identifier<?>> suggestedIdentifier) {
         Map<LinkKey, Identifier<?>> allocation = this.assignLabelToLinks(links,
-                                                                         resourceConsumer,
-                                                                         type);
+                resourceConsumer,
+                type,
+                suggestedIdentifier);
         if (allocation.isEmpty()) {
             return Collections.emptyMap();
         }
@@ -434,6 +528,32 @@
     }
 
     /**
+     * Allocates labels and associates them to source
+     * and destination ports of a link.
+     *
+     * @param links the links on which labels will be reserved
+     * @param resourceConsumer the resource consumer
+     * @param type the encapsulation type
+     * @return the list of ports and associated labels
+     */
+    public Map<ConnectPoint, Identifier<?>> assignLabelToPorts(Set<Link> links,
+                                                               ResourceConsumer resourceConsumer,
+                                                               EncapsulationType type) {
+        Map<LinkKey, Identifier<?>> allocation = this.assignLabelToLinks(links,
+                resourceConsumer,
+                type);
+        if (allocation.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        Map<ConnectPoint, Identifier<?>> finalAllocation = Maps.newHashMap();
+        allocation.forEach((link, value) -> {
+                finalAllocation.putIfAbsent(link.src(), value);
+                finalAllocation.putIfAbsent(link.dst(), value);
+            });
+        return ImmutableMap.copyOf(finalAllocation);
+    }
+
+    /**
      * Interface for selection algorithms of the labels.
      */
     public interface LabelSelection {