Bug-2094 : L3 North-South does not work -- fix inbound rewrite
authorFlavio Fernandes <ffernand@redhat.com>
Wed, 17 Jun 2015 12:37:12 +0000 (08:37 -0400)
committerFlavio Fernandes <ffernand@redhat.com>
Fri, 19 Jun 2015 00:41:34 +0000 (20:41 -0400)
This set of changes will fix the inbound table (ie table 30) to use reg3
to track the ip rewrite. Also, changes added to routing table (talbe 60)
so that reg3 match can route packet into tenant segmentation.

Patch set 2: code review.
Patch set 3: fix test to account for removal of inboundIpRewriteExclusionCache.

Change-Id: I53b327da1631ac35aa7aaa519e0e9945d017fb3e
Signed-off-by: Flavio Fernandes <ffernand@redhat.com>
openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/AbstractServiceInstance.java
openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/InboundNatService.java
openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/OutboundNatService.java
openstack/net-virt-providers/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/RoutingService.java
openstack/net-virt-providers/src/test/java/org/opendaylight/ovsdb/openstack/netvirt/providers/openflow13/services/InboundNatServiceTest.java
openstack/net-virt/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/api/Constants.java
openstack/net-virt/src/main/java/org/opendaylight/ovsdb/openstack/netvirt/api/InboundNatProvider.java
openstack/net-virt/src/test/java/org/opendaylight/ovsdb/openstack/netvirt/impl/NeutronL3AdapterTest.java
utils/mdsal-openflow/src/main/java/org/opendaylight/ovsdb/utils/mdsal/openflow/InstructionUtils.java

index 8bfa094cad8be69164ddc67222615a5b76fa95e8..20fd37f3eb3191612abb0648683e924608de884c 100644 (file)
@@ -190,7 +190,7 @@ public abstract class AbstractServiceInstance {
         return null;
     }
 
-    private Long getDpid(Node node) {
+    protected Long getDpid(Node node) {
         Long dpid = 0L;
         dpid = southbound.getDataPathId(node);
         if (dpid == 0) {
index 7c63f5f38971fdce236d9e17a4bc1a2aa9f42f1c..502f3ceedb3dbae1d7f7c5cd79978c70db73cf9a 100644 (file)
@@ -22,24 +22,36 @@ import org.opendaylight.ovsdb.openstack.netvirt.providers.ConfigInterface;
 import org.opendaylight.ovsdb.openstack.netvirt.providers.openflow13.AbstractServiceInstance;
 import org.opendaylight.ovsdb.openstack.netvirt.providers.openflow13.OF13Provider;
 import org.opendaylight.ovsdb.openstack.netvirt.providers.openflow13.Service;
+import org.opendaylight.ovsdb.utils.mdsal.openflow.ActionUtils;
 import org.opendaylight.ovsdb.utils.mdsal.openflow.InstructionUtils;
 import org.opendaylight.ovsdb.utils.mdsal.openflow.MatchUtils;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder;
 
 import com.google.common.collect.Lists;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg3;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.dst.choice.grouping.dst.choice.DstNxRegCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
 public class InboundNatService extends AbstractServiceInstance implements ConfigInterface, InboundNatProvider {
+    public final static long REG_VALUE_NO_INBOUND_REWRITE = 0x0L;
+    public static final Class<? extends NxmNxReg> REG_FIELD = NxmNxReg3.class;
+
     public InboundNatService() {
         super(Service.INBOUND_NAT);
     }
@@ -49,7 +61,7 @@ public class InboundNatService extends AbstractServiceInstance implements Config
     }
 
     @Override
-    public Status programIpRewriteRule(Long dpid, String segmentationId, InetAddress matchAddress,
+    public Status programIpRewriteRule(Long dpid, Long inPort, String destSegId, InetAddress matchAddress,
                                        InetAddress rewriteAddress, Action action) {
         String nodeName = Constants.OPENFLOW_NODE_PREFIX + dpid;
 
@@ -61,11 +73,17 @@ public class InboundNatService extends AbstractServiceInstance implements Config
         List<Instruction> instructions = Lists.newArrayList();
         InstructionBuilder ib = new InstructionBuilder();
 
-        MatchUtils.createTunnelIDMatch(matchBuilder, new BigInteger(segmentationId));
+        MatchUtils.createInPortMatch(matchBuilder, dpid, inPort);
         MatchUtils.createDstL3IPv4Match(matchBuilder, MatchUtils.iPv4PrefixFromIPv4Address(matchAddress.getHostAddress()));
 
-        // Set Dest IP address
-        InstructionUtils.createNwDstInstructions(ib, MatchUtils.iPv4PrefixFromIPv4Address(rewriteAddress.getHostAddress()));
+        // Set register to indicate that rewrite took place
+        ActionBuilder actionBuilder = new ActionBuilder();
+        actionBuilder.setAction(ActionUtils.nxLoadRegAction(new DstNxRegCaseBuilder().setNxReg(REG_FIELD).build(),
+                new BigInteger(destSegId)));
+
+        // Set Dest IP address and set REG_FIELD
+        InstructionUtils.createNwDstInstructions(ib,
+                MatchUtils.iPv4PrefixFromIPv4Address(rewriteAddress.getHostAddress()), actionBuilder);
         ib.setOrder(0);
         ib.setKey(new InstructionKey(0));
         instructions.add(ib.build());
@@ -80,7 +98,7 @@ public class InboundNatService extends AbstractServiceInstance implements Config
         flowBuilder.setMatch(matchBuilder.build());
         flowBuilder.setInstructions(isb.setInstruction(instructions).build());
 
-        String flowId = "InboundNAT_" + segmentationId + "_" + rewriteAddress.getHostAddress();
+        String flowId = "InboundNAT_" + inPort + "_" + destSegId + "_" + matchAddress.getHostAddress();
         flowBuilder.setId(new FlowId(flowId));
         FlowKey key = new FlowKey(new FlowId(flowId));
         flowBuilder.setBarrier(true);
@@ -148,6 +166,77 @@ public class InboundNatService extends AbstractServiceInstance implements Config
         return new Status(StatusCode.SUCCESS);
     }
 
+    /**
+     * Program Default Pipeline Flow for Inbound NAT.
+     *
+     * @param node on which the default pipeline flow is programmed.
+     */
+    @Override
+    protected void programDefaultPipelineRule(Node node) {
+        if (!isBridgeInPipeline(node)) {
+            //logger.trace("Bridge is not in pipeline {} ", node);
+            return;
+        }
+        MatchBuilder matchBuilder = new MatchBuilder();
+        FlowBuilder flowBuilder = new FlowBuilder();
+        Long dpid = getDpid(node);
+        if (dpid == 0L) {
+            return;
+        }
+        String nodeName = OPENFLOW + getDpid(node);
+        NodeBuilder nodeBuilder = createNodeBuilder(nodeName);
+
+        // Create the OF Actions and Instructions
+        InstructionsBuilder isb = new InstructionsBuilder();
+
+        // Instructions List Stores Individual Instructions
+        List<Instruction> instructions = Lists.newArrayList();
+
+        // Set register to indicate that rewrite did _not_ take place
+        InstructionBuilder ib = new InstructionBuilder();
+        List<org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action> actionList =
+                Lists.newArrayList();
+        ActionBuilder ab = new ActionBuilder();
+        ab.setAction(ActionUtils.nxLoadRegAction(new DstNxRegCaseBuilder().setNxReg(REG_FIELD).build(),
+                BigInteger.valueOf(REG_VALUE_NO_INBOUND_REWRITE)));
+        ab.setOrder(1);
+        ab.setKey(new ActionKey(1));
+        actionList.add(ab.build());
+
+        ApplyActionsBuilder aab = new ApplyActionsBuilder();
+        aab.setAction(actionList);
+
+        ib.setInstruction(new ApplyActionsCaseBuilder().setApplyActions(aab.build()).build());
+        ib.setOrder(0);
+        ib.setKey(new InstructionKey(0));
+        instructions.add(ib.build());
+
+        // Call the InstructionBuilder Methods Containing Actions
+        ib = getMutablePipelineInstructionBuilder();
+        ib.setOrder(1);
+        ib.setKey(new InstructionKey(1));
+        instructions.add(ib.build());
+
+        // Add InstructionBuilder to the Instruction(s)Builder List
+        isb.setInstruction(instructions);
+
+        // Add InstructionsBuilder to FlowBuilder
+        flowBuilder.setInstructions(isb.build());
+
+        String flowId = "CUSTOM_DEFAULT_PIPELINE_FLOW_"+getTable();
+        flowBuilder.setId(new FlowId(flowId));
+        FlowKey key = new FlowKey(new FlowId(flowId));
+        flowBuilder.setMatch(matchBuilder.build());
+        flowBuilder.setPriority(0);
+        flowBuilder.setBarrier(true);
+        flowBuilder.setTableId(getTable());
+        flowBuilder.setKey(key);
+        flowBuilder.setFlowName(flowId);
+        flowBuilder.setHardTimeout(0);
+        flowBuilder.setIdleTimeout(0);
+        writeFlow(flowBuilder, nodeBuilder);
+    }
+
     @Override
     public void setDependencies(BundleContext bundleContext, ServiceReference serviceReference) {
         super.setDependencies(bundleContext.getServiceReference(InboundNatProvider.class.getName()), this);
index 86fc8e37fb145f38b1509ba9c1d446a0c3299756..21c999f39a564b032e57c9d8a9d6565cd41d563b 100644 (file)
@@ -68,7 +68,8 @@ public class OutboundNatService extends AbstractServiceInstance implements Outbo
 
         // Set Dest IP address
         InstructionUtils.createNwDstInstructions(ib,
-                                                 MatchUtils.iPv4PrefixFromIPv4Address(rewriteAddress.getHostAddress()));
+                MatchUtils.iPv4PrefixFromIPv4Address(rewriteAddress.getHostAddress()),
+                null);
         ib.setOrder(0);
         ib.setKey(new InstructionKey(0));
         instructions.add(ib.build());
index 9ea0c4bb15b46544bd6f8671103fb82a7ba3d95e..6a549bbeabcfad419cbb122379ea30377694cf2e 100644 (file)
@@ -72,8 +72,16 @@ public class RoutingService extends AbstractServiceInstance implements RoutingPr
         ActionBuilder ab = new ActionBuilder();
         List<org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action> actionList = Lists.newArrayList();
 
-        String prefixString = address.getHostAddress() + "/" + mask;
-        MatchUtils.createTunnelIDMatch(matchBuilder, new BigInteger(sourceSegId));
+        if (sourceSegId.equals(Constants.EXTERNAL_NETWORK)) {
+            // If matching on external network, use register reserved for InboundNatService to ensure that
+            // ip rewrite is meant to be consumed by this destination tunnel id.
+            MatchUtils.addNxRegMatch(matchBuilder,
+                    new MatchUtils.RegMatch(InboundNatService.REG_FIELD, Long.valueOf(destSegId)));
+        } else {
+            MatchUtils.createTunnelIDMatch(matchBuilder, new BigInteger(sourceSegId));
+        }
+
+        final String prefixString = address.getHostAddress() + "/" + mask;
         MatchUtils.createDstL3IPv4Match(matchBuilder, new Ipv4Prefix(prefixString));
 
         // Set source Mac address
@@ -111,7 +119,7 @@ public class RoutingService extends AbstractServiceInstance implements RoutingPr
         flowBuilder.setMatch(matchBuilder.build());
         flowBuilder.setInstructions(isb.setInstruction(instructions).build());
 
-        String flowId = "Routing_" + sourceSegId + "_" + prefixString;
+        String flowId = "Routing_" + sourceSegId + "_" + destSegId + "_" + prefixString;
         flowBuilder.setId(new FlowId(flowId));
         FlowKey key = new FlowKey(new FlowId(flowId));
         flowBuilder.setBarrier(true);
index e9421a527878dead3a1bb387cf245140e34109bc..bcd5bb69c1f6dac4e0d0bc811280a3a11fbe2161 100644 (file)
@@ -66,7 +66,7 @@ public class InboundNatServiceTest {
     }
 
     /**
-     * Test method {@link InboundNatService#programIpRewriteRule(Long, String, InetAddress, InetAddress, Action)}
+     * Test method {@link InboundNatService#programIpRewriteRule(Long, Long, InetAddress, InetAddress, Action)}
      */
     @Test
     public void testProgramIpRewriteRule() throws Exception {
@@ -77,7 +77,7 @@ public class InboundNatServiceTest {
 
         assertEquals("Error, did not return the expected StatusCode",
                 new Status(StatusCode.SUCCESS),
-                inboundNatService.programIpRewriteRule(Long.valueOf(123), "2",
+                inboundNatService.programIpRewriteRule(Long.valueOf(123), Long.valueOf(2), "2",  // FIXME: describe params
                         matchAddress, rewriteAddress, Action.ADD));
         verify(writeTransaction, times(2)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class), anyBoolean());
         verify(writeTransaction, times(1)).submit();
@@ -85,7 +85,7 @@ public class InboundNatServiceTest {
 
         assertEquals("Error, did not return the expected StatusCode",
                 new Status(StatusCode.SUCCESS),
-                inboundNatService.programIpRewriteRule(Long.valueOf(123), "2",
+                inboundNatService.programIpRewriteRule(Long.valueOf(123), Long.valueOf(2), "2",  // FIXME: describe params
                         matchAddress, rewriteAddress, Action.DELETE));
         verify(writeTransaction, times(1)).delete(any(LogicalDatastoreType.class), any(InstanceIdentifier.class));
         verify(writeTransaction, times(2)).submit();
index 98da268e21e104354074e49109d6e039a571e205..36ce97190bed23e3828105aa79ac354e7a261d8a 100644 (file)
@@ -20,6 +20,7 @@ public final class Constants {
     public static final String EXTERNAL_ID_VM_ID = "vm-id";
     public static final String EXTERNAL_ID_INTERFACE_ID = "iface-id";
     public static final String EXTERNAL_ID_VM_MAC = "attached-mac";
+    public static final String EXTERNAL_NETWORK = "external";
 
     /*
      * @see http://docs.openstack.org/grizzly/openstack-network/admin/content/ovs_quantum_plugin.html
index aa2f91a6212e1f0da6fd575f4c4ce22a44f79861..933f7ec56af69c19abd9cd788ed433f3eadd150b 100644 (file)
@@ -16,7 +16,7 @@ import java.net.InetAddress;
  *  This interface allows NAT flows to be written to devices
  */
 public interface InboundNatProvider {
-    Status programIpRewriteRule(Long dpid, String segmentationId, InetAddress matchAddress,
+    Status programIpRewriteRule(Long dpid, Long inPort, String destSegId, InetAddress matchAddress,
                                 InetAddress rewriteAddress, Action action);
 
     Status programIpRewriteExclusion(Long dpid, String segmentationId,
index 464a80b70025fb4baa53935731f05f98b453d823..12297b95477b53a20e5350dec195901ff9f6159c 100644 (file)
@@ -79,7 +79,6 @@ public class NeutronL3AdapterTest {
 
     private Set<String> inboundIpRewriteCache;
     private Set<String> outboundIpRewriteCache;
-    private Set<String> inboundIpRewriteExclusionCache;
     private Set<String> outboundIpRewriteExclusionCache;
     private Set<String> routerInterfacesCache;
     private Set<String> staticArpEntryCache;
@@ -112,7 +111,6 @@ public class NeutronL3AdapterTest {
     private void getNeutronL3AdapterFields() throws Exception{
         inboundIpRewriteCache = (Set<String>) getField("inboundIpRewriteCache");
         outboundIpRewriteCache = (Set<String>) getField("outboundIpRewriteCache");
-        inboundIpRewriteExclusionCache = (Set<String>) getField("inboundIpRewriteExclusionCache");
         outboundIpRewriteExclusionCache = (Set<String>) getField("outboundIpRewriteExclusionCache");
         routerInterfacesCache = (Set<String>) getField("routerInterfacesCache");
         staticArpEntryCache = (Set<String>) getField("staticArpEntryCache");
@@ -221,7 +219,6 @@ public class NeutronL3AdapterTest {
         /* TODO SB_MIGRATION */
         //assertEquals("Error, did not return the correct routerInterfacesCache size", 2, routerInterfacesCache.size());
 //        assertEquals("Error, did not return the correct staticArpEntryCache size", 2, staticArpEntryCache.size());
-//        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 1, inboundIpRewriteExclusionCache.size());
 //        assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 1, outboundIpRewriteExclusionCache.size());
 //        assertEquals("Error, did not return the correct l3ForwardingCache size", 1, l3ForwardingCache.size());
         // Unchanged
@@ -236,7 +233,6 @@ public class NeutronL3AdapterTest {
 //        assertEquals("Error, did not return the correct staticArpEntryCache size", 1, staticArpEntryCache.size());
         // Unchanged
         assertEquals("Error, did not return the correct routerInterfacesCache size", 2, routerInterfacesCache.size());
-        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 1, inboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 1, outboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct l3ForwardingCache size", 1, l3ForwardingCache.size());
         assertEquals("Error, did not return the correct inboundIpRewriteCache size", 0, inboundIpRewriteCache.size());
@@ -265,7 +261,6 @@ public class NeutronL3AdapterTest {
         /* TODO SB_MIGRATION */
         //assertEquals("Error, did not return the correct routerInterfacesCache size", 2, routerInterfacesCache.size());
         assertEquals("Error, did not return the correct staticArpEntryCache size", 2, staticArpEntryCache.size());
-        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 1, inboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 1, outboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct l3ForwardingCache size", 1, l3ForwardingCache.size());
         assertEquals("Error, did not return the correct networkIdToRouterMacCache size", 1, networkIdToRouterMacCache.size());
@@ -282,7 +277,6 @@ public class NeutronL3AdapterTest {
         assertEquals("Error, did not return the correct l3ForwardingCache size", 0, l3ForwardingCache.size());
         // Unchanged
         assertEquals("Error, did not return the correct routerInterfacesCache size", 2, routerInterfacesCache.size());
-        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 1, inboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 1, outboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct networkIdToRouterMacCache size", 1, networkIdToRouterMacCache.size());
         assertEquals("Error, did not return the correct inboundIpRewriteCache size", 0, inboundIpRewriteCache.size());
@@ -314,7 +308,6 @@ public class NeutronL3AdapterTest {
         assertEquals("Error, did not return the correct staticArpEntryCache size", 1, staticArpEntryCache.size());
         // Unchanged
         assertEquals("Error, did not return the correct routerInterfacesCache size", 0, routerInterfacesCache.size());
-        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 0, inboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 0, outboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct subnetIdToRouterInterfaceCache size", 0, subnetIdToRouterInterfaceCache.size());
         assertEquals("Error, did not return the correct l3ForwardingCache size", 0, l3ForwardingCache.size());
@@ -327,7 +320,6 @@ public class NeutronL3AdapterTest {
         assertEquals("Error, did not return the correct outboundIpRewriteCache size", 1, outboundIpRewriteCache.size());
         assertEquals("Error, did not return the correct staticArpEntryCache size", 1, staticArpEntryCache.size());
         assertEquals("Error, did not return the correct routerInterfacesCache size", 0, routerInterfacesCache.size());
-        assertEquals("Error, did not return the correct inboundIpRewriteExclusionCache size", 0, inboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct outboundIpRewriteExclusionCache size", 0, outboundIpRewriteExclusionCache.size());
         assertEquals("Error, did not return the correct subnetIdToRouterInterfaceCache size", 0, subnetIdToRouterInterfaceCache.size());
         assertEquals("Error, did not return the correct l3ForwardingCache size", 0, l3ForwardingCache.size());
index 03db619ee89927a75917d679af2b30d386d3e0e8..80ad548dc5da22cfdd35c922e8685637a2566e48 100644 (file)
@@ -435,9 +435,11 @@ public class InstructionUtils {
      *
      * @param ib        Map InstructionBuilder without any instructions
      * @param prefixdst String containing an IPv4 prefix
+     * @param extraAction (optional) Additional action to be performed in actionList
      * @return ib Map InstructionBuilder with instructions
      */
-    public static InstructionBuilder createNwDstInstructions(InstructionBuilder ib, Ipv4Prefix prefixdst) {
+    public static InstructionBuilder createNwDstInstructions(InstructionBuilder ib, Ipv4Prefix prefixdst,
+                                                             ActionBuilder extraAction) {
 
         List<Action> actionList = Lists.newArrayList();
         ActionBuilder ab = new ActionBuilder();
@@ -451,6 +453,12 @@ public class InstructionUtils {
         ab.setKey(new ActionKey(0));
         actionList.add(ab.build());
 
+        if (extraAction != null) {
+            extraAction.setOrder(1);
+            extraAction.setKey(new ActionKey(1));
+            actionList.add(extraAction.build());
+        }
+
         // Create an Apply Action
         ApplyActionsBuilder aab = new ApplyActionsBuilder();
         aab.setAction(actionList);