Implements [CORD-96] and [CORD-410]

Changes:
- Introduces L2TunnelHandler for managing the pws;
- Supports pws initiation and pws policy for olt<->vsg communication;
- Supports teardown and update;

Change-Id: If51272c91445f618727434606edd2491f93cc4dd
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 2d4cc45..1423e06 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -74,6 +74,7 @@
 import org.onosproject.net.packet.PacketContext;
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.topology.PathService;
 import org.onosproject.net.topology.TopologyService;
 import org.onosproject.routing.config.RouterConfig;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
@@ -134,6 +135,9 @@
     private NeighbourResolutionService neighbourResolutionService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    public PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -146,16 +150,16 @@
     DeviceService deviceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    FlowObjectiveService flowObjectiveService;
+    public FlowObjectiveService flowObjectiveService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     LinkService linkService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    MastershipService mastershipService;
+    public MastershipService mastershipService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    StorageService storageService;
+    public StorageService storageService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     MulticastRouteService multicastRouteService;
@@ -180,7 +184,7 @@
     IpHandler ipHandler = null;
     RoutingRulePopulator routingRulePopulator = null;
     public ApplicationId appId;
-    protected DeviceConfiguration deviceConfiguration = null;
+    public DeviceConfiguration deviceConfiguration = null;
 
     DefaultRoutingHandler defaultRoutingHandler = null;
     private TunnelHandler tunnelHandler = null;
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
index 96fe6c9..5f0ed35 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
@@ -16,36 +16,166 @@
 
 package org.onosproject.segmentrouting.pwaas;
 
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.RandomUtils;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.DefaultObjectiveContext;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.segmentrouting.SegmentRoutingManager;
+import org.onosproject.segmentrouting.SegmentRoutingService;
+import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.PwaasConfig;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.onosproject.net.flowobjective.ForwardingObjective.Flag.VERSATILE;
+import static org.onosproject.segmentrouting.pwaas.L2Mode.TAGGED;
+import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Pipeline.INITIATION;
+import static org.onosproject.segmentrouting.pwaas.L2TunnelHandler.Result.*;
+
 /**
  * Handles pwaas related events.
  */
 public class L2TunnelHandler {
+
     private static final Logger log = LoggerFactory.getLogger(L2TunnelHandler.class);
-    private static final String CONFIG_NOT_FOUND = "Pwaas config not found";
+
+    private static final String FWD = "f";
+    private static final String REV = "r";
+
     private static final String NOT_MASTER = "Not master controller";
+    private static final String WRONG_TOPOLOGY = "Path in leaf-spine topology" +
+            " should always be two hops: ";
+
     private final SegmentRoutingManager srManager;
 
-    public L2TunnelHandler(SegmentRoutingManager srManager) {
-        this.srManager = srManager;
+    private final ConsistentMap<String, NextObjective> l2InitiationNextObjStore;
+
+    /**
+     * TODO a proper store is necessary to handle the policies and collisions.
+     */
+    private final KryoNamespace.Builder l2TunnelKryo;
+
+    /**
+     * Create a l2 tunnel handler for the deploy and
+     * for the tear down of pseudo wires.
+     *
+     * @param segmentRoutingManager the segment routing manager
+     */
+    public L2TunnelHandler(SegmentRoutingManager segmentRoutingManager) {
+        srManager = segmentRoutingManager;
+        l2TunnelKryo = new KryoNamespace.Builder()
+                .register(KryoNamespaces.API);
+
+        l2InitiationNextObjStore = srManager.storageService
+                .<String, NextObjective>consistentMapBuilder()
+                .withName("onos-l2initiation-nextobj-store")
+                .withSerializer(Serializer.using(l2TunnelKryo.build()))
+                .build();
     }
 
     /**
      * Processes Pwaas Config added event.
      *
-     * @param event network config added event
+     * @param event network config add event
      */
     public void processPwaasConfigAdded(NetworkConfigEvent event) {
         log.info("Processing Pwaas CONFIG_ADDED");
         PwaasConfig config = (PwaasConfig) event.config().get();
-        config.getPwIds().forEach(pwId -> {
-            log.info("{}", config.getPwDescription(pwId));
-        });
+        Set<DefaultL2TunnelDescription> pwToAdd = config.getPwIds()
+                .stream()
+                .map(config::getPwDescription)
+                .collect(Collectors.toSet());
+        // We deploy all the pseudo wire deployed
+        deploy(pwToAdd);
+    }
+
+    private void deploy(Set<DefaultL2TunnelDescription> pwToAdd) {
+        Result result;
+        long l2TunnelId;
+        for (DefaultL2TunnelDescription currentL2Tunnel : pwToAdd) {
+            l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId();
+            // The tunnel id cannot be 0.
+            if (l2TunnelId == 0) {
+                log.warn("Tunnel id cannot be 0");
+                continue;
+            }
+            // We do a sanity check of the pseudo wire.
+            result = verifyPseudoWire(currentL2Tunnel);
+            if (result != SUCCESS) {
+                continue;
+            }
+            // We establish the tunnel.
+            result = deployPseudoWire(
+                    currentL2Tunnel.l2Tunnel(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    FWD
+            );
+            if (result != SUCCESS) {
+                continue;
+            }
+            // We create the policy.
+            result = deployPolicy(
+                    l2TunnelId,
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
+                    result.nextId
+            );
+            if (result != SUCCESS) {
+                continue;
+            }
+            // We establish the reverse tunnel.
+            result = deployPseudoWire(
+                    currentL2Tunnel.l2Tunnel(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    REV
+            );
+            if (result != SUCCESS) {
+                continue;
+            }
+            deployPolicy(
+                    l2TunnelId,
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
+                    result.nextId
+            );
+        }
     }
 
     /**
@@ -55,9 +185,161 @@
      */
     public void processPwaasConfigUpdated(NetworkConfigEvent event) {
         log.info("Processing Pwaas CONFIG_UPDATED");
+        // We retrieve the old pseudo wires.
+        PwaasConfig prevConfig = (PwaasConfig) event.prevConfig().get();
+        Set<Long> prevPws = prevConfig.getPwIds();
+        // We retrieve the new pseudo wires.
         PwaasConfig config = (PwaasConfig) event.config().get();
-        config.getPwIds().forEach(pwId -> {
-            log.info("{}", config.getPwDescription(pwId));
+        Set<Long> newPws = config.getPwIds();
+        // We compute the pseudo wires to update.
+        Set<Long> updPws = newPws.stream()
+                .filter(tunnelId -> prevPws.contains(tunnelId) &&
+                !config.getPwDescription(tunnelId).equals(prevConfig.getPwDescription(tunnelId)))
+                .collect(Collectors.toSet());
+        // The pseudo wires to remove.
+        Set<DefaultL2TunnelDescription> pwToRemove = prevPws.stream()
+                .filter(tunnelId -> !newPws.contains(tunnelId))
+                .map(prevConfig::getPwDescription)
+                .collect(Collectors.toSet());
+        tearDown(pwToRemove);
+        // The pseudo wires to add.
+        Set<DefaultL2TunnelDescription> pwToAdd = newPws.stream()
+                .filter(tunnelId -> !prevPws.contains(tunnelId))
+                .map(config::getPwDescription)
+                .collect(Collectors.toSet());
+        deploy(pwToAdd);
+        // The pseudo wires to update.
+        updPws.forEach(tunnelId -> {
+            updatePw(
+                    prevConfig.getPwDescription(tunnelId),
+                    config.getPwDescription(tunnelId)
+            );
+        });
+    }
+
+    /**
+     * Helper function to update a pw.
+     *
+     * @param oldPw the pseudo wire to remove
+     * @param newPw the pseudo wirte to add
+     */
+    private void updatePw(DefaultL2TunnelDescription oldPw,
+                          DefaultL2TunnelDescription newPw) {
+
+        long tunnelId = oldPw.l2Tunnel().tunnelId();
+        String fwdKey = generateKey(tunnelId, FWD);
+        String revKey = generateKey(tunnelId, REV);
+        Result result;
+        NextObjective fwdNextObjective;
+        NextObjective revNextObjective;
+        // The async tasks to orchestrate the next and
+        // forwarding update.
+        CompletableFuture<ObjectiveError> revPolicyFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> newPwFuture = new CompletableFuture<>();
+
+        result = verifyPseudoWire(newPw);
+        if (result != SUCCESS) {
+            return;
+        }
+        if (!l2InitiationNextObjStore.containsKey(fwdKey)) {
+            log.warn("NextObj for {} does not exist in the store.", fwdKey);
+            return;
+        }
+        fwdNextObjective = l2InitiationNextObjStore.get(fwdKey).value();
+        if (!l2InitiationNextObjStore.containsKey(revKey)) {
+            log.warn("NextObj for {} does not exist in the store.", revKey);
+            return;
+        }
+        // First we remove both policy.
+        revNextObjective = l2InitiationNextObjStore.get(revKey).value();
+        log.debug("Start deleting fwd policy for {}", tunnelId);
+        deletePolicy(
+                tunnelId,
+                oldPw.l2TunnelPolicy().cP1(),
+                oldPw.l2TunnelPolicy().cP1InnerTag(),
+                oldPw.l2TunnelPolicy().cP1OuterTag(),
+                fwdNextObjective.id(),
+                revPolicyFuture
+        );
+        revPolicyFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                log.debug("Fwd policy removed. Now remove rev policy for {}", tunnelId);
+                deletePolicy(
+                        tunnelId,
+                        oldPw.l2TunnelPolicy().cP2(),
+                        oldPw.l2TunnelPolicy().cP2InnerTag(),
+                        oldPw.l2TunnelPolicy().cP2OuterTag(),
+                        revNextObjective.id(),
+                        fwdInitNextFuture
+                );
+            }
+        });
+        // Finally we remove both the tunnels.
+        fwdInitNextFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                log.debug("Rev policy removed. Now remove fwd pw for {}", tunnelId);
+                tearDownPseudoWire(
+                        fwdKey,
+                        fwdNextObjective,
+                        oldPw.l2TunnelPolicy().cP1(),
+                        oldPw.l2TunnelPolicy().cP2(),
+                        revInitNextFuture
+                );
+            }
+        });
+        revInitNextFuture.thenAcceptAsync(status -> {
+           if (status == null) {
+               log.debug("Fwd tunnel removed. Now remove rev pw for {}", tunnelId);
+               tearDownPseudoWire(
+                       revKey,
+                       revNextObjective,
+                       oldPw.l2TunnelPolicy().cP2(),
+                       oldPw.l2TunnelPolicy().cP1(),
+                       newPwFuture
+               );
+
+           }
+        });
+        // At the end we install the new pw.
+        newPwFuture.thenAcceptAsync(status -> {
+            if (status == null) {
+                log.debug("Deploying new fwd pw for {}", tunnelId);
+                Result lamdaResult = deployPseudoWire(
+                        newPw.l2Tunnel(),
+                        newPw.l2TunnelPolicy().cP1(),
+                        newPw.l2TunnelPolicy().cP2(),
+                        FWD
+                );
+                if (lamdaResult != SUCCESS) {
+                    return;
+                }
+                lamdaResult = deployPolicy(
+                        tunnelId,
+                        newPw.l2TunnelPolicy().cP1(),
+                        newPw.l2TunnelPolicy().cP1InnerTag(),
+                        newPw.l2TunnelPolicy().cP1OuterTag(),
+                        lamdaResult.nextId
+                );
+                log.debug("Deploying new rev pw for {}", tunnelId);
+                lamdaResult = deployPseudoWire(
+                        newPw.l2Tunnel(),
+                        newPw.l2TunnelPolicy().cP2(),
+                        newPw.l2TunnelPolicy().cP1(),
+                        REV
+                );
+                if (lamdaResult != SUCCESS) {
+                    return;
+                }
+                lamdaResult = deployPolicy(
+                        tunnelId,
+                        newPw.l2TunnelPolicy().cP2(),
+                        newPw.l2TunnelPolicy().cP2InnerTag(),
+                        newPw.l2TunnelPolicy().cP2OuterTag(),
+                        lamdaResult.nextId
+                );
+            }
         });
     }
 
@@ -68,9 +350,685 @@
      */
     public void processPwaasConfigRemoved(NetworkConfigEvent event) {
         log.info("Processing Pwaas CONFIG_REMOVED");
-        PwaasConfig config = (PwaasConfig) event.config().get();
-        config.getPwIds().forEach(pwId -> {
-            log.info("{}", config.getPwDescription(pwId));
-        });
+        PwaasConfig config = (PwaasConfig) event.prevConfig().get();
+        Set<DefaultL2TunnelDescription> pwToRemove = config.getPwIds()
+                .stream()
+                .map(config::getPwDescription)
+                .collect(Collectors.toSet());
+        // We teardown all the pseudo wire deployed
+        tearDown(pwToRemove);
     }
+
+    /**
+     * Helper function to handle the pw removal.
+     *
+     * @param pwToRemove the pseudo wires to remove
+     */
+    private void tearDown(Set<DefaultL2TunnelDescription> pwToRemove) {
+        Result result;
+        int nextId;
+        NextObjective nextObjective;
+        long l2TunnelId;
+        // We remove all the pw in the configuration
+        // file.
+        for (DefaultL2TunnelDescription currentL2Tunnel : pwToRemove) {
+            l2TunnelId = currentL2Tunnel.l2Tunnel().tunnelId();
+            if (l2TunnelId == 0) {
+                log.warn("Tunnel id cannot be 0");
+                continue;
+            }
+            result = verifyPseudoWire(currentL2Tunnel);
+            if (result != SUCCESS) {
+                continue;
+            }
+            String key = generateKey(l2TunnelId, FWD);
+            if (!l2InitiationNextObjStore.containsKey(key)) {
+                log.warn("NextObj for {} does not exist in the store.", key);
+                continue;
+            }
+            nextObjective = l2InitiationNextObjStore.get(key).value();
+            nextId = nextObjective.id();
+            // First all we have to delete the policy.
+            deletePolicy(
+                    l2TunnelId,
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1InnerTag(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1OuterTag(),
+                    nextId,
+                    null
+            );
+            // Finally we will tear down the pseudo wire.
+            tearDownPseudoWire(
+                    key,
+                    nextObjective,
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    null
+            );
+            // We do the same operations on the reverse side.
+            key = generateKey(l2TunnelId, REV);
+            if (!l2InitiationNextObjStore.containsKey(key)) {
+                log.warn("NextObj for {} does not exist in the store.", key);
+                continue;
+            }
+            nextObjective = l2InitiationNextObjStore.get(key).value();
+            nextId = nextObjective.id();
+            deletePolicy(
+                    l2TunnelId,
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2InnerTag(),
+                    currentL2Tunnel.l2TunnelPolicy().cP2OuterTag(),
+                    nextId,
+                    null
+            );
+            tearDownPseudoWire(
+                    key,
+                    nextObjective,
+                    currentL2Tunnel.l2TunnelPolicy().cP2(),
+                    currentL2Tunnel.l2TunnelPolicy().cP1(),
+                    null
+            );
+        }
+
+    }
+
+    /**
+     * Helper method to verify the integrity of the pseudo wire.
+     *
+     * @param l2TunnelDescription the pseudo wire description
+     * @return the result of the check
+     */
+    private Result verifyPseudoWire(DefaultL2TunnelDescription l2TunnelDescription) {
+        Result result;
+        DefaultL2Tunnel l2Tunnel = l2TunnelDescription.l2Tunnel();
+        DefaultL2TunnelPolicy l2TunnelPolicy = l2TunnelDescription.l2TunnelPolicy();
+        result = verifyTunnel(l2Tunnel);
+        if (result != SUCCESS) {
+            log.warn("Tunnel {} did not pass the validation", l2Tunnel.tunnelId());
+            return result;
+        }
+        result = verifyPolicy(
+                l2TunnelPolicy.isAllVlan(),
+                l2TunnelPolicy.cP1InnerTag(),
+                l2TunnelPolicy.cP1OuterTag(),
+                l2TunnelPolicy.cP2InnerTag(),
+                l2TunnelPolicy.cP2OuterTag()
+        );
+        if (result != SUCCESS) {
+            log.warn("Policy for tunnel {} did not pass the validation", l2Tunnel.tunnelId());
+            return result;
+        }
+
+        return SUCCESS;
+    }
+
+    /**
+     * TODO Operation on the policies store.
+     *
+     * Handles the policy establishment which consists in
+     * create the filtering and forwarding objectives related
+     * to the initiation and termination.
+     *
+     * @param tunnelId the tunnel id
+     * @param ingress the ingress point
+     * @param ingressInner the ingress inner tag
+     * @param ingressOuter the ingress outer tag
+     * @param nextId the next objective id
+     * @return SUCCESS if the policy has been deployed.
+     * Otherwise an error according to the failure
+     * scenario.
+     */
+    private Result deployPolicy(long tunnelId,
+                                ConnectPoint ingress,
+                                VlanId ingressInner,
+                                VlanId ingressOuter,
+                                int nextId) {
+
+        ForwardingObjective.Builder fwdBuilder;
+        FilteringObjective.Builder filtBuilder;
+        List<Objective> objectives = Lists.newArrayList();
+        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
+            log.info("Abort creation of policy for L2 tunnel {}: {}", tunnelId, NOT_MASTER);
+            return SUCCESS;
+        }
+        // We create the forwarding objective for supporting
+        // the l2 tunnel.
+        fwdBuilder = createFwdObjective(
+                INITIATION,
+                tunnelId,
+                ingress.port(),
+                nextId
+        );
+        // We create and add objective context.
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective)
+                        -> log.debug("FwdObj for tunnel {} populated", tunnelId),
+                (objective, error)
+                        -> log.warn("Failed to populate fwdrObj for tunnel {}", tunnelId, error));
+        objectives.add(fwdBuilder.add(context));
+        // We create the filtering objective to define the
+        // permit traffic in the switch
+        filtBuilder = createFiltObjective(
+                ingress.port(),
+                ingressInner,
+                ingressOuter
+        );
+        // We add the metadata.
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .setTunnelId(tunnelId);
+        filtBuilder.withMeta(treatment.build());
+        // We create and add objective context.
+        context = new DefaultObjectiveContext(
+                (objective)
+                        -> log.debug("FilterObj for tunnel {} populated", tunnelId),
+                (objective, error)
+                        -> log.warn("Failed to populate filterObj for tunnel {}", tunnelId, error));
+        objectives.add(filtBuilder.add(context));
+
+        for (Objective objective : objectives) {
+            if (objective instanceof ForwardingObjective) {
+                srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
+                log.debug("Creating new FwdObj for NextObj with id={} for tunnel {}", nextId, tunnelId);
+            } else {
+                srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
+                log.debug("Creating new FiltObj for tunnel {}", tunnelId);
+            }
+        }
+        return SUCCESS;
+    }
+
+    /**
+     * Helper method to verify if the policy is whether or not
+     * supported.
+     *
+     * @param isAllVlan all vlan mode
+     * @param ingressInner the ingress inner tag
+     * @param ingressOuter the ingress outer tag
+     * @param egressInner the egress inner tag
+     * @param egressOuter the egress outer tag
+     * @return the result of verification
+     */
+    private Result verifyPolicy(boolean isAllVlan,
+                                VlanId ingressInner,
+                                VlanId ingressOuter,
+                                VlanId egressInner,
+                                VlanId egressOuter) {
+        // AllVlan mode is not supported yet.
+        if (isAllVlan) {
+            log.warn("AllVlan not supported yet");
+            return UNSUPPORTED;
+        }
+        // The vlan tags for cP1 and cP2 have to be different from
+        // vlan none.
+        if (ingressInner.equals(VlanId.NONE) ||
+                ingressOuter.equals(VlanId.NONE) ||
+                egressInner.equals(VlanId.NONE) ||
+                egressOuter.equals(VlanId.NONE)) {
+            log.warn("The vlan tags for the connect point have to be" +
+                             "different from vlan none");
+            return WRONG_PARAMETERS;
+        }
+        return SUCCESS;
+    }
+
+    /**
+     * TODO Operation on the policies store.
+     *
+     * Handles the tunnel establishment which consists in
+     * create the next objectives related to the initiation
+     * and termination.
+     *
+     * @param l2Tunnel the tunnel to deploy
+     * @param ingress the ingress connect point
+     * @param egress the egress connect point
+     * @param direction the direction of the pw
+     * @return SUCCESS if the tunnel has been created.
+     * Otherwise an error according to the failure
+     * scenario
+     */
+    private Result deployPseudoWire(DefaultL2Tunnel l2Tunnel,
+                                    ConnectPoint ingress,
+                                    ConnectPoint egress,
+                                    String direction) {
+        Link nextHop;
+        NextObjective.Builder nextObjectiveBuilder;
+        NextObjective nextObjective;
+        int nextId;
+        Result result;
+        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
+            log.info("Abort initiation creation of L2 tunnel {}: {}",
+                     l2Tunnel.tunnelId(), NOT_MASTER);
+            return SUCCESS;
+        }
+        // We need at least a path between ingress and egress.
+        nextHop = getNextHop(ingress, egress);
+        if (nextHop == null) {
+            log.warn("No path between ingress and egress");
+            return WRONG_PARAMETERS;
+        }
+        // We create the next objective without the metadata
+        // context and id. We check if it already exists in the
+        // store. If not we store as it is in the store ?
+        nextObjectiveBuilder = createNextObjective(
+                INITIATION,
+                nextHop,
+                l2Tunnel,
+                egress.deviceId()
+        );
+        if (nextObjectiveBuilder == null) {
+            return INTERNAL_ERROR;
+        }
+        // We set the metadata. We will use this metadata
+        // to inform the driver we are doing a l2 tunnel.
+        TrafficSelector metadata = DefaultTrafficSelector
+                .builder()
+                .matchTunnelId(l2Tunnel.tunnelId())
+                .build();
+        nextObjectiveBuilder.withMeta(metadata);
+        nextId = srManager.flowObjectiveService.allocateNextId();
+        if (nextId < 0) {
+            log.warn("Not able to allocate a next id for initiation");
+            return INTERNAL_ERROR;
+        }
+        nextObjectiveBuilder.withId(nextId);
+        String key = generateKey(l2Tunnel.tunnelId(), direction);
+        l2InitiationNextObjStore.put(key, nextObjectiveBuilder.add());
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective)
+                        -> log.debug("Initiation l2 tunnel rule for {} populated",
+                                     l2Tunnel.tunnelId()),
+                (objective, error)
+                        -> log.warn("Failed to populate Initiation l2 tunnel rule for {}: {}",
+                                    l2Tunnel.tunnelId(), error));
+        nextObjective = nextObjectiveBuilder.add(context);
+        srManager.flowObjectiveService.next(ingress.deviceId(), nextObjective);
+        log.debug("Initiation next objective for {} not found. Creating new NextObj with id={}",
+                  l2Tunnel.tunnelId(),
+                  nextObjective.id()
+        );
+        result = SUCCESS;
+        result.nextId = nextObjective.id();
+        return result;
+    }
+
+    /**
+     * Helper method to verify if the tunnel is whether or not
+     * supported.
+     *
+     * @param l2Tunnel the tunnel to verify
+     * @return the result of the verification
+     */
+    private Result verifyTunnel(DefaultL2Tunnel l2Tunnel) {
+        // Service delimiting tag not supported yet.
+        if (!l2Tunnel.sdTag().equals(VlanId.NONE)) {
+            log.warn("Service delimiting tag not supported yet");
+            return UNSUPPORTED;
+        }
+        // Tag mode not supported yet.
+        if (l2Tunnel.pwMode() == TAGGED) {
+            log.warn("Tagged mode not supported yet");
+            return UNSUPPORTED;
+        }
+        // Raw mode without service delimiting tag
+        // is the only mode supported for now.
+        return SUCCESS;
+    }
+
+    /**
+     * Create the filtering objective according to a given policy.
+     *
+     * @param inPort the in port
+     * @param innerTag the inner vlan tag
+     * @param outerTag the outer vlan tag
+     * @return the filtering objective
+     */
+    private FilteringObjective.Builder createFiltObjective(PortNumber inPort,
+                                                           VlanId innerTag,
+                                                           VlanId outerTag) {
+        return DefaultFilteringObjective.builder()
+                .withKey(Criteria.matchInPort(inPort))
+                .addCondition(Criteria.matchInnerVlanId(innerTag))
+                .addCondition(Criteria.matchVlanId(outerTag))
+                .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
+                .permit()
+                .fromApp(srManager.appId);
+    }
+
+    /**
+     * Create the forwarding objective according to a given pipeline.
+     *
+     * @param pipeline the pipeline
+     * @param tunnelId the tunnel id
+     * @param nextId the next step
+     * @return the forwarding objective to support the pipeline.
+     */
+    private ForwardingObjective.Builder createFwdObjective(Pipeline pipeline,
+                                                           long tunnelId,
+                                                           PortNumber inPort,
+                                                           int nextId) {
+        ForwardingObjective.Builder fwdBuilder = null;
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector
+                .builder();
+        if (pipeline == INITIATION) {
+            // The flow has to match on the mpls logical
+            // port and the tunnel id.
+            trafficSelector.matchTunnelId(tunnelId);
+            trafficSelector.matchInPort(inPort);
+            fwdBuilder = DefaultForwardingObjective.builder()
+                    .fromApp(srManager.appId)
+                    .makePermanent()
+                    .nextStep(nextId)
+                    .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
+                    .withSelector(trafficSelector.build())
+                    .withFlag(VERSATILE);
+        }
+        return fwdBuilder;
+
+    }
+
+    /**
+     * Creates the next objective according to a given
+     * pipeline. We don't set the next id and we don't
+     * create the final meta to check if we are re-using
+     * the same next objective for different tunnels.
+     *
+     * @param pipeline the pipeline to support
+     * @param nextHop the next hop towards the destination
+     * @param l2Tunnel the tunnel to support
+     * @param egressId the egress device id
+     * @return the next objective to support the pipeline
+     */
+    private NextObjective.Builder createNextObjective(Pipeline pipeline,
+                                                      Link nextHop,
+                                                      DefaultL2Tunnel l2Tunnel,
+                                                      DeviceId egressId) {
+        NextObjective.Builder nextObjBuilder;
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        if (pipeline == INITIATION) {
+            nextObjBuilder = DefaultNextObjective
+                    .builder()
+                    .withType(NextObjective.Type.SIMPLE)
+                    .fromApp(srManager.appId);
+            // The pw label is the bottom of stack. It has to
+            // be different -1.
+            if (l2Tunnel.pwLabel().toInt() == MplsLabel.MAX_MPLS) {
+                log.warn("Pw label not configured");
+                return null;
+            }
+            treatmentBuilder.pushMpls();
+            treatmentBuilder.setMpls(l2Tunnel.pwLabel());
+            treatmentBuilder.setMplsBos(true);
+            treatmentBuilder.copyTtlOut();
+            // If the inter-co label is present we have to set the label.
+            if (l2Tunnel.interCoLabel().toInt() != MplsLabel.MAX_MPLS) {
+                treatmentBuilder.pushMpls();
+                treatmentBuilder.setMpls(l2Tunnel.interCoLabel());
+                treatmentBuilder.setMplsBos(false);
+                treatmentBuilder.copyTtlOut();
+            }
+            // We retrieve the sr label from the config
+            // using the egress leaf device id.
+            MplsLabel srLabel;
+            try {
+                 srLabel = MplsLabel.mplsLabel(
+                         srManager.deviceConfiguration.getIPv4SegmentId(egressId)
+                 );
+            } catch (DeviceConfigNotFoundException e) {
+                log.warn("Sr label not configured");
+                return null;
+            }
+            treatmentBuilder.pushMpls();
+            treatmentBuilder.setMpls(srLabel);
+            treatmentBuilder.setMplsBos(false);
+            treatmentBuilder.copyTtlOut();
+            // We have to rewrite the src and dst mac address.
+            MacAddress ingressMac;
+            try {
+                ingressMac = srManager
+                        .deviceConfiguration
+                        .getDeviceMac(nextHop.src().deviceId());
+            } catch (DeviceConfigNotFoundException e) {
+                log.warn("Was not able to find the ingress mac");
+                return null;
+            }
+            treatmentBuilder.setEthSrc(ingressMac);
+            MacAddress neighborMac;
+            try {
+                neighborMac = srManager
+                        .deviceConfiguration
+                        .getDeviceMac(nextHop.dst().deviceId());
+            } catch (DeviceConfigNotFoundException e) {
+                log.warn("Was not able to find the neighbor mac");
+                return null;
+            }
+            treatmentBuilder.setEthDst(neighborMac);
+        } else {
+            nextObjBuilder = DefaultNextObjective
+                    .builder()
+                    .withType(NextObjective.Type.SIMPLE)
+                    .fromApp(srManager.appId);
+
+        }
+        treatmentBuilder.setOutput(nextHop.src().port());
+        nextObjBuilder.addTreatment(treatmentBuilder.build());
+        return nextObjBuilder;
+    }
+
+    /**
+     * Returns the next hop.
+     *
+     * @param srcCp the ingress connect point
+     * @param dstCp the egress connect point
+     * @return the next hop
+     */
+    private Link getNextHop(ConnectPoint srcCp, ConnectPoint dstCp) {
+        // We retrieve a set of disjoint paths.
+        Set<DisjointPath> paths = srManager.pathService.getDisjointPaths(
+                srcCp.elementId(),
+                dstCp.elementId()
+        );
+        // We randmly pick a path.
+        if (paths.isEmpty()) {
+            return null;
+        }
+        int size = paths.size();
+        int index = RandomUtils.nextInt(0, size);
+        // We verify if the path is ok and there is not
+        // a misconfiguration.
+        List<Link> links = Iterables.get(paths, index).links();
+        checkState(links.size() == 2, WRONG_TOPOLOGY, links);
+        return links.get(0);
+    }
+
+    /**
+     * TODO Operation on the store.
+     * Deletes a given policy using the parameter supplied.
+     *
+     * @param tunnelId the tunnel id
+     * @param ingress the ingress point
+     * @param ingressInner the ingress inner vlan id
+     * @param ingressOuter the ingress outer vlan id
+     * @param nextId the next objective id
+     */
+    private void deletePolicy(long tunnelId,
+                              ConnectPoint ingress,
+                              VlanId ingressInner,
+                              VlanId ingressOuter,
+                              int nextId,
+                              CompletableFuture<ObjectiveError> fwdFuture) {
+
+        ForwardingObjective.Builder fwdBuilder;
+        FilteringObjective.Builder filtBuilder;
+        List<Objective> objectives = Lists.newArrayList();
+        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
+            log.info("Abort delete of policy for L2 tunnel {}: {}", tunnelId, NOT_MASTER);
+            return;
+        }
+        // We create the forwarding objective.
+        fwdBuilder = createFwdObjective(
+                INITIATION,
+                tunnelId,
+                ingress.port(),
+                nextId
+        );
+        ObjectiveContext context = new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Previous FwdObj for policy {} removed", tunnelId);
+                if (fwdFuture != null) {
+                    fwdFuture.complete(null);
+                }
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                log.warn("Failed to remove previous FwdObj for policy {}: {}", tunnelId, error);
+                if (fwdFuture != null) {
+                    fwdFuture.complete(error);
+                }
+            }
+        };
+        objectives.add(fwdBuilder.remove(context));
+        // We create the filtering objective to define the
+        // permit traffic in the switch
+        filtBuilder = createFiltObjective(
+                ingress.port(),
+                ingressInner,
+                ingressOuter
+        );
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .setTunnelId(tunnelId);
+        filtBuilder.withMeta(treatment.build());
+        context = new DefaultObjectiveContext(
+                (objective)
+                        -> log.debug("FilterObj for policy {} revoked", tunnelId),
+                (objective, error)
+                        -> log.warn("Failed to revoke filterObj for policy {}", tunnelId, error));
+        objectives.add(filtBuilder.remove(context));
+
+        for (Objective objective : objectives) {
+            if (objective instanceof ForwardingObjective) {
+                srManager.flowObjectiveService.forward(ingress.deviceId(), (ForwardingObjective) objective);
+            } else {
+                srManager.flowObjectiveService.filter(ingress.deviceId(), (FilteringObjective) objective);
+            }
+        }
+    }
+
+    /**
+     * TODO Operation on the store.
+     * Deletes a given pseudo wire using the parameter supplied.
+     *
+     * @param key the key of the store
+     * @param nextObjective the next objective representing the pw
+     * @param ingress the ingress connect point
+     * @param egress the egress connect point
+     */
+    private void tearDownPseudoWire(String key,
+                                    NextObjective nextObjective,
+                                    ConnectPoint ingress,
+                                    ConnectPoint egress,
+                                    CompletableFuture<ObjectiveError> nextFutureForInit) {
+        if (!srManager.mastershipService.isLocalMaster(ingress.deviceId())) {
+            log.info("Abort delete of {} for {}: {}", INITIATION, key, NOT_MASTER);
+            return;
+        }
+        ObjectiveContext context = new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Previous {} NextObj for {} removed", INITIATION, key);
+                if (nextFutureForInit != null) {
+                    nextFutureForInit.complete(null);
+                }
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                log.warn("Failed to remove previous {} NextObj for {}: {}", INITIATION, key, error);
+                if (nextFutureForInit != null) {
+                    nextFutureForInit.complete(error);
+                }
+            }
+        };
+        srManager.flowObjectiveService.next(
+                ingress.deviceId(),
+                (NextObjective) nextObjective.copy().remove(context)
+        );
+        l2InitiationNextObjStore.remove(key);
+    }
+
+    /**
+     * Utilities to generate pw key.
+     *
+     * @param tunnelId the tunnel id
+     * @param direction the direction of the pw
+     * @return the key of the store
+     */
+    private String generateKey(long tunnelId, String direction) {
+        return String.format("%s-%s", tunnelId, direction);
+    }
+
+    /**
+     * VPWS pipelines.
+     */
+    protected enum Pipeline {
+        /**
+         * The initiation pipeline.
+         */
+        INITIATION,
+        /**
+         * The termination pipeline.
+         */
+        TERMINATION;
+    }
+
+    /**
+     * Enum helper to carry the outcomes of an operation.
+     */
+    public enum Result {
+        /**
+         * Happy ending scenario it has been created.
+         */
+        SUCCESS(0, "It has been Created"),
+        /**
+         * We have problems with the supplied parameters.
+         */
+        WRONG_PARAMETERS(1, "Wrong parameters"),
+        /**
+         * It already exists.
+         */
+        ID_EXISTS(2, "The id already exists"),
+        /**
+         * We have an internal error during the deployment
+         * phase.
+         */
+        INTERNAL_ERROR(3, "Internal error"),
+        /**
+         * The operation is not supported.
+         */
+        UNSUPPORTED(4, "Unsupported");
+
+        private final int code;
+        private final String description;
+        private int nextId;
+
+        private Result(int code, String description) {
+            this.code = code;
+            this.description = description;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        @Override
+        public String toString() {
+            return code + ": " + description;
+        }
+    }
+
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
index 2a4de98..9e3161a 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
@@ -37,6 +37,7 @@
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
@@ -80,6 +81,7 @@
 import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT;
 import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT;
 import static org.onosproject.driver.pipeline.Ofdpa2Pipeline.isNotMplsBos;
+import static org.onosproject.net.flow.criteria.Criterion.Type.TUNNEL_ID;
 import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
 import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -284,32 +286,50 @@
         }
 
         boolean isMpls = false;
+        // In order to understand if it is a pseudo wire related
+        // next objective we look for the tunnel id in the meta.
+        boolean isPw = false;
         if (nextObj.meta() != null) {
             isMpls = isNotMplsBos(nextObj.meta());
+
+            TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) nextObj
+                    .meta()
+                    .getCriterion(TUNNEL_ID);
+            if (tunnelIdCriterion != null) {
+                isPw = true;
+            }
+
         }
 
-        // break up simple next objective to GroupChain objects
-        GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
-                                              nextObj.appId(), isMpls,
-                                              nextObj.meta());
-        if (groupInfo == null) {
-            log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
-            return;
+        if (!isPw) {
+            // break up simple next objective to GroupChain objects
+            GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
+                                                  nextObj.appId(), isMpls,
+                                                  nextObj.meta());
+            if (groupInfo == null) {
+                log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
+                return;
+            }
+            // create object for local and distributed storage
+            Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+            gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
+            gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
+            OfdpaNextGroup ofdpaGrp =
+                    new OfdpaNextGroup(Collections.singletonList(gkeyChain), nextObj);
+
+            // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
+            updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
+
+            // now we are ready to send the l2 groupDescription (inner), as all the stores
+            // that will get async replies have been updated. By waiting to update
+            // the stores, we prevent nasty race conditions.
+            groupService.addGroup(groupInfo.innerMostGroupDesc);
+        } else {
+            // We handle the pseudo wire with a different a procedure.
+            // This procedure is meant to handle both initiation and
+            // termination of the pseudo wire.
+            processPwNextObjective(nextObj);
         }
-        // create object for local and distributed storage
-        Deque<GroupKey> gkeyChain = new ArrayDeque<>();
-        gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
-        gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
-        OfdpaNextGroup ofdpaGrp =
-                new OfdpaNextGroup(Collections.singletonList(gkeyChain), nextObj);
-
-        // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
-        updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
-
-        // now we are ready to send the l2 groupDescription (inner), as all the stores
-        // that will get async replies have been updated. By waiting to update
-        // the stores, we prevent nasty race conditions.
-        groupService.addGroup(groupInfo.innerMostGroupDesc);
     }
 
     /**
@@ -387,8 +407,8 @@
      *         error in processing the chain
      */
     protected GroupInfo createL2L3ChainInternal(TrafficTreatment treatment, int nextId,
-                                      ApplicationId appId, boolean mpls,
-                                      TrafficSelector meta, boolean useSetVlanExtension) {
+                                                ApplicationId appId, boolean mpls,
+                                                TrafficSelector meta, boolean useSetVlanExtension) {
         // for the l2interface group, get vlan and port info
         // for the outer group, get the src/dst mac, and vlan info
         TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
@@ -973,6 +993,18 @@
         }
     }
 
+    /**
+     * Processes the pseudo wire related next objective.
+     * This procedure try to reuse the mpls label groups,
+     * the mpls interface group and the l2 interface group.
+     *
+     * @param nextObjective the objective to process.
+     */
+    protected void processPwNextObjective(NextObjective nextObjective) {
+        log.warn("Pseudo wire extensions are not support for the OFDPA 2.0 {}", nextObjective.id());
+        return;
+    }
+
     //////////////////////////////////////
     //  Group Editing
     //////////////////////////////////////
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
index a49e4fa..e32f5d5 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
@@ -102,8 +102,12 @@
  *
  */
 public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeliner {
+
     protected static final int PORT_TABLE = 0;
     protected static final int VLAN_TABLE = 10;
+    protected static final int VLAN_1_TABLE = 11;
+    protected static final int MPLS_L2_PORT_FLOW_TABLE = 13;
+    protected static final int MPLS_L2_PORT_PCP_TRUST_FLOW_TABLE = 16;
     protected static final int TMAC_TABLE = 20;
     protected static final int UNICAST_ROUTING_TABLE = 30;
     protected static final int MULTICAST_ROUTING_TABLE = 40;
@@ -120,6 +124,11 @@
     protected static final int DEFAULT_PRIORITY = 0x8000;
     protected static final int LOWEST_PRIORITY = 0x0;
 
+    protected static final int MPLS_L2_PORT_PRIORITY = 2;
+
+    protected static final int MPLS_TUNNEL_ID_BASE = 0x10000;
+    protected static final int MPLS_TUNNEL_ID_MAX = 0x1FFFF;
+
     private final Logger log = getLogger(getClass());
     protected ServiceDirectory serviceDirectory;
     protected FlowRuleService flowRuleService;
@@ -225,6 +234,7 @@
             rules.stream()
             .filter(Objects::nonNull)
             .forEach(flowOpsBuilder::remove);
+            log.debug("Deleting a flow rule to sw:{}", deviceId);
             break;
         default:
             fail(fwd, ObjectiveError.UNKNOWN);
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java
index 5ba0446..1c30333 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java
@@ -16,18 +16,338 @@
 
 package org.onosproject.driver.pipeline;
 
+import com.google.common.collect.Lists;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.driver.extensions.Ofdpa3PushCw;
+import org.onosproject.driver.extensions.Ofdpa3PushL2Header;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
 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.L2ModificationInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+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.slf4j.Logger;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.*;
+import static org.onosproject.net.flow.instructions.L3ModificationInstruction.L3SubType.TTL_OUT;
+import static org.onosproject.net.group.GroupDescription.Type.INDIRECT;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Group handler for OFDPA2 pipeline.
  */
 public class Ofdpa3GroupHandler extends Ofdpa2GroupHandler {
+
+    private static final int PW_INTERNAL_VLAN = 4094;
+    private static final int MAX_DEPTH_UNPROTECTED_PW = 3;
+
+    private final Logger log = getLogger(getClass());
+
     @Override
     protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
                                         ApplicationId appId, boolean mpls,
                                         TrafficSelector meta) {
         return createL2L3ChainInternal(treatment, nextId, appId, mpls, meta, false);
     }
+
+    @Override
+    protected void processPwNextObjective(NextObjective nextObjective) {
+        TrafficTreatment treatment = nextObjective.next().iterator().next();
+        Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+        GroupChainElem groupChainElem;
+        GroupKey groupKey;
+        GroupDescription groupDescription;
+        // Now we separate the mpls actions from the l2/l3 actions
+        TrafficTreatment.Builder l2L3Treatment = DefaultTrafficTreatment.builder();
+        TrafficTreatment.Builder mplsTreatment = DefaultTrafficTreatment.builder();
+        createL2L3AndMplsTreatments(treatment, l2L3Treatment, mplsTreatment);
+        // We create the chain from mpls intf group to
+        // l2 intf group.
+        GroupInfo groupInfo = createL2L3ChainInternal(
+                l2L3Treatment.build(),
+                nextObjective.id(),
+                nextObjective.appId(),
+                true,
+                nextObjective.meta(),
+                false
+        );
+        if (groupInfo == null) {
+            log.error("Could not process nextObj={} in dev:{}", nextObjective.id(), deviceId);
+            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.GROUPINSTALLATIONFAILED);
+            return;
+        }
+        // We update the chain with the last two groups;
+        gkeyChain.addFirst(groupInfo.getInnerMostGroupDesc().appCookie());
+        gkeyChain.addFirst(groupInfo.getNextGroupDesc().appCookie());
+        // We retrieve also all mpls instructions.
+        List<List<Instruction>> mplsInstructionSets = Lists.newArrayList();
+        List<Instruction> mplsInstructionSet = Lists.newArrayList();
+        L3ModificationInstruction l3Ins;
+        for (Instruction ins : treatment.allInstructions()) {
+            // Each mpls instruction set is delimited by a
+            // copy ttl outward action.
+            mplsInstructionSet.add(ins);
+            if (ins.type() == Instruction.Type.L3MODIFICATION) {
+                l3Ins = (L3ModificationInstruction) ins;
+                if (l3Ins.subtype() == TTL_OUT) {
+                    mplsInstructionSets.add(mplsInstructionSet);
+                    mplsInstructionSet = Lists.newArrayList();
+                }
+
+            }
+        }
+        if (mplsInstructionSets.size() > MAX_DEPTH_UNPROTECTED_PW) {
+            log.error("Next Objective for pseudo wire should have at "
+                              + "most {} mpls instruction sets. Next Objective Id:{}",
+                      MAX_DEPTH_UNPROTECTED_PW, nextObjective.id());
+            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+            return;
+        }
+        int nextGid = groupInfo.getNextGroupDesc().givenGroupId();
+        int index;
+        // We create the mpls tunnel label groups.
+        // In this case we need to use also the
+        // tunnel label group 2;
+        if (mplsInstructionSets.size() == MAX_DEPTH_UNPROTECTED_PW) {
+            // We deal with the label 2 group.
+            index = getNextAvailableIndex();
+            groupDescription = createMplsTunnelLabelGroup(
+                    nextGid,
+                    MPLS_TUNNEL_LABEL_2,
+                    index,
+                    mplsInstructionSets.get(2),
+                    nextObjective.appId()
+            );
+            groupKey = new DefaultGroupKey(
+                    Ofdpa2Pipeline.appKryo.serialize(index)
+            );
+            // We update the chain.
+            groupChainElem = new GroupChainElem(groupDescription, 1, false);
+            updatePendingGroups(
+                    groupInfo.getNextGroupDesc().appCookie(),
+                    groupChainElem
+            );
+            gkeyChain.addFirst(groupKey);
+            // We have to create tunnel label group and
+            // l2 vpn group before to send the inner most
+            // group. We update the nextGid.
+            nextGid = groupDescription.givenGroupId();
+            groupInfo = new GroupInfo(groupInfo.getInnerMostGroupDesc(), groupDescription);
+
+            log.debug("Trying Label 2 Group: device:{} gid:{} gkey:{} nextId:{}",
+                      deviceId, Integer.toHexString(nextGid),
+                      groupKey, nextObjective.id());
+        }
+        // We deal with the label 1 group.
+        index = getNextAvailableIndex();
+        groupDescription = createMplsTunnelLabelGroup(
+                nextGid,
+                MPLS_TUNNEL_LABEL_1,
+                index,
+                mplsInstructionSets.get(1),
+                nextObjective.appId()
+        );
+        groupKey = new DefaultGroupKey(
+                Ofdpa2Pipeline.appKryo.serialize(index)
+        );
+        groupChainElem = new GroupChainElem(groupDescription, 1, false);
+        updatePendingGroups(
+                groupInfo.getNextGroupDesc().appCookie(),
+                groupChainElem
+        );
+        gkeyChain.addFirst(groupKey);
+        // We have to create the l2 vpn group before
+        // to send the inner most group.
+        nextGid = groupDescription.givenGroupId();
+        groupInfo = new GroupInfo(groupInfo.getInnerMostGroupDesc(), groupDescription);
+
+        log.debug("Trying Label 1 Group: device:{} gid:{} gkey:{} nextId:{}",
+                  deviceId, Integer.toHexString(nextGid),
+                  groupKey, nextObjective.id());
+        // Finally we create the l2 vpn group.
+        index = getNextAvailableIndex();
+        groupDescription = createMplsL2VpnGroup(
+                nextGid,
+                index,
+                mplsInstructionSets.get(0),
+                nextObjective.appId()
+        );
+        groupKey = new DefaultGroupKey(
+                Ofdpa2Pipeline.appKryo.serialize(index)
+        );
+        groupChainElem = new GroupChainElem(groupDescription, 1, false);
+        updatePendingGroups(
+                groupInfo.getNextGroupDesc().appCookie(),
+                groupChainElem
+        );
+        gkeyChain.addFirst(groupKey);
+        OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
+                Collections.singletonList(gkeyChain),
+                nextObjective
+        );
+        updatePendingNextObjective(groupKey, ofdpaGrp);
+
+        log.debug("Trying L2 Vpn Group: device:{} gid:{} gkey:{} nextId:{}",
+                  deviceId, Integer.toHexString(nextGid),
+                  groupKey, nextObjective.id());
+        // Finally we send the innermost group.
+        log.debug("Sending innermost group {} in group chain on device {} ",
+                  Integer.toHexString(groupInfo.getInnerMostGroupDesc().givenGroupId()), deviceId);
+        groupService.addGroup(groupInfo.getInnerMostGroupDesc());
+    }
+
+    /**
+     * Helper method to create a mpls tunnel label group.
+     *
+     * @param nextGroupId the next group in the chain
+     * @param subtype the mpls tunnel label group subtype
+     * @param index the index of the group
+     * @param instructions the instructions to push
+     * @param applicationId the application id
+     * @return the group description
+     */
+    private GroupDescription createMplsTunnelLabelGroup(int nextGroupId,
+                                                          OfdpaMplsGroupSubType subtype,
+                                                          int index,
+                                                          List<Instruction> instructions,
+                                                          ApplicationId applicationId) {
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        // We add all the instructions.
+        instructions.forEach(treatment::add);
+        // We point the group to the next group.
+        treatment.group(new DefaultGroupId(nextGroupId));
+        GroupBucket groupBucket = DefaultGroupBucket
+                .createIndirectGroupBucket(treatment.build());
+        // Finally we build the group description.
+        int groupId = makeMplsLabelGroupId(subtype, index);
+        GroupKey groupKey = new DefaultGroupKey(
+                Ofdpa2Pipeline.appKryo.serialize(index)
+        );
+        return new DefaultGroupDescription(
+                deviceId,
+                INDIRECT,
+                new GroupBuckets(Collections.singletonList(groupBucket)),
+                groupKey,
+                groupId,
+                applicationId
+        );
+    }
+
+    /**
+     * Helper method to create a mpls l2 vpn group.
+     *
+     * @param nextGroupId the next group in the chain
+     * @param index the index of the group
+     * @param instructions the instructions to push
+     * @param applicationId the application id
+     * @return the group description
+     */
+    private GroupDescription createMplsL2VpnGroup(int nextGroupId,
+                                                    int index,
+                                                    List<Instruction> instructions,
+                                                    ApplicationId applicationId) {
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        // We add the extensions and the instructions.
+        treatment.extension(new Ofdpa3PushL2Header(), deviceId);
+        treatment.pushVlan();
+        instructions.forEach(treatment::add);
+        treatment.extension(new Ofdpa3PushCw(), deviceId);
+        // We point the group to the next group.
+        treatment.group(new DefaultGroupId(nextGroupId));
+        GroupBucket groupBucket = DefaultGroupBucket
+                .createIndirectGroupBucket(treatment.build());
+        // Finally we build the group description.
+        int groupId = makeMplsLabelGroupId(L2_VPN, index);
+        GroupKey groupKey = new DefaultGroupKey(
+                Ofdpa2Pipeline.appKryo.serialize(index)
+        );
+        return new DefaultGroupDescription(
+                deviceId,
+                INDIRECT,
+                new GroupBuckets(Collections.singletonList(groupBucket)),
+                groupKey,
+                groupId,
+                applicationId
+        );
+    }
+
+    /**
+     * Helper method for dividing the l2/l3 instructions from the mpls
+     * instructions.
+     *
+     * @param treatment the treatment to analyze
+     * @param l2L3Treatment the l2/l3 treatment builder
+     * @param mplsTreatment the mpls treatment builder
+     */
+    private void createL2L3AndMplsTreatments(TrafficTreatment treatment,
+                                               TrafficTreatment.Builder l2L3Treatment,
+                                               TrafficTreatment.Builder mplsTreatment) {
+
+        for (Instruction ins : treatment.allInstructions()) {
+
+            if (ins.type() == Instruction.Type.L2MODIFICATION) {
+                L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+                switch (l2ins.subtype()) {
+                    // These instructions have to go in the l2/l3 treatment.
+                    case ETH_DST:
+                    case ETH_SRC:
+                    case VLAN_ID:
+                    case VLAN_POP:
+                        l2L3Treatment.add(ins);
+                        break;
+                    // These instructions have to go in the mpls treatment.
+                    case MPLS_BOS:
+                    case DEC_MPLS_TTL:
+                    case MPLS_LABEL:
+                    case MPLS_PUSH:
+                        mplsTreatment.add(ins);
+                        break;
+                    default:
+                        log.warn("Driver does not handle this type of TrafficTreatment"
+                                         + " instruction in nextObjectives: {} - {}",
+                                 ins.type(), ins);
+                        break;
+                }
+            } else if (ins.type() == Instruction.Type.OUTPUT) {
+                // The output goes in the l2/l3 treatment.
+                l2L3Treatment.add(ins);
+            } else if (ins.type() == Instruction.Type.L3MODIFICATION) {
+                 // We support partially the l3 instructions.
+                L3ModificationInstruction l3ins = (L3ModificationInstruction) ins;
+                switch (l3ins.subtype()) {
+                    case TTL_OUT:
+                        mplsTreatment.add(ins);
+                        break;
+                    default:
+                        log.warn("Driver does not handle this type of TrafficTreatment"
+                                         + " instruction in nextObjectives: {} - {}",
+                                 ins.type(), ins);
+                }
+
+            } else {
+                log.warn("Driver does not handle this type of TrafficTreatment"
+                                 + " instruction in nextObjectives: {} - {}",
+                         ins.type(), ins);
+            }
+        }
+        // We add in a transparent way the set vlan to 4094.
+        l2L3Treatment.setVlanId(VlanId.vlanId((short) PW_INTERNAL_VLAN));
+    }
+    // TODO Introduce in the future an inner class to return two treatments
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
index 895c187..38fe384 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
@@ -16,21 +16,57 @@
 
 package org.onosproject.driver.pipeline;
 
+import com.google.common.collect.ImmutableList;
 import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.driver.extensions.Ofdpa3MatchMplsL2Port;
+import org.onosproject.driver.extensions.Ofdpa3MatchOvid;
+import org.onosproject.driver.extensions.Ofdpa3SetMplsL2Port;
+import org.onosproject.driver.extensions.Ofdpa3SetMplsType;
+import org.onosproject.driver.extensions.Ofdpa3SetOvid;
+import org.onosproject.driver.extensions.Ofdpa3SetQosIndex;
+import org.onosproject.driver.extensions.OfdpaMatchVlanVid;
+import org.onosproject.net.behaviour.NextGroup;
 import org.onosproject.net.behaviour.PipelinerContext;
+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.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType;
+import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupKey;
+import org.slf4j.Logger;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
 import java.util.List;
 
+import static org.onosproject.driver.extensions.Ofdpa3MplsType.VPWS;
+import static org.onosproject.net.flow.criteria.Criterion.Type.*;
+import static org.onosproject.net.flow.instructions.Instruction.Type.L2MODIFICATION;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModTunnelIdInstruction;
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Pipeliner for Broadcom OF-DPA 3.0 TTP.
  */
 public class Ofdpa3Pipeline extends Ofdpa2Pipeline {
+
+    private final Logger log = getLogger(getClass());
+
     @Override
     protected void initDriverId() {
         driverId = coreService.registerApplication(
@@ -44,6 +80,180 @@
     }
 
     @Override
+    protected void processFilter(FilteringObjective filteringObjective,
+                                 boolean install,
+                                 ApplicationId applicationId) {
+        // We are looking for inner vlan id criterion. We use this
+        // to identify the pseudo wire flows. In future we can enforce
+        // using also the tunnel id in the meta.
+        VlanIdCriterion innerVlanIdCriterion = null;
+        for (Criterion criterion : filteringObjective.conditions()) {
+            if (criterion.type() == INNER_VLAN_VID) {
+                innerVlanIdCriterion = (VlanIdCriterion) criterion;
+                break;
+            }
+        }
+        if (innerVlanIdCriterion != null) {
+            FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+            PortCriterion portCriterion;
+            VlanIdCriterion outerVlanIdCriterion = null;
+            // We extract the expected port criterion in the key.
+            portCriterion = (PortCriterion) filteringObjective.key();
+            // We extract the outer vlan id criterion.
+            for (Criterion criterion : filteringObjective.conditions()) {
+                if (criterion.type() == VLAN_VID) {
+                    outerVlanIdCriterion = (VlanIdCriterion) criterion;
+                    break;
+                }
+            }
+            // We extract the tunnel id.
+            long tunnelId;
+            if (filteringObjective.meta() != null &&
+                    filteringObjective.meta().allInstructions().size() != 1) {
+                log.warn("Bad filtering objective from app: {}. Not"
+                                 + "processing filtering objective", applicationId);
+                fail(filteringObjective, ObjectiveError.BADPARAMS);
+                return;
+            } else if (filteringObjective.meta() != null &&
+                    filteringObjective.meta().allInstructions().size() == 1 &&
+                    filteringObjective.meta().allInstructions().get(0).type() == L2MODIFICATION) {
+                L2ModificationInstruction l2instruction = (L2ModificationInstruction)
+                        filteringObjective.meta().allInstructions().get(0);
+                if (l2instruction.subtype() != L2SubType.TUNNEL_ID) {
+                    log.warn("Bad filtering objective from app: {}. Not"
+                                     + "processing filtering objective", applicationId);
+                    fail(filteringObjective, ObjectiveError.BADPARAMS);
+                    return;
+                } else {
+                    tunnelId = ((ModTunnelIdInstruction) l2instruction).tunnelId();
+                }
+            } else {
+                log.warn("Bad filtering objective from app: {}. Not"
+                                 + "processing filtering objective", applicationId);
+                fail(filteringObjective, ObjectiveError.BADPARAMS);
+                return;
+            }
+            // Mpls tunnel ids according to the OFDPA manual have to be
+            // in the range [2^17-1, 2^16].
+            tunnelId = MPLS_TUNNEL_ID_BASE | tunnelId;
+            // Sanity check for the filtering objective.
+            if (portCriterion == null ||
+                    outerVlanIdCriterion == null ||
+                    tunnelId > MPLS_TUNNEL_ID_MAX) {
+                log.warn("Bad filtering objective from app: {}. Not"
+                                 + "processing filtering objective", applicationId);
+                fail(filteringObjective, ObjectiveError.BADPARAMS);
+                return;
+            }
+            // 0x0000XXXX is UNI interface.
+            if (portCriterion.port().toLong() > 0x0000FFFF) {
+                log.error("Filering Objective invalid logical port {}",
+                          portCriterion.port().toLong());
+                fail(filteringObjective, ObjectiveError.BADPARAMS);
+                return;
+            }
+            // We create the flows.
+            List<FlowRule> pwRules = processPwFilter(portCriterion,
+                                                     innerVlanIdCriterion,
+                                                     outerVlanIdCriterion,
+                                                     tunnelId,
+                                                     applicationId
+            );
+            // We tag the flow for adding or for removing.
+            for (FlowRule pwRule : pwRules) {
+                log.debug("adding filtering rule in VLAN tables: {} for dev: {}",
+                          pwRule, deviceId);
+                ops = install ? ops.add(pwRule) : ops.remove(pwRule);
+            }
+            // We push the filtering rules for the pw.
+            flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
+                @Override
+                public void onSuccess(FlowRuleOperations ops) {
+                    log.info("Applied {} filtering rules in device {}",
+                             ops.stages().get(0).size(), deviceId);
+                    pass(filteringObjective);
+                }
+
+                @Override
+                public void onError(FlowRuleOperations ops) {
+                    log.info("Failed to apply all filtering rules in dev {}", deviceId);
+                    fail(filteringObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
+                }
+            }));
+
+            return;
+        }
+        // If it is not a pseudo wire flow we fall back
+        // to the OFDPA 2.0 pipeline.
+        super.processFilter(filteringObjective, install, applicationId);
+    }
+
+    /**
+     * Method to process the pw related filtering objectives.
+     *
+     * @param portCriterion the in port match
+     * @param innerVlanIdCriterion the inner vlan match
+     * @param outerVlanIdCriterion the outer vlan match
+     * @param tunnelId the tunnel id
+     * @param applicationId the application id
+     * @return a list of flow rules to install
+     */
+    private List<FlowRule> processPwFilter(PortCriterion portCriterion,
+                                             VlanIdCriterion innerVlanIdCriterion,
+                                             VlanIdCriterion outerVlanIdCriterion,
+                                             long tunnelId,
+                                             ApplicationId applicationId) {
+        // As first we create the flow rule for the vlan 1 table.
+        FlowRule vlan1FlowRule;
+        int mplsLogicalPort = ((int) portCriterion.port().toLong());
+        // We have to match on the inner vlan and outer vlan at the same time.
+        // Ofdpa supports this through the OVID meta-data type.
+        TrafficSelector.Builder vlan1Selector = DefaultTrafficSelector.builder()
+                .matchInPort(portCriterion.port())
+                .extension(new OfdpaMatchVlanVid(innerVlanIdCriterion.vlanId()), deviceId)
+                .extension(new Ofdpa3MatchOvid(outerVlanIdCriterion.vlanId()), deviceId);
+        // TODO understand for the future how to manage the vlan rewrite.
+        TrafficTreatment.Builder vlan1Treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(outerVlanIdCriterion.vlanId())
+                .extension(new Ofdpa3SetMplsType(VPWS), deviceId)
+                .extension(new Ofdpa3SetMplsL2Port(mplsLogicalPort), deviceId)
+                .setTunnelId(tunnelId)
+                .transition(MPLS_L2_PORT_FLOW_TABLE);
+        vlan1FlowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(vlan1Selector.build())
+                .withTreatment(vlan1Treatment.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .fromApp(applicationId)
+                .makePermanent()
+                .forTable(VLAN_1_TABLE)
+                .build();
+        // Finally we create the flow rule for the vlan table.
+        FlowRule vlanFlowRule;
+        // We have to match on the outer vlan.
+        TrafficSelector.Builder vlanSelector = DefaultTrafficSelector.builder()
+                .matchInPort(portCriterion.port())
+                .extension(new OfdpaMatchVlanVid(outerVlanIdCriterion.vlanId()), deviceId);
+        // TODO understand for the future how to manage the vlan rewrite.
+        TrafficTreatment.Builder vlanTreatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .extension(new Ofdpa3SetOvid(outerVlanIdCriterion.vlanId()), deviceId)
+                .transition(VLAN_1_TABLE);
+        vlanFlowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(vlanSelector.build())
+                .withTreatment(vlanTreatment.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .fromApp(applicationId)
+                .makePermanent()
+                .forTable(VLAN_TABLE)
+                .build();
+
+        return ImmutableList.of(vlan1FlowRule, vlanFlowRule);
+    }
+
+    @Override
     protected List<FlowRule> processVlanIdFilter(PortCriterion portCriterion,
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
@@ -59,4 +269,104 @@
         }
         return processEthTypeSpecificInternal(fwd, true, MPLS_L3_TYPE_TABLE);
     }
+
+    @Override
+    protected Collection<FlowRule> processVersatile(ForwardingObjective fwd) {
+        // We use the tunnel id to identify pw related flows.
+        TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) fwd.selector()
+                .getCriterion(TUNNEL_ID);
+        if (tunnelIdCriterion != null) {
+            return processPwVersatile(fwd);
+        }
+        // If it is not a pseudo wire flow we fall back
+        // to the OFDPA 2.0 pipeline.
+        return super.processVersatile(fwd);
+    }
+
+    /**
+     * Helper method to process the pw forwarding objectives.
+     *
+     * @param forwardingObjective the fw objective to process
+     * @return a singleton list of flow rule
+     */
+    private Collection<FlowRule> processPwVersatile(ForwardingObjective forwardingObjective) {
+        // We retrieve the matching criteria for mpls l2 port.
+        TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) forwardingObjective.selector()
+                .getCriterion(TUNNEL_ID);
+        PortCriterion portCriterion = (PortCriterion) forwardingObjective.selector()
+                .getCriterion(IN_PORT);
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        int mplsLogicalPort;
+        long tunnelId;
+        // Mpls tunnel ids according to the OFDPA manual have to be
+        // in the range [2^17-1, 2^16].
+        tunnelId = MPLS_TUNNEL_ID_BASE | tunnelIdCriterion.tunnelId();
+        if (tunnelId > MPLS_TUNNEL_ID_MAX) {
+            log.error("Pw Versatile Forwarding Objective must include tunnel id < {}",
+                      MPLS_TUNNEL_ID_MAX);
+            fail(forwardingObjective, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+        // Port has not been null.
+        if (portCriterion == null) {
+            log.error("Pw Versatile Forwarding Objective must include port");
+            fail(forwardingObjective, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+        // 0x0000XXXX is UNI interface.
+        if (portCriterion.port().toLong() > 0x0000FFFF) {
+            log.error("Pw Versatile Forwarding Objective invalid logical port {}",
+                      portCriterion.port().toLong());
+            fail(forwardingObjective, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+        mplsLogicalPort = ((int) portCriterion.port().toLong());
+        if (forwardingObjective.nextId() == null) {
+            log.error("Pw Versatile Forwarding Objective must contain nextId ",
+                      forwardingObjective.nextId());
+            fail(forwardingObjective, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+        // We don't expect a treatment.
+        if (forwardingObjective.treatment() != null &&
+                !forwardingObjective.treatment().equals(DefaultTrafficTreatment.emptyTreatment())) {
+            log.error("Pw Versatile Forwarding Objective cannot contain a treatment ",
+                      forwardingObjective.nextId());
+            fail(forwardingObjective, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+        // We retrieve the l2 vpn group and point the mpls
+        // l2 port to this.
+        NextGroup next = getGroupForNextObjective(forwardingObjective.nextId());
+        if (next == null) {
+            log.warn("next-id:{} not found in dev:{}", forwardingObjective.nextId(), deviceId);
+            fail(forwardingObjective, ObjectiveError.GROUPMISSING);
+            return Collections.emptySet();
+        }
+        List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
+        Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
+        if (group == null) {
+            log.warn("Group with key:{} for next-id:{} not found in dev:{}",
+                     gkeys.get(0).peekFirst(), forwardingObjective.nextId(), deviceId);
+            fail(forwardingObjective, ObjectiveError.GROUPMISSING);
+            return Collections.emptySet();
+        }
+        // We prepare the flow rule for the mpls l2 port table.
+        selector.matchTunnelId(tunnelId);
+        selector.extension(new Ofdpa3MatchMplsL2Port(mplsLogicalPort), deviceId);
+        // This should not be necessary but without we receive an error
+        treatment.extension(new Ofdpa3SetQosIndex(0), deviceId);
+        treatment.transition(MPLS_L2_PORT_PCP_TRUST_FLOW_TABLE);
+        treatment.deferred().group(group.id());
+        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                .fromApp(forwardingObjective.appId())
+                .withPriority(MPLS_L2_PORT_PRIORITY)
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .makePermanent()
+                .forTable(MPLS_L2_PORT_FLOW_TABLE);
+        return Collections.singletonList(ruleBuilder.build());
+    }
 }