Merge remote-tracking branch 'origin/master' into dev/murrelet
diff --git a/apps/ofagent/BUCK b/apps/ofagent/BUCK
index 8e0826b..0d28fca 100644
--- a/apps/ofagent/BUCK
+++ b/apps/ofagent/BUCK
@@ -15,6 +15,12 @@
     '//lib:openflowj',
     '//lib:javax.ws.rs-api',
     '//utils/rest:onlab-rest',
+    '//providers/openflow/flow:onos-providers-openflow-flow',
+]
+
+BUNDLES = [
+    '//apps/ofagent:onos-apps-ofagent',
+    '//providers/openflow/flow:onos-providers-openflow-flow',
 ]
 
 TEST_DEPS = [
@@ -30,12 +36,17 @@
 osgi_jar_with_tests (
     deps = COMPILE_DEPS,
     test_deps = TEST_DEPS,
-    web_context = '/onos/v1/ofagent',
+    web_context = '/onos/ofagent',
+    api_title = 'OFAgent API',
+    api_version = '1.0',
+    api_description = 'REST API for OFAgent',
+    api_package = 'org.onosproject.ofagent.rest',
 )
 
 onos_app (
     title = 'OpenFlow Agent',
     category = 'Traffic Steering',
     url = 'http://onosproject.org',
+    included_bundles = BUNDLES,
     description = 'OpenFlow agent application for virtualization subsystem.',
 )
diff --git a/apps/ofagent/pom.xml b/apps/ofagent/pom.xml
index a3590e9..76fb852 100644
--- a/apps/ofagent/pom.xml
+++ b/apps/ofagent/pom.xml
@@ -172,6 +172,13 @@
             <scope>test</scope>
         </dependency>
 
+        <!-- Flow Entry building dependencies -->
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-providers-openflow-flow</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -191,6 +198,9 @@
                             *,org.glassfish.jersey.servlet
                         </Import-Package>
                         <Web-ContextPath>${web.context}</Web-ContextPath>
+                        <Private-Package>
+                            org.onosproject.provider.of.flow.util
+                        </Private-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
index 3f4f878..f06446a 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
@@ -20,13 +20,16 @@
 import io.netty.channel.Channel;
 import org.onlab.osgi.ServiceDirectory;
 import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TableStatisticsEntry;
 import org.onosproject.net.group.Group;
 import org.onosproject.net.packet.InboundPacket;
@@ -34,6 +37,7 @@
 import org.onosproject.ofagent.api.OFSwitchCapabilities;
 import org.onosproject.ofagent.api.OFSwitchService;
 import org.projectfloodlight.openflow.protocol.OFActionType;
+import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
 import org.projectfloodlight.openflow.protocol.OFBarrierReply;
 import org.projectfloodlight.openflow.protocol.OFBucket;
 import org.projectfloodlight.openflow.protocol.OFBucketCounter;
@@ -43,6 +47,7 @@
 import org.projectfloodlight.openflow.protocol.OFFactories;
 import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
 import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
 import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
 import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry;
@@ -55,6 +60,7 @@
 import org.projectfloodlight.openflow.protocol.OFPacketInReason;
 import org.projectfloodlight.openflow.protocol.OFPacketOut;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortMod;
 import org.projectfloodlight.openflow.protocol.OFPortReason;
 import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
 import org.projectfloodlight.openflow.protocol.OFPortStatsRequest;
@@ -69,6 +75,7 @@
 import org.projectfloodlight.openflow.protocol.OFVersion;
 import org.projectfloodlight.openflow.protocol.action.OFAction;
 import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
+import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
 import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
@@ -103,6 +110,8 @@
     private final Logger log;
 
     private final OFSwitchService ofSwitchService;
+    private final FlowRuleService flowRuleService;
+    private final DriverService driverService;
 
     private final DatapathId dpId;
     private final OFSwitchCapabilities capabilities;
@@ -121,12 +130,15 @@
 
     private DefaultOFSwitch(DatapathId dpid, OFSwitchCapabilities capabilities,
                             NetworkId networkId, DeviceId deviceId,
-                            OFSwitchService ofSwitchService) {
+                            ServiceDirectory serviceDirectory) {
         this.dpId = dpid;
         this.capabilities = capabilities;
         this.networkId = networkId;
         this.deviceId = deviceId;
-        this.ofSwitchService = ofSwitchService;
+        this.ofSwitchService = serviceDirectory.get(OFSwitchService.class);
+        this.driverService = serviceDirectory.get(DriverService.class);
+        VirtualNetworkService virtualNetworkService = serviceDirectory.get(VirtualNetworkService.class);
+        this.flowRuleService = virtualNetworkService.get(networkId, FlowRuleService.class);
         log = LoggerFactory.getLogger(getClass().getName() + " : " + dpid);
     }
 
@@ -135,8 +147,7 @@
                                      ServiceDirectory serviceDirectory) {
         checkNotNull(dpid, "DPID cannot be null");
         checkNotNull(capabilities, "OF capabilities cannot be null");
-        return new DefaultOFSwitch(dpid, capabilities, networkId, deviceId,
-                                   serviceDirectory.get(OFSwitchService.class));
+        return new DefaultOFSwitch(dpid, capabilities, networkId, deviceId, serviceDirectory);
     }
 
     @Override
@@ -224,10 +235,53 @@
         log.debug("processPacketIn: Functionality not yet supported for {}", packet);
     }
 
+    private void processPortMod(OFPortMod portMod) {
+//        PortNumber portNumber = PortNumber.portNumber(portMod.getPortNo().getPortNumber());
+        log.debug("processPortMod: {} not yet supported for {}",
+                  portMod.getType(), portMod);
+    }
+
+    private void processFlowMod(OFFlowMod flowMod) {
+        // convert OFFlowMod to FLowRule object
+        OFAgentVirtualFlowEntryBuilder flowEntryBuilder =
+                new OFAgentVirtualFlowEntryBuilder(deviceId, flowMod, driverService);
+        FlowEntry flowEntry = flowEntryBuilder.build();
+        flowRuleService.applyFlowRules(flowEntry);
+    }
+
     @Override
     public void processControllerCommand(Channel channel, OFMessage msg) {
-        // TODO process controller command
-        log.debug("processControllerCommand: Functionality not yet supported for {}", msg);
+
+        OFControllerRole myRole = role(channel);
+        if (OFControllerRole.ROLE_SLAVE.equals(myRole)) {
+            OFBadRequestErrorMsg errorMsg = FACTORY.errorMsgs()
+                    .buildBadRequestErrorMsg()
+                    .setXid(msg.getXid())
+                    .setCode(OFBadRequestCode.IS_SLAVE)
+                    .build();
+            channel.writeAndFlush(Collections.singletonList(errorMsg));
+            return;
+        }
+
+        switch (msg.getType()) {
+            case PORT_MOD:
+                OFPortMod portMod = (OFPortMod) msg;
+                processPortMod(portMod);
+                break;
+            case FLOW_MOD:
+                OFFlowMod flowMod = (OFFlowMod) msg;
+                processFlowMod(flowMod);
+                break;
+            case GROUP_MOD:
+            case METER_MOD:
+            case TABLE_MOD:
+                log.debug("processControllerCommand: {} not yet supported for {}",
+                          msg.getType(), msg);
+                break;
+            default:
+                log.warn("Unexpected message {} received for switch {}",
+                         msg.getType(), this);
+        }
     }
 
     private void sendPortStatus(Port port, OFPortReason ofPortReason) {
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualFlowEntryBuilder.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualFlowEntryBuilder.java
new file mode 100644
index 0000000..e3cb9e3
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualFlowEntryBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ofagent.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.provider.of.flow.util.FlowEntryBuilder;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// FlowEntryBuilder customized for OFAgent.  This builder will be used to build FlowEntry objects for
+// virtual devices encountered by OFAgent.  The driver has been hardcoded to "ovs".
+public class OFAgentVirtualFlowEntryBuilder extends FlowEntryBuilder {
+    private static final Logger log = LoggerFactory.getLogger(OFAgentVirtualFlowEntryBuilder.class);
+    private static final String DRIVER_NAME = "ovs";
+
+    private final DriverService driverService;
+
+    public OFAgentVirtualFlowEntryBuilder(DeviceId deviceId, OFFlowMod fm, DriverService driverService) {
+        super(deviceId, fm, driverService);
+        this.driverService = driverService;
+    }
+
+    protected DriverHandler getDriver(DeviceId devId) {
+        log.debug("calling getDriver for {}", devId);
+        Driver driver = driverService.getDriver(DRIVER_NAME);
+        DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, devId));
+        return handler;
+    }
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
index aea97d1..ddfadc2 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
@@ -128,6 +128,13 @@
                         // TODO: check if this is lldp - ignore if it is not lldp
                         handler.ofSwitch.processLldp(handler.channel, msg);
                         break;
+                    case FLOW_MOD:
+                    case PORT_MOD:
+                    case GROUP_MOD:
+                    case METER_MOD:
+                    case TABLE_MOD:
+                        handler.ofSwitch.processControllerCommand(handler.channel, msg);
+                        break;
                     case ERROR:
                         handler.logErrorClose((OFErrorMsg) msg);
                         break;
diff --git a/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java b/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
index dcad695..514ece7 100644
--- a/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
+++ b/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
@@ -29,9 +29,14 @@
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiPacketMetadata;
@@ -43,10 +48,13 @@
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.ctl.P4RuntimeClientImpl;
 import org.onosproject.p4runtime.ctl.P4RuntimeControllerImpl;
+import org.slf4j.Logger;
 import p4.P4RuntimeGrpc;
 import p4.P4RuntimeOuterClass;
 
 import java.net.URL;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
@@ -54,6 +62,7 @@
 import static org.onlab.util.ImmutableByteSequence.ofZeros;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
 import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+import static org.slf4j.LoggerFactory.getLogger;
 import static p4.P4RuntimeOuterClass.ActionProfileGroup.Type.SELECT;
 import static p4.P4RuntimeOuterClass.Update.Type.INSERT;
 
@@ -61,6 +70,7 @@
  * Class used for quick testing of P4Runtime with real devices. To be removed before release.
  */
 public class P4RuntimeTest {
+    private static final Logger log = getLogger(P4RuntimeTest.class);
 
     private static final String GRPC_SERVER_ADDR = "192.168.56.102";
     private static final int GRPC_SERVER_PORT = 55044;
@@ -233,6 +243,56 @@
 
     @Test
     @Ignore
+    public void testBmv2ActionProfile() throws Exception {
+        createClient();
+        setPipelineConfig(bmv2DefaultPipeconf, BMV2_JSON);
+        PiActionProfileId actionProfileId = PiActionProfileId.of("ecmp_selector");
+        PiActionGroupId groupId = PiActionGroupId.of(1);
+
+        Collection<PiActionGroupMember> members = Lists.newArrayList();
+
+        for (int port = 1; port <= 4; port++) {
+            PiAction memberAction = PiAction.builder()
+                    .withId(PiActionId.of(SET_EGRESS_PORT))
+                    .withParameter(new PiActionParam(PiActionParamId.of(PORT),
+                                                     ImmutableByteSequence.copyFrom((short) port)))
+                    .build();
+            PiActionGroupMemberId memberId = PiActionGroupMemberId.of(port);
+            PiActionGroupMember member = PiActionGroupMember.builder()
+                    .withId(memberId)
+                    .withAction(memberAction)
+                    .withWeight(port)
+                    .build();
+            members.add(member);
+        }
+        PiActionGroup actionGroup = PiActionGroup.builder()
+                .withType(PiActionGroup.Type.SELECT)
+                .withActionProfileId(actionProfileId)
+                .withId(groupId)
+                .addMembers(members)
+                .build();
+        CompletableFuture<Boolean> success = client.writeActionGroupMembers(actionGroup, members,
+                                                                            P4RuntimeClient.WriteOperationType.INSERT,
+                                                                            bmv2DefaultPipeconf);
+        assert (success.get());
+
+        success = client.writeActionGroup(actionGroup, P4RuntimeClient.WriteOperationType.INSERT, bmv2DefaultPipeconf);
+        assert (success.get());
+
+        CompletableFuture<Collection<PiActionGroup>> piGroups = client.dumpGroups(actionProfileId, bmv2DefaultPipeconf);
+
+        log.info("Number of groups: {}", piGroups.get().size());
+        piGroups.get().forEach(piGroup -> {
+            log.info("Group {}", piGroup);
+            log.info("------");
+            piGroup.members().forEach(piMem -> {
+                log.info("    {}", piMem);
+            });
+        });
+    }
+
+    @Test
+    @Ignore
     public void testTofino() throws Exception {
         createClient();
         setPipelineConfig(bmv2DefaultPipeconf, null);
diff --git a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
index 2e0988b..3862718 100644
--- a/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
+++ b/core/api/src/main/java/org/onosproject/app/DefaultApplicationDescription.java
@@ -15,15 +15,17 @@
  */
 package org.onosproject.app;
 
-import org.onosproject.core.ApplicationRole;
-import org.onosproject.core.Version;
-import org.onosproject.security.Permission;
-
 import java.net.URI;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
+import org.onosproject.core.ApplicationRole;
+import org.onosproject.core.Version;
+import org.onosproject.security.Permission;
+
+import com.google.common.collect.ImmutableList;
+
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -31,7 +33,7 @@
 /**
  * Default implementation of network control/management application descriptor.
  */
-public class DefaultApplicationDescription implements ApplicationDescription {
+public final class DefaultApplicationDescription implements ApplicationDescription {
 
     private final String name;
     private final Version version;
@@ -49,6 +51,14 @@
     private final List<String> requiredApps;
 
     /**
+     * Default constructor is hidden to prevent calls to new.
+     */
+    private DefaultApplicationDescription() {
+        //  Should not happen
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Creates a new application descriptor using the supplied data.
      *
      * @param name         application name
@@ -66,27 +76,26 @@
      * @param features     application features
      * @param requiredApps list of required application names
      */
-    public DefaultApplicationDescription(String name, Version version, String title,
-                                         String description, String origin, String category,
-                                         String url, String readme, byte[] icon,
-                                         ApplicationRole role, Set<Permission> permissions,
-                                         URI featuresRepo, List<String> features,
-                                         List<String> requiredApps) {
-        this.name = checkNotNull(name, "Name cannot be null");
-        this.version = checkNotNull(version, "Version cannot be null");
-        this.title = checkNotNull(title, "Title cannot be null");
-        this.description = checkNotNull(description, "Description cannot be null");
-        this.origin = checkNotNull(origin, "Origin cannot be null");
-        this.category = checkNotNull(category, "Category cannot be null");
+    private DefaultApplicationDescription(String name, Version version, String title,
+                                          String description, String origin, String category,
+                                          String url, String readme, byte[] icon,
+                                          ApplicationRole role, Set<Permission> permissions,
+                                          URI featuresRepo, List<String> features,
+                                          List<String> requiredApps) {
+        this.name = name;
+        this.version = version;
+        this.title = title;
+        this.description = description;
+        this.origin = origin;
+        this.category = category;
         this.url = url;
-        this.readme = checkNotNull(readme, "Readme cannot be null");
+        this.readme = readme;
         this.icon = icon;
-        this.role = checkNotNull(role, "Role cannot be null");
-        this.permissions = checkNotNull(permissions, "Permissions cannot be null");
+        this.role = role;
+        this.permissions = permissions;
         this.featuresRepo = Optional.ofNullable(featuresRepo);
-        this.features = checkNotNull(features, "Features cannot be null");
-        this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null");
-        checkArgument(!features.isEmpty(), "There must be at least one feature");
+        this.features = ImmutableList.copyOf(features);
+        this.requiredApps = ImmutableList.copyOf(requiredApps);
     }
 
     @Override
@@ -177,4 +186,220 @@
                 .add("requiredApps", requiredApps)
                 .toString();
     }
+
+    /**
+     * Returns a default application description builder.
+     *
+     * @return builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Default application description builder.
+     */
+    public static final class Builder {
+
+        private String name;
+        private Version version;
+        private String title;
+        private String description;
+        private String category;
+        private String url;
+        private String readme;
+        private byte[] icon;
+        private String origin;
+        private ApplicationRole role;
+        private Set<Permission> permissions;
+        private URI featuresRepo;
+        private List<String> features;
+        private List<String> requiredApps;
+
+        /**
+         * Default constructor for the builder.
+         */
+        public Builder() {}
+
+        /**
+         * Adds an application id.
+         *
+         * @param name application name
+         * @return builder
+         */
+        public Builder withName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        /**
+         * Adds a version string.
+         *
+         * @param version version string
+         * @return builder
+         */
+        public Builder withVersion(Version version) {
+            this.version = version;
+            return this;
+        }
+
+        /**
+         * Adds a title string.
+         *
+         * @param title title string
+         * @return builder
+         */
+        public Builder withTitle(String title) {
+            this.title = title;
+            return this;
+        }
+
+        /**
+         * Adds a description string.
+         *
+         * @param description description string
+         * @return builder
+         */
+        public Builder withDescription(String description) {
+            this.description = description;
+            return this;
+        }
+
+        /**
+         * Adds a category string.
+         *
+         * @param category category string
+         * @return builder
+         */
+        public Builder withCategory(String category) {
+            this.category = category;
+            return this;
+        }
+
+        /**
+         * Adds a URL string.
+         *
+         * @param url url string
+         * @return builder
+         */
+        public Builder withUrl(String url) {
+            this.url = url;
+            return this;
+        }
+
+        /**
+         * Adds a readme string.
+         *
+         * @param readme readme string
+         * @return builder
+         */
+        public Builder withReadme(String readme) {
+            this.readme = readme;
+            return this;
+        }
+
+        /**
+         * Adds an icon.
+         *
+         * @param icon icon data
+         * @return builder
+         */
+        public Builder withIcon(byte[] icon) {
+            this.icon = icon;
+            return this;
+        }
+
+        /**
+         * Adds an origin string.
+         *
+         * @param origin origin string
+         * @return builder
+         */
+        public Builder withOrigin(String origin) {
+            this.origin = origin;
+            return this;
+        }
+
+        /**
+         * Adds an application role.
+         *
+         * @param role application role
+         * @return builder
+         */
+        public Builder withRole(ApplicationRole role) {
+            this.role = role;
+            return this;
+        }
+
+        /**
+         * Adds a permissions set.
+         *
+         * @param permissions permissions set
+         * @return builder
+         */
+        public Builder withPermissions(Set<Permission> permissions) {
+            this.permissions = permissions;
+            return this;
+        }
+
+        /**
+         * Adds a URI for a features repository.
+         *
+         * @param featuresRepo Optional URI for a features repository
+         * @return builder
+         */
+        public Builder withFeaturesRepo(URI featuresRepo) {
+            this.featuresRepo = featuresRepo;
+            return this;
+        }
+
+        /**
+         * Adds a features list.
+         *
+         * @param features features list
+         * @return builder
+         */
+        public Builder withFeatures(List<String> features) {
+            this.features = features;
+            return this;
+        }
+
+        /**
+         * Adds a list of required applications.
+         *
+         * @param requiredApps List of name strings of required applications
+         * @return builder
+         */
+        public Builder withRequiredApps(List<String> requiredApps) {
+            this.requiredApps = requiredApps;
+            return this;
+        }
+
+        /**
+         * Builds a default application object from the gathered parameters.
+         *
+         * @return new default application
+         */
+        public DefaultApplicationDescription build() {
+            checkNotNull(name, "Name cannot be null");
+            checkNotNull(version, "Version cannot be null");
+            checkNotNull(title, "Title cannot be null");
+            checkNotNull(description, "Description cannot be null");
+            checkNotNull(origin, "Origin cannot be null");
+            checkNotNull(category, "Category cannot be null");
+            checkNotNull(readme, "Readme cannot be null");
+            checkNotNull(role, "Role cannot be null");
+            checkNotNull(permissions, "Permissions cannot be null");
+            checkNotNull(features, "Features cannot be null");
+            checkNotNull(requiredApps, "Required apps cannot be null");
+            checkArgument(!features.isEmpty(), "There must be at least one feature");
+
+            return new DefaultApplicationDescription(name, version, title,
+                                          description, origin, category,
+                                          url, readme, icon,
+                                          role, permissions,
+                                          featuresRepo, features,
+                                          requiredApps);
+        }
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
index f780623..2df2f8f 100644
--- a/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
+++ b/core/api/src/main/java/org/onosproject/core/DefaultApplication.java
@@ -17,6 +17,8 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+
+import org.onosproject.app.ApplicationDescription;
 import org.onosproject.security.Permission;
 
 import java.net.URI;
@@ -32,7 +34,7 @@
 /**
  * Default implementation of network control/management application descriptor.
  */
-public class DefaultApplication implements Application {
+public final class DefaultApplication implements Application {
 
     private final ApplicationId appId;
     private final Version version;
@@ -50,6 +52,26 @@
     private final List<String> requiredApps;
 
     /**
+     * Default constructor is hidden to prevent calls to new.
+     */
+    private DefaultApplication() {
+        appId = null;
+        version = null;
+        title = null;
+        description = null;
+        category = null;
+        url = null;
+        readme = null;
+        icon = null;
+        origin = null;
+        role = null;
+        permissions = null;
+        featuresRepo = Optional.empty();
+        features = ImmutableList.of();
+        requiredApps = ImmutableList.of();
+    }
+
+    /**
      * Creates a new application descriptor using the supplied data.
      *
      * @param appId        application identifier
@@ -67,33 +89,26 @@
      * @param features     application features
      * @param requiredApps list of required application names
      */
-    public DefaultApplication(ApplicationId appId, Version version, String title,
+    private DefaultApplication(ApplicationId appId, Version version, String title,
                               String description, String origin, String category,
                               String url, String readme, byte[] icon,
                               ApplicationRole role, Set<Permission> permissions,
                               Optional<URI> featuresRepo, List<String> features,
                               List<String> requiredApps) {
-        this.appId = checkNotNull(appId, "ID cannot be null");
-        this.version = checkNotNull(version, "Version cannot be null");
-        this.title = checkNotNull(title, "Title cannot be null");
-        this.description = checkNotNull(description, "Description cannot be null");
-        this.origin = checkNotNull(origin, "Origin cannot be null");
-        this.category = checkNotNull(category, "Category cannot be null");
+        this.appId = appId;
+        this.version = version;
+        this.title = title;
+        this.description = description;
+        this.origin = origin;
+        this.category = category;
         this.url = url;
-        this.readme = checkNotNull(readme, "Readme cannot be null");
+        this.readme = readme;
         this.icon = icon == null ? new byte[0] : icon.clone();
-        this.role = checkNotNull(role, "Role cannot be null");
-        this.permissions = ImmutableSet.copyOf(
-                checkNotNull(permissions, "Permissions cannot be null")
-        );
-        this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null");
-        this.features = ImmutableList.copyOf(
-                checkNotNull(features, "Features cannot be null")
-        );
-        this.requiredApps = ImmutableList.copyOf(
-                checkNotNull(requiredApps, "Required apps cannot be null")
-        );
-        checkArgument(!features.isEmpty(), "There must be at least one feature");
+        this.role = role;
+        this.permissions = ImmutableSet.copyOf(permissions);
+        this.featuresRepo = featuresRepo;
+        this.features = ImmutableList.copyOf(features);
+        this.requiredApps = ImmutableList.copyOf(requiredApps);
     }
 
     @Override
@@ -217,4 +232,317 @@
                 .add("requiredApps", requiredApps)
                 .toString();
     }
+
+    /**
+     * Returns a default application builder.
+     *
+     * @return builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Creates a new builder as a copy of an existing builder.
+     *
+     * @param builder existing builder to copy
+     * @return new builder
+     */
+    public static Builder builder(Builder builder) {
+        return new Builder(builder);
+    }
+
+    /**
+     * Creates a new builder as a copy of an existing application.
+     *
+     * @param application existing application to copy
+     * @return new builder
+     */
+    public static Builder builder(Application application) {
+        return new Builder(application);
+    }
+
+    /**
+     * Creates a new builder as a copy of an existing application description.
+     *
+     * @param appDesc existing application description
+     * @return new builder
+     */
+    public static Builder builder(ApplicationDescription appDesc) {
+        return new Builder(appDesc);
+    }
+
+
+    /**
+     * Default application builder.
+     */
+    public static final class Builder {
+
+        private ApplicationId appId;
+        private Version version;
+        private String title;
+        private String description;
+        private String category;
+        private String url;
+        private String readme;
+        private byte[] icon;
+        private String origin;
+        private ApplicationRole role;
+        private Set<Permission> permissions;
+        private Optional<URI> featuresRepo;
+        private List<String> features;
+        private List<String> requiredApps;
+
+        /**
+         * Default constructor for the builder.
+         */
+        public Builder() {}
+
+        /**
+         * Updates the builder to be a copy of an existing builder.
+         *
+         * @param builder existing builder to copy
+         */
+        public Builder(Builder builder) {
+            this.appId = builder.appId;
+            this.version = builder.version;
+            this.title = builder.title;
+            this.description = builder.description;
+            this.category = builder.category;
+            this.url = builder.url;
+            this.readme = builder.readme;
+            this.icon = builder.icon;
+            this.origin = builder.origin;
+            this.role = builder.role;
+            this.permissions = builder.permissions;
+            this.featuresRepo = builder.featuresRepo;
+            this.features = builder.features;
+            this.requiredApps = builder.requiredApps;
+        }
+
+        /**
+         * Updates the builder to be a copy of an existing application.
+         *
+         * @param application existing application to copy
+         */
+        public Builder(Application application) {
+            this.appId = application.id();
+            this.version = application.version();
+            this.title = application.title();
+            this.description = application.description();
+            this.category = application.category();
+            this.url = application.url();
+            this.readme = application.readme();
+            this.icon = application.icon();
+            this.origin = application.origin();
+            this.role = application.role();
+            this.permissions = application.permissions();
+            this.featuresRepo = application.featuresRepo();
+            this.features = application.features();
+            this.requiredApps = application.requiredApps();
+        }
+
+        /**
+         * Updates the builder to be a copy of an existing application description.
+         *
+         * @param appDesc existing application description
+         */
+        public Builder(ApplicationDescription appDesc) {
+            this.version = appDesc.version();
+            this.title = appDesc.title();
+            this.description = appDesc.description();
+            this.category = appDesc.category();
+            this.url = appDesc.url();
+            this.readme = appDesc.readme();
+            this.icon = appDesc.icon();
+            this.origin = appDesc.origin();
+            this.role = appDesc.role();
+            this.permissions = appDesc.permissions();
+            this.featuresRepo = appDesc.featuresRepo();
+            this.features = appDesc.features();
+            this.requiredApps = appDesc.requiredApps();
+        }
+
+        /**
+         * Adds an application id.
+         *
+         * @param appId application id
+         * @return builder
+         */
+        public Builder withAppId(ApplicationId appId) {
+            this.appId = appId;
+            return this;
+        }
+
+        /**
+         * Adds a version string.
+         *
+         * @param version version string
+         * @return builder
+         */
+        public Builder withVersion(Version version) {
+            this.version = version;
+            return this;
+        }
+
+        /**
+         * Adds a title string.
+         *
+         * @param title title string
+         * @return builder
+         */
+        public Builder withTitle(String title) {
+            this.title = title;
+            return this;
+        }
+
+        /**
+         * Adds a description string.
+         *
+         * @param description description string
+         * @return builder
+         */
+        public Builder withDescription(String description) {
+            this.description = description;
+            return this;
+        }
+
+        /**
+         * Adds a category string.
+         *
+         * @param category category string
+         * @return builder
+         */
+        public Builder withCategory(String category) {
+            this.category = category;
+            return this;
+        }
+
+        /**
+         * Adds a URL string.
+         *
+         * @param url url string
+         * @return builder
+         */
+        public Builder withUrl(String url) {
+            this.url = url;
+            return this;
+        }
+
+        /**
+         * Adds a readme string.
+         *
+         * @param readme readme string
+         * @return builder
+         */
+        public Builder withReadme(String readme) {
+            this.readme = readme;
+            return this;
+        }
+
+        /**
+         * Adds an icon.
+         *
+         * @param icon icon data
+         * @return builder
+         */
+        public Builder withIcon(byte[] icon) {
+            this.icon = icon;
+            return this;
+        }
+
+        /**
+         * Adds an origin string.
+         *
+         * @param origin origin string
+         * @return builder
+         */
+        public Builder withOrigin(String origin) {
+            this.origin = origin;
+            return this;
+        }
+
+        /**
+         * Adds an application role.
+         *
+         * @param role application role
+         * @return builder
+         */
+        public Builder withRole(ApplicationRole role) {
+            this.role = role;
+            return this;
+        }
+
+        /**
+         * Adds a permissions set.
+         *
+         * @param permissions permissions set
+         * @return builder
+         */
+        public Builder withPermissions(Set<Permission> permissions) {
+            this.permissions = permissions;
+            return this;
+        }
+
+        /**
+         * Adds a URI for a features repository.
+         *
+         * @param featuresRepo Optional URI for a features repository
+         * @return builder
+         */
+        public Builder withFeaturesRepo(Optional<URI> featuresRepo) {
+            this.featuresRepo = featuresRepo;
+            return this;
+        }
+
+        /**
+         * Adds a features list.
+         *
+         * @param features features list
+         * @return builder
+         */
+        public Builder withFeatures(List<String> features) {
+            this.features = features;
+            return this;
+        }
+
+        /**
+         * Adds a list of required applications.
+         *
+         * @param requiredApps List of name strings of required applications
+         * @return builder
+         */
+        public Builder withRequiredApps(List<String> requiredApps) {
+            this.requiredApps = requiredApps;
+            return this;
+        }
+
+        /**
+         * Builds a default application object from the gathered parameters.
+         *
+         * @return new default application
+         */
+        public DefaultApplication build() {
+            checkNotNull(appId, "ID cannot be null");
+            checkNotNull(version, "Version cannot be null");
+            checkNotNull(title, "Title cannot be null");
+            checkNotNull(description, "Description cannot be null");
+            checkNotNull(origin, "Origin cannot be null");
+            checkNotNull(category, "Category cannot be null");
+            checkNotNull(readme, "Readme cannot be null");
+            checkNotNull(role, "Role cannot be null");
+            checkNotNull(permissions, "Permissions cannot be null");
+            checkNotNull(featuresRepo, "Features repo cannot be null");
+            checkNotNull(features, "Features cannot be null");
+            checkNotNull(requiredApps, "Required apps cannot be null");
+            checkArgument(!features.isEmpty(), "There must be at least one feature");
+
+            return new DefaultApplication(appId, version, title,
+                                          description, origin, category,
+                                          url, readme, icon,
+                                          role, permissions,
+                                          featuresRepo, features,
+                                          requiredApps);
+        }
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
index d3157e1..da93417 100644
--- a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -122,6 +122,16 @@
     public static final String PORT_NAME = "portName";
 
     /**
+     * Annotation key for the optical channel receiving/in port (RX).
+     */
+    public static final String PORT_IN = "portIn";
+
+    /**
+     * Annotation key for the optical channel port transmitting/out port (TX).
+     */
+
+    public static final String PORT_OUT = "portOut";
+    /**
      * Annotation key for the port mac.
      */
     public static final String PORT_MAC = "portMac";
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/DefaultMirroringDescription.java b/core/api/src/main/java/org/onosproject/net/behaviour/DefaultMirroringDescription.java
index 47a19a3..0c82e48 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/DefaultMirroringDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/DefaultMirroringDescription.java
@@ -29,7 +29,7 @@
  * Default implementation of mirroring description entity.
  */
 @Beta
-public class DefaultMirroringDescription extends AbstractDescription
+public final class DefaultMirroringDescription extends AbstractDescription
     implements MirroringDescription {
 
     private final MirroringName mirroringName;
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/DefaultPatchDescription.java b/core/api/src/main/java/org/onosproject/net/behaviour/DefaultPatchDescription.java
index 8d13c68..9450f39 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/DefaultPatchDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/DefaultPatchDescription.java
@@ -20,6 +20,7 @@
 import org.onosproject.net.AbstractDescription;
 import org.onosproject.net.SparseAnnotations;
 
+import java.util.Objects;
 import java.util.Optional;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -61,6 +62,26 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, ifaceName, peerName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultPatchDescription) {
+            final DefaultPatchDescription that = (DefaultPatchDescription) obj;
+            return this.getClass() == that.getClass() &&
+                    Objects.equals(this.deviceId, that.deviceId) &&
+                    Objects.equals(this.ifaceName, that.ifaceName) &&
+                    Objects.equals(this.peerName, that.peerName);
+        }
+        return false;
+    }
+
+    @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
                 .add("deviceId", deviceId)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/MirroringStatistics.java b/core/api/src/main/java/org/onosproject/net/behaviour/MirroringStatistics.java
index 6713633..b9aae84 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/MirroringStatistics.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/MirroringStatistics.java
@@ -16,19 +16,28 @@
 
 package org.onosproject.net.behaviour;
 
-import com.google.common.base.MoreObjects;
-
 import java.util.Map;
 import java.util.Objects;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Represents statistics associated to a mirroring.
  */
 public final class MirroringStatistics {
 
-    private MirroringName mirroringName;
-    private int txBytes;
-    private int txPackets;
+    private final MirroringName mirroringName;
+    private final long txBytes;
+    private final long txPackets;
+
+    /**
+     * Hide private constructor to prevent calls to new().
+     */
+    private MirroringStatistics() {
+        mirroringName = null;
+        txBytes = 0;
+        txPackets = 0;
+    }
 
     /**
      * Statistics associated to a named mirroring.
@@ -37,7 +46,7 @@
      * @param bytes transmitted bytes
      * @param packets transmitted packets
      */
-    private MirroringStatistics(String name, int bytes, int packets) {
+    private MirroringStatistics(String name, long bytes, long packets) {
         this.mirroringName = MirroringName.mirroringName(name);
         this.txBytes = bytes;
         this.txPackets = packets;
@@ -78,7 +87,7 @@
      *
      * @return the packets
      */
-    public long packtes() {
+    public long packets() {
         return txPackets;
     }
 
@@ -107,7 +116,7 @@
         return MoreObjects.toStringHelper(getClass())
                 .add("name", name())
                 .add("tx_bytes", bytes())
-                .add("tx_packets", packtes())
+                .add("tx_packets", packets())
                 .toString();
     }
 
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java b/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java
index 2b96452..ab9eee5 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/TunnelEndPoint.java
@@ -22,9 +22,9 @@
  * Represents for source end point or destination end point of a tunnel. Maybe a tunnel
  * based on ConnectPoint, IpAddress, MacAddress and so on is built.
  */
-public class TunnelEndPoint<T> {
+public final class TunnelEndPoint<T> {
 
-    protected final T value;
+    private final T value;
 
     /**
      * Default constructor.
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
index cbf1da5..d20f9aa 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
@@ -16,6 +16,7 @@
 package org.onosproject.net.flow;
 
 import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.provider.Provider;
 
 /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java
index 13a91a7..ff0bbe1 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java
@@ -18,6 +18,8 @@
 import java.util.List;
 
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.store.Store;
 
 /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java
index 7b31f48..3e712f2 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStoreDelegate.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.net.flow;
 
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
 import org.onosproject.store.StoreDelegate;
 
 /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEntry.java
similarity index 85%
rename from core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java
rename to core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEntry.java
index 964647b..905af60 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEntry.java
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.net.flow;
+package org.onosproject.net.flow.oldbatch;
 
-import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onosproject.net.flow.BatchOperationEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation;
 
 @Deprecated
 /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEvent.java
similarity index 97%
rename from core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java
rename to core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEvent.java
index f70067f..ab41753 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchEvent.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchEvent.java
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.net.flow;
+package org.onosproject.net.flow.oldbatch;
 
 import org.onosproject.event.AbstractEvent;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.CompletedBatchOperation;
 
 @Deprecated
 /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchOperation.java
similarity index 94%
rename from core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java
rename to core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchOperation.java
index a721cc8..d252e6a 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchOperation.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchOperation.java
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.net.flow;
+package org.onosproject.net.flow.oldbatch;
 
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.BatchOperation;
 
 import java.util.Collection;
 
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchRequest.java
similarity index 97%
rename from core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java
rename to core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchRequest.java
index d1e74cd..33539e4 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleBatchRequest.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/FlowRuleBatchRequest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.net.flow;
+package org.onosproject.net.flow.oldbatch;
 
 import java.util.List;
 import java.util.Set;
diff --git a/core/api/src/main/java/org/onosproject/net/flow/oldbatch/package-info.java b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/package-info.java
new file mode 100644
index 0000000..20cb7a3
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/flow/oldbatch/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Flow rule model &amp; related services API definitions.
+ *
+ * <p>
+ * The figure below depicts the general interactions between different
+ * components of the intent subsystem.<br>
+ * <img src="doc-files/flow-design.png" alt="ONOS flow rule subsystem design">
+ * </p>
+ */
+package org.onosproject.net.flow.oldbatch;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
index f023e05..1de4d61 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
@@ -47,11 +47,15 @@
     private final PiActionGroupId id;
     private final Type type;
     private final ImmutableSet<PiActionGroupMember> members;
+    private final PiActionProfileId piActionProfileId;
 
-    private PiActionGroup(PiActionGroupId id, Type type, ImmutableSet<PiActionGroupMember> members) {
+    private PiActionGroup(PiActionGroupId id, Type type,
+                          ImmutableSet<PiActionGroupMember> members,
+                          PiActionProfileId piActionProfileId) {
         this.id = id;
         this.type = type;
         this.members = members;
+        this.piActionProfileId = piActionProfileId;
     }
 
     /**
@@ -81,18 +85,28 @@
         return members;
     }
 
+    /**
+     * Gets identifier of the action profile.
+     *
+     * @return action profile id
+     */
+    public PiActionProfileId actionProfileId() {
+        return piActionProfileId;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (o == null || !(o instanceof PiActionGroup)) {
             return false;
         }
         PiActionGroup that = (PiActionGroup) o;
-        return id == that.id &&
+        return Objects.equal(id, that.id) &&
                 Objects.equal(type, that.type) &&
-                Objects.equal(members, that.members);
+                Objects.equal(members, that.members) &&
+                Objects.equal(piActionProfileId, that.piActionProfileId);
     }
 
     @Override
@@ -106,6 +120,7 @@
                 .add("groupId", id)
                 .add("type", type)
                 .add("members", members)
+                .add("piActionProfileId", piActionProfileId)
                 .toString();
     }
 
@@ -126,6 +141,7 @@
         private PiActionGroupId id;
         private Type type;
         private Map<PiActionGroupMemberId, PiActionGroupMember> members = Maps.newHashMap();
+        private PiActionProfileId piActionProfileId;
 
         private Builder() {
             // hides constructor.
@@ -176,6 +192,17 @@
         }
 
         /**
+         * Sets the identifier of the action profile.
+         *
+         * @param piActionProfileId the identifier of the action profile
+         * @return this
+         */
+        public Builder withActionProfileId(PiActionProfileId piActionProfileId) {
+            this.piActionProfileId = piActionProfileId;
+            return this;
+        }
+
+        /**
          * Creates a new action group.
          *
          * @return action group
@@ -183,8 +210,11 @@
         public PiActionGroup build() {
             checkNotNull(id);
             checkNotNull(type);
-            checkArgument(members.size() > 0, "Members cannot be empty");
-            return new PiActionGroup(id, type, ImmutableSet.copyOf(members.values()));
+            checkArgument(!members.isEmpty(), "Members cannot be empty");
+            checkNotNull(piActionProfileId);
+            return new PiActionGroup(id, type,
+                                     ImmutableSet.copyOf(members.values()),
+                                     piActionProfileId);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java
new file mode 100644
index 0000000..e0158c9
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+/**
+ * Identifier of an action profile of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiActionProfileId extends Identifier<String> {
+
+    private PiActionProfileId(String actionProfileName) {
+        super(actionProfileName);
+    }
+
+    /**
+     * Returns action profile id with given action profile name.
+     *
+     * @param actionProfileName action profile name
+     * @return action profile id
+     */
+    public static PiActionProfileId of(String actionProfileName) {
+        return new PiActionProfileId(actionProfileName);
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
index 3cdedd2..61538f0 100644
--- a/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
+++ b/core/api/src/test/java/org/onosproject/app/ApplicationEventTest.java
@@ -32,9 +32,22 @@
 public class ApplicationEventTest extends AbstractEventTest {
 
     private Application createApp() {
-        return new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN, CATEGORY,
-                                      URL, README, ICON, ROLE, PERMS,
-                                      Optional.of(FURL), FEATURES, APPS);
+        return DefaultApplication.builder()
+                .withAppId(APP_ID)
+                .withVersion(VER)
+                .withTitle(TITLE)
+                .withDescription(DESC)
+                .withOrigin(ORIGIN)
+                .withCategory(CATEGORY)
+                .withUrl(URL)
+                .withReadme(README)
+                .withIcon(ICON)
+                .withRole(ROLE)
+                .withPermissions(PERMS)
+                .withFeaturesRepo(Optional.of(FURL))
+                .withFeatures(FEATURES)
+                .withRequiredApps(APPS)
+                .build();
     }
 
     @Test
@@ -53,4 +66,4 @@
         validateEvent(event, APP_ACTIVATED, app, before, after);
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
index ac8d971..a935c1b 100644
--- a/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
+++ b/core/api/src/test/java/org/onosproject/app/DefaultApplicationDescriptionTest.java
@@ -29,6 +29,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 
 
 /**
@@ -53,12 +54,34 @@
     public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
     public static final List<String> APPS = ImmutableList.of("fifi");
 
+    /**
+     * Checks that the DefaultApplicationDescription class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultApplicationDescription.class);
+    }
+
     @Test
     public void basics() {
         ApplicationDescription app =
-                new DefaultApplicationDescription(APP_NAME, VER, TITLE, DESC, ORIGIN,
-                                                  CATEGORY, URL, README, ICON,
-                                                  ROLE, PERMS, FURL, FEATURES, APPS);
+            DefaultApplicationDescription.builder()
+                .withName(APP_NAME)
+                .withVersion(VER)
+                .withTitle(TITLE)
+                .withDescription(DESC)
+                .withOrigin(ORIGIN)
+                .withCategory(CATEGORY)
+                .withUrl(URL)
+                .withReadme(README)
+                .withIcon(ICON)
+                .withRole(ROLE)
+                .withPermissions(PERMS)
+                .withFeaturesRepo(FURL)
+                .withFeatures(FEATURES)
+                .withRequiredApps(APPS)
+                .build();
+
         assertEquals("incorrect id", APP_NAME, app.name());
         assertEquals("incorrect version", VER, app.version());
         assertEquals("incorrect title", TITLE, app.title());
@@ -74,4 +97,4 @@
         assertEquals("incorrect apps", APPS, app.requiredApps());
         assertTrue("incorrect toString", app.toString().contains(APP_NAME));
     }
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
index 38113bf..b523126 100644
--- a/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
+++ b/core/api/src/test/java/org/onosproject/core/DefaultApplicationTest.java
@@ -28,6 +28,9 @@
 import java.util.Optional;
 import java.util.Set;
 
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.core.DefaultApplication.Builder;
+
 import static org.junit.Assert.*;
 import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
 
@@ -36,13 +39,35 @@
  */
 public class DefaultApplicationTest {
 
+    /**
+     * Checks that the DefaultApplication class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultApplication.class);
+    }
+
     public static final ApplicationId APP_ID = new DefaultApplicationId(2, APP_NAME);
+    private Builder baseBuilder = DefaultApplication.builder()
+                .withAppId(APP_ID)
+                .withVersion(VER)
+                .withTitle(TITLE)
+                .withDescription(DESC)
+                .withOrigin(ORIGIN)
+                .withCategory(CATEGORY)
+                .withUrl(URL)
+                .withReadme(README)
+                .withIcon(ICON)
+                .withRole(ROLE)
+                .withPermissions(PERMS)
+                .withFeaturesRepo(Optional.of(FURL))
+                .withFeatures(FEATURES)
+                .withRequiredApps(APPS);
 
     @Test
     public void basics() {
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                                                 CATEGORY, URL, README, ICON, ROLE,
-                                                 PERMS, Optional.of(FURL), FEATURES, APPS);
+        Application app = baseBuilder.build();
+
         assertEquals("incorrect id", APP_ID, app.id());
         assertEquals("incorrect version", VER, app.version());
         assertEquals("incorrect title", TITLE, app.title());
@@ -62,20 +87,20 @@
 
     @Test
     public void testEquality() {
-        Application a1 = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                                                CATEGORY, URL, README, ICON, ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES, APPS);
-        Application a2 = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                                                CATEGORY, URL, README, ICON, ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES, APPS);
-        Application a3 = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                                                CATEGORY, URL, README, ICON, ROLE,
-                                                PERMS, Optional.empty(), FEATURES, APPS);
-        Application a4 = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN + "asd",
-                                                CATEGORY, URL, README, ICON, ROLE,
-                                                PERMS, Optional.of(FURL), FEATURES, APPS);
-        new EqualsTester().addEqualityGroup(a1, a2)
-                .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
+        Application a1 = baseBuilder.build();
+        Application a2 = DefaultApplication.builder(a1)
+                .build();
+        Application a3 = DefaultApplication.builder(baseBuilder)
+                .withFeaturesRepo(Optional.empty())
+                .build();
+        Application a4 = DefaultApplication.builder(baseBuilder)
+                .withOrigin(ORIGIN + "asd")
+                .build();
+        new EqualsTester()
+                .addEqualityGroup(a1, a2)
+                .addEqualityGroup(a3)
+                .addEqualityGroup(a4)
+                .testEquals();
     }
 
 
@@ -85,9 +110,8 @@
     public void immutableIcon() {
         byte[] iconSourceData = ICON_ORIG.clone();
 
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, iconSourceData, ROLE,
-                PERMS, Optional.of(FURL), FEATURES, APPS);
+        Application app = DefaultApplication.builder(baseBuilder)
+                .withIcon(iconSourceData).build();
 
         // can we modify the icon after getting a reference to the app?
         byte[] icon = app.icon();
@@ -126,9 +150,7 @@
 //        Set<Permission> p = PERMS_ORIG;
         Set<Permission> p = PERMS_UNSAFE;
 
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, ICON, ROLE,
-                p, Optional.of(FURL), FEATURES, APPS);
+        Application app = baseBuilder.build();
 
         Set<Permission> perms = app.permissions();
         try {
@@ -168,9 +190,7 @@
 //        List<String> f = FEATURES_ORIG;
         List<String> f = FEATURES_UNSAFE;
 
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, ICON, ROLE,
-                PERMS, Optional.of(FURL), f, APPS);
+        Application app = DefaultApplication.builder(baseBuilder).withFeatures(f).build();
 
         List<String> features = app.features();
         try {
@@ -188,9 +208,7 @@
 //        List<String> ra = REQ_APPS_ORIG;
         List<String> ra = REQ_APPS_UNSAFE;
 
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, ICON, ROLE,
-                PERMS, Optional.of(FURL), FEATURES, ra);
+        Application app = DefaultApplication.builder(baseBuilder).withRequiredApps(ra).build();
 
         List<String> reqApps = app.requiredApps();
         try {
@@ -204,11 +222,9 @@
 
     @Test
     public void nullIcon() {
-        Application app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, null, ROLE,
-                PERMS, Optional.of(FURL), FEATURES, APPS);
+        Application app = DefaultApplication.builder(baseBuilder).withIcon(null).build();
         byte[] icon = app.icon();
         assertNotNull("null icon", icon);
         assertEquals("unexpected size", 0, icon.length);
     }
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/BridgeNameTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/BridgeNameTest.java
new file mode 100644
index 0000000..ad23c9a
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/BridgeNameTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.behaviour;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class BridgeNameTest {
+
+    private static final String NAME1 = "bridge-1";
+    private static final String NAME2 = "bridge-2";
+    private BridgeName bridgeName1 = BridgeName.bridgeName(NAME1);
+    private BridgeName sameAsBridgeName1 = BridgeName.bridgeName(NAME1);
+    private BridgeName bridgeName2 = BridgeName.bridgeName(NAME2);
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(BridgeName.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(bridgeName1.name(), is(NAME1));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(bridgeName1, sameAsBridgeName1)
+                .addEqualityGroup(bridgeName2)
+                .testEquals();
+    }
+
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultMirroringDescriptionTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultMirroringDescriptionTest.java
new file mode 100644
index 0000000..2596f7b
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultMirroringDescriptionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.behaviour;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class DefaultMirroringDescriptionTest {
+
+    private static final MirroringName NAME_1 = MirroringName.mirroringName("mirror1");
+    private static final List<String> MONITOR_SRC_PORTS_1 =
+            ImmutableList.of("s1", "s2", "s3");
+    private static final List<String> MONITOR_DST_PORTS_1 =
+            ImmutableList.of("d1", "d2");
+    private static final List<VlanId> MONITOR_VLANS_1 = ImmutableList.of(VlanId.ANY);
+    private static final Optional<String> MIRROR_PORT_1 = Optional.of("port1");
+    private static final Optional<VlanId> MIRROR_VLAN_1 = Optional.of(VlanId.ANY);
+    private MirroringDescription md1 =
+            new DefaultMirroringDescription(NAME_1, MONITOR_SRC_PORTS_1,
+                                            MONITOR_DST_PORTS_1, MONITOR_VLANS_1,
+                                            MIRROR_PORT_1, MIRROR_VLAN_1);
+
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultMirroringDescription.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(md1.name(), is(NAME_1));
+        assertThat(md1.monitorSrcPorts(), is(MONITOR_SRC_PORTS_1));
+        assertThat(md1.monitorDstPorts(), is(MONITOR_DST_PORTS_1));
+        assertThat(md1.monitorVlans(), is(MONITOR_VLANS_1));
+        assertThat(md1.mirrorPort(), is(MIRROR_PORT_1));
+        assertThat(md1.mirrorVlan(), is(MIRROR_VLAN_1));
+    }
+
+    @Test
+    public void testToString() {
+        String result = md1.toString();
+        assertThat(result, notNullValue());
+        assertThat(result, containsString("name=" + NAME_1.toString()));
+        assertThat(result, containsString("monitorsrcports=" + MONITOR_SRC_PORTS_1.toString()));
+        assertThat(result, containsString("monitordstports=" + MONITOR_DST_PORTS_1.toString()));
+        assertThat(result, containsString("monitorvlans=" + MONITOR_VLANS_1.toString()));
+        assertThat(result, containsString("mirrorport=" + MIRROR_PORT_1.toString()));
+        assertThat(result, containsString("mirrorvlan=" + MIRROR_VLAN_1.toString()));
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultPatchDescriptionTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultPatchDescriptionTest.java
new file mode 100644
index 0000000..34c4521
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultPatchDescriptionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.behaviour;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+
+public class DefaultPatchDescriptionTest {
+
+    private String deviceId1 = "d1";
+    private String ifaceName1 = "i1";
+    private String peerName1 = "p1";
+
+    private PatchDescription defaultPatchDescription1 =
+            DefaultPatchDescription.builder()
+            .deviceId(deviceId1)
+            .ifaceName(ifaceName1)
+            .peer(peerName1)
+            .build();
+    private PatchDescription sameAsDefaultPatchDescription1 =
+            DefaultPatchDescription.builder()
+                    .deviceId(deviceId1)
+                    .ifaceName(ifaceName1)
+                    .peer(peerName1)
+                    .build();
+    private PatchDescription defaultPatchDescription2 =
+            DefaultPatchDescription.builder()
+                    .deviceId(deviceId1 + "2")
+                    .ifaceName(ifaceName1)
+                    .peer(peerName1)
+                    .build();
+    private PatchDescription defaultPatchDescription3 =
+            DefaultPatchDescription.builder()
+                    .deviceId(deviceId1)
+                    .ifaceName(ifaceName1 + "2")
+                    .peer(peerName1)
+                    .build();
+    private PatchDescription defaultPatchDescription4 =
+            DefaultPatchDescription.builder()
+                    .deviceId(deviceId1)
+                    .ifaceName(ifaceName1)
+                    .peer(peerName1 + "2")
+                    .build();
+    private PatchDescription defaultPatchDescriptionNoDeviceId =
+            DefaultPatchDescription.builder()
+                    .ifaceName(ifaceName1)
+                    .peer(peerName1 + "2")
+                    .build();
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultPatchDescription.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(defaultPatchDescription1.deviceId(), optionalWithValue(is(deviceId1)));
+        assertThat(defaultPatchDescription1.ifaceName(), is(ifaceName1));
+        assertThat(defaultPatchDescription1.peer(), is(peerName1));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(defaultPatchDescription1, sameAsDefaultPatchDescription1)
+                .addEqualityGroup(defaultPatchDescription2)
+                .addEqualityGroup(defaultPatchDescription3)
+                .addEqualityGroup(defaultPatchDescription4)
+                .addEqualityGroup(defaultPatchDescriptionNoDeviceId)
+                .testEquals();
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQosDescriptionTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQosDescriptionTest.java
new file mode 100644
index 0000000..8196d74
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQosDescriptionTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.behaviour;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.onlab.util.Bandwidth;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.EqualsTester;
+
+import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class DefaultQosDescriptionTest {
+
+    private QosId qosId1 = QosId.qosId("1");
+    private Bandwidth bandwidth1 = Bandwidth.bps(1);
+    private Map<Long, QueueDescription> queues1 = ImmutableMap.of();
+
+    private QosDescription defaultQosDescription1 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(11L)
+                    .maxRate(bandwidth1)
+                    .queues(queues1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription sameAsDefaultQosDescription1 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(11L)
+                    .maxRate(bandwidth1)
+                    .queues(queues1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription defaultQosDescription2 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(2L)
+                    .cir(11L)
+                    .maxRate(bandwidth1)
+                    .queues(queues1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription defaultQosDescription3 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(33L)
+                    .maxRate(bandwidth1)
+                    .queues(queues1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription defaultQosDescription4 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(11L)
+                    .queues(queues1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription defaultQosDescription5 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(11L)
+                    .maxRate(bandwidth1)
+                    .type(QosDescription.Type.NOOP)
+                    .build();
+    private QosDescription defaultQosDescription6 =
+            DefaultQosDescription.builder()
+                    .qosId(qosId1)
+                    .cbs(1L)
+                    .cir(11L)
+                    .maxRate(bandwidth1)
+                    .queues(queues1)
+                    .type(QosDescription.Type.CODEL)
+                    .build();
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultQosDescription.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(defaultQosDescription1.qosId(), is(qosId1));
+        assertThat(defaultQosDescription1.cbs(), optionalWithValue(is(1L)));
+        assertThat(defaultQosDescription1.cir(), optionalWithValue(is(11L)));
+        assertThat(defaultQosDescription1.maxRate(), optionalWithValue(is(bandwidth1)));
+        assertThat(defaultQosDescription1.queues(), optionalWithValue(is(queues1)));
+        assertThat(defaultQosDescription1.type(), is(QosDescription.Type.NOOP));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(defaultQosDescription1, sameAsDefaultQosDescription1)
+                .addEqualityGroup(defaultQosDescription2)
+                .addEqualityGroup(defaultQosDescription3)
+                .addEqualityGroup(defaultQosDescription4)
+                .addEqualityGroup(defaultQosDescription5)
+                .addEqualityGroup(defaultQosDescription6)
+                .testEquals();
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQueueDescriptionTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQueueDescriptionTest.java
index 6c495fb..99647cc 100644
--- a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQueueDescriptionTest.java
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultQueueDescriptionTest.java
@@ -16,17 +16,15 @@
 
 package org.onosproject.net.behaviour;
 
-import java.util.EnumSet;
-
+import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.Bandwidth;
 
-import com.google.common.testing.EqualsTester;
+import java.util.EnumSet;
 
-import static org.hamcrest.Matchers.contains;
+import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 
 
 public class DefaultQueueDescriptionTest {
@@ -71,17 +69,11 @@
 
     @Test
     public void testConstruction() {
-        assertTrue(queueDescription1.burst().isPresent());
-        assertThat(queueDescription1.burst().get(), is(1L));
-        assertTrue(queueDescription1.dscp().isPresent());
-        assertThat(queueDescription1.dscp().get(), is(11));
-        assertTrue(queueDescription1.maxRate().isPresent());
-        assertThat(queueDescription1.maxRate().get(), is(MAX_BANDWIDTH_1));
-        assertTrue(queueDescription1.minRate().isPresent());
-        assertThat(queueDescription1.minRate().get(), is(MIN_BANDWIDTH_1));
-        assertThat(queueDescription1.type(), contains(QueueDescription.Type.MAX));
-        assertTrue(queueDescription1.priority().isPresent());
-        assertThat(queueDescription1.priority().get(), is(1L));
+        assertThat(queueDescription1.burst(), optionalWithValue(is(1L)));
+        assertThat(queueDescription1.dscp(), optionalWithValue(is(11)));
+        assertThat(queueDescription1.maxRate(), optionalWithValue(is(MAX_BANDWIDTH_1)));
+        assertThat(queueDescription1.minRate(), optionalWithValue(is(MIN_BANDWIDTH_1)));
+        assertThat(queueDescription1.priority(), optionalWithValue(is(1L)));
         assertThat(queueDescription1.queueId(), is(QUEUE_ID1));
     }
 
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultTunnelDescriptionTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultTunnelDescriptionTest.java
index 5cd339f..4d9e2b9 100644
--- a/core/api/src/test/java/org/onosproject/net/behaviour/DefaultTunnelDescriptionTest.java
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/DefaultTunnelDescriptionTest.java
@@ -16,15 +16,14 @@
 
 package org.onosproject.net.behaviour;
 
+import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.SparseAnnotations;
 
-import com.google.common.testing.EqualsTester;
-
+import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue;
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 
 
 public class DefaultTunnelDescriptionTest {
@@ -64,16 +63,12 @@
 
     @Test
     public void testConstruction() {
-        assertTrue(tunnelDescription1.deviceId().isPresent());
-        assertThat(tunnelDescription1.deviceId().get(), is(DID_1));
+        assertThat(tunnelDescription1.deviceId(), optionalWithValue(is(DID_1)));
         assertThat(tunnelDescription1.ifaceName(), is(IFACE_NAME_1));
-        assertTrue(tunnelDescription1.key().isPresent());
-        assertThat(tunnelDescription1.key().get(), is(KEY_1));
+        assertThat(tunnelDescription1.key(), optionalWithValue(is(KEY_1)));
         assertThat(tunnelDescription1.type(), is(TunnelDescription.Type.GRE));
-        assertTrue(tunnelDescription1.local().isPresent());
-        assertThat(tunnelDescription1.local().get(), is(LOCAL_1));
-        assertTrue(tunnelDescription1.remote().isPresent());
-        assertThat(tunnelDescription1.remote().get(), is(REMOTE_1));
+        assertThat(tunnelDescription1.local(), optionalWithValue(is(LOCAL_1)));
+        assertThat(tunnelDescription1.remote(), optionalWithValue(is(REMOTE_1)));
     }
 
     @Test
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/MirroringNameTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/MirroringNameTest.java
new file mode 100644
index 0000000..bce584f
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/MirroringNameTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.behaviour;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class MirroringNameTest {
+
+    private static final String NAME1 = "name1";
+    private MirroringName name1 = MirroringName.mirroringName(NAME1);
+    private MirroringName sameAsName1 = MirroringName.mirroringName(NAME1);
+    private static final String NAME2 = "name2";
+    private MirroringName name2 = MirroringName.mirroringName(NAME2);
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(MirroringName.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(name1.name(), is(NAME1));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(name1, sameAsName1)
+                .addEqualityGroup(name2)
+                .testEquals();
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/MirroringStatisticsTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/MirroringStatisticsTest.java
new file mode 100644
index 0000000..b79da88
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/MirroringStatisticsTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.behaviour;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class MirroringStatisticsTest {
+
+    private static final long BYTES_1 = 100L;
+    private static final long PACKETS_1 = 2L;
+    private static final String NAME_1 = "mirror1";
+    private Map<String, Integer> statistics1 =
+            ImmutableMap.of("tx_bytes", (int) BYTES_1, "tx_packets", (int) PACKETS_1);
+    private MirroringStatistics mirrorStatisticStats1 = MirroringStatistics.mirroringStatistics(NAME_1, statistics1);
+
+    private Map<String, Integer> sameAsStatistics1 =
+            ImmutableMap.of("tx_bytes", (int) BYTES_1, "tx_packets", (int) PACKETS_1);
+    private MirroringStatistics sameAsMirrorStatisticStats1 =
+            MirroringStatistics.mirroringStatistics(NAME_1, sameAsStatistics1);
+
+    private static final long BYTES_2 = 100L;
+    private static final long PACKETS_2 = 2L;
+    private static final String NAME_2 = "mirror2";
+    private Map<String, Integer> statistics2 =
+            ImmutableMap.of("tx_bytes", (int) BYTES_2, "tx_packets", (int) PACKETS_2);
+    private MirroringStatistics mirrorStatisticStats2 = MirroringStatistics.mirroringStatistics(NAME_2, statistics2);
+
+    private static final long BYTES_3 = 100L;
+    private static final long PACKETS_3 = 2L;
+    private static final String NAME_3 = "mirror3";
+    private Map<String, Integer> statistics3 =
+            ImmutableMap.of("tx_bytes", (int) BYTES_3, "tx_packets", (int) PACKETS_3);
+    private MirroringStatistics mirrorStatisticStats3 = MirroringStatistics.mirroringStatistics(NAME_3, statistics3);
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(MirroringStatistics.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(mirrorStatisticStats1.bytes(), is(BYTES_1));
+        assertThat(mirrorStatisticStats1.name().name(), is(NAME_1));
+        assertThat(mirrorStatisticStats1.packets(), is(PACKETS_1));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(mirrorStatisticStats1, sameAsMirrorStatisticStats1)
+                .addEqualityGroup(mirrorStatisticStats2)
+                .addEqualityGroup(mirrorStatisticStats3)
+                .testEquals();
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/QosIdTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/QosIdTest.java
new file mode 100644
index 0000000..11f5c40
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/QosIdTest.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.behaviour;
+
+public class QosIdTest {
+
+}
diff --git a/core/api/src/test/java/org/onosproject/net/behaviour/TunnelEndPointTest.java b/core/api/src/test/java/org/onosproject/net/behaviour/TunnelEndPointTest.java
new file mode 100644
index 0000000..af76773
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/behaviour/TunnelEndPointTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.behaviour;
+
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.NetTestTools;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+public class TunnelEndPointTest {
+
+    private ConnectPoint cp1 = NetTestTools.connectPoint("cp1", 1);
+    private TunnelEndPoint<ConnectPoint> endPoint1 =
+            new TunnelEndPoint<>(cp1);
+    private TunnelEndPoint<ConnectPoint> sameAsEndPoint1 =
+            new TunnelEndPoint<>(cp1);
+
+    private ConnectPoint cp2 = NetTestTools.connectPoint("cp2", 2);
+    private TunnelEndPoint<ConnectPoint> endPoint2 =
+            new TunnelEndPoint<>(cp2);
+
+    private TunnelEndPoint<MacAddress> endPoint3 =
+            new TunnelEndPoint<>(MacAddress.BROADCAST);
+
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(TunnelEndPoint.class);
+    }
+
+    @Test
+    public void testConstruction() {
+        assertThat(endPoint1.value(), is(cp1));
+        assertThat(endPoint1.strValue(), is(cp1.toString()));
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(endPoint1, sameAsEndPoint1)
+                .addEqualityGroup(endPoint2)
+                .addEqualityGroup(endPoint3)
+                .testEquals();
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java
index 2ff894c..0fa563f 100644
--- a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java
+++ b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchOperationTest.java
@@ -18,6 +18,8 @@
 import java.util.LinkedList;
 
 import org.junit.Test;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.intent.IntentTestsMocks;
 
 import com.google.common.testing.EqualsTester;
diff --git a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java
index a06e84a..ca263ee 100644
--- a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java
+++ b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleBatchRequestTest.java
@@ -16,6 +16,9 @@
 package org.onosproject.net.flow;
 
 import org.junit.Test;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.intent.IntentTestsMocks;
 
 import java.util.HashSet;
@@ -25,8 +28,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
-import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.ADD;
-import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.REMOVE;
+import static org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation.ADD;
+import static org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation.REMOVE;
 
 /**
  * Unit tests for the FlowRuleBatchRequest class.
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
index e9b736b..0da15e4 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
@@ -28,6 +28,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.ACTION_PROF_ID;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.MOD_NW_DST;
 
@@ -51,12 +52,14 @@
             .addMember(piActionGroupMember)
             .withId(piActionGroupId)
             .withType(PiActionGroup.Type.SELECT)
+            .withActionProfileId(ACTION_PROF_ID)
             .build();
 
     PiActionGroup sameAsPiActionGroup1 = PiActionGroup.builder()
             .addMember(piActionGroupMember)
             .withId(piActionGroupId)
             .withType(PiActionGroup.Type.SELECT)
+            .withActionProfileId(ACTION_PROF_ID)
             .build();
 
     PiActionGroupId piActionGroupId2 = PiActionGroupId.of(20);
@@ -64,6 +67,7 @@
             .addMember(piActionGroupMember)
             .withId(piActionGroupId2)
             .withType(PiActionGroup.Type.SELECT)
+            .withActionProfileId(ACTION_PROF_ID)
             .build();
 
     /**
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
index b2bda37..86f713d 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
@@ -38,4 +38,7 @@
 
     public static final String EGRESS_PORT = "egress_port";
     public static final String INGRESS_PORT = "ingress_port";
+
+    public static final PiActionProfileId ACTION_PROF_ID =
+            PiActionProfileId.of("Test action profile");
 }
diff --git a/core/api/src/test/java/org/onosproject/net/topology/AbstractPathServiceTest.java b/core/api/src/test/java/org/onosproject/net/topology/AbstractPathServiceTest.java
new file mode 100644
index 0000000..d8d14e9
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/topology/AbstractPathServiceTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.topology;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.graph.Weight;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDisjointPath;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Path;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.onosproject.net.NetTestTools.did;
+import static org.onosproject.net.NetTestTools.hid;
+
+public class AbstractPathServiceTest {
+
+    private TestPathService service;
+    private FakeTopoMgr topoMgr;
+
+    private ConnectPoint cpA = NetTestTools.connectPoint("A", 1);
+    private ConnectPoint cpB = NetTestTools.connectPoint("B", 2);
+    private ConnectPoint cpC = NetTestTools.connectPoint("C", 3);
+    private ProviderId pid = ProviderId.NONE;
+    private Link link1 = DefaultLink.builder()
+            .providerId(pid)
+            .src(cpA)
+            .dst(cpB)
+            .type(Link.Type.DIRECT)
+            .state(Link.State.ACTIVE)
+            .build();
+    private Link link2 = DefaultLink.builder()
+            .providerId(pid)
+            .src(cpB)
+            .dst(cpC)
+            .type(Link.Type.DIRECT)
+            .state(Link.State.ACTIVE)
+            .build();
+    private List<Link> links1 = ImmutableList.of(link1, link2);
+    private Path path1 = new DefaultPath(pid, links1, new ScalarWeight(1.0));
+
+
+
+    private class TestPathService extends AbstractPathService {
+        Set<Path> paths = null;
+        Set<DisjointPath> disjointPaths = null;
+
+        @Override
+        public Set<Path> getPaths(ElementId src, ElementId dst) {
+            return paths;
+        }
+
+        @Override
+        public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst) {
+            return disjointPaths;
+        }
+
+        @Override
+        public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst, Map<Link, Object> riskProfile) {
+            return disjointPaths;
+        }
+    }
+
+    class TestWeigher implements LinkWeigher {
+        @Override
+        public Weight weight(TopologyEdge edge) {
+            return new ScalarWeight(1.0);
+        }
+
+        @Override
+        public Weight getInitialWeight() {
+            return new ScalarWeight(1.0);
+        }
+
+        @Override
+        public Weight getNonViableWeight() {
+            return new ScalarWeight(0.0);
+        }
+    }
+
+    // Fake entity to give out paths.
+    private class FakeTopoMgr extends TopologyServiceAdapter {
+
+        Set<Path> paths = new HashSet<>();
+        Set<DisjointPath> disjointPaths = new HashSet<>();
+
+        void definePaths(Set<Path> paths) {
+            this.paths = paths;
+            this.disjointPaths = paths.stream()
+                    .map(path ->
+                        new DefaultDisjointPath(path.providerId(),
+                                                (DefaultPath) path))
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<Path> getPaths(Topology topology, DeviceId src,
+                                  DeviceId dst) {
+            return paths;
+        }
+
+        @Override
+        public Set<Path> getPaths(Topology topology, DeviceId src,
+                                  DeviceId dst, LinkWeigher weight) {
+            return paths;
+        }
+
+        @Override
+        public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                                  DeviceId dst,
+                                                  LinkWeigher weigher) {
+            return disjointPaths;
+        }
+
+        @Override
+        public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                                  DeviceId dst,
+                                                  LinkWeigher weigher,
+                                                  Map<Link, Object> riskProfile) {
+            return disjointPaths;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        service = new TestPathService();
+        topoMgr = new FakeTopoMgr();
+        service.topologyService = topoMgr;
+        service.hostService = new HostServiceAdapter();
+    }
+
+    private void checkPathValues(Path path) {
+        assertThat(path, notNullValue());
+        assertThat(path.links(), hasSize(2));
+        assertThat(path.links().get(0).src(), is(cpA));
+        assertThat(path.links().get(0).dst(), is(cpB));
+        assertThat(path.links().get(1).src(), is(cpB));
+        assertThat(path.links().get(1).dst(), is(cpC));
+    }
+
+    private void checkDisjointPaths(Set<DisjointPath> paths) {
+        assertThat(paths, notNullValue());
+        assertThat(paths, hasSize(1));
+        Path path = paths.iterator().next();
+        checkPathValues(path);
+    }
+
+    private void checkPaths(Collection<Path> paths) {
+        assertThat(paths, notNullValue());
+        assertThat(paths, hasSize(1));
+        Path path = paths.iterator().next();
+        checkPathValues(path);
+    }
+
+    /**
+     * Tests no paths being set up.
+     */
+    @Test
+    public void testNoPaths() {
+        Set<Path> noPaths = service.getPaths(did("A"), did("B"), new TestWeigher());
+        assertThat(noPaths, empty());
+    }
+
+    /**
+     * Tests paths from a host.
+     */
+    @Test
+    public void testSelfPaths() {
+        HostId host = hid("12:34:56:78:90:ab/1");
+        Set<Path> paths = service.getPaths(host, host, new TestWeigher());
+        assertThat(paths, hasSize(1));
+        Path path = paths.iterator().next();
+        assertThat(path, not(nullValue()));
+        assertThat(path.links(), hasSize(2));
+        Link link1 = path.links().get(0);
+        Link link2 = path.links().get(1);
+        assertThat(link1.src(), is(link2.dst()));
+        assertThat(link2.src(), is(link1.dst()));
+        assertThat(link1.src().hostId(), is(host));
+        assertThat(link2.dst().hostId(), is(host));
+    }
+
+    /**
+     * Tests paths from a device to a device.
+     */
+    @Test
+    public void testDevicePaths() {
+        topoMgr.definePaths(ImmutableSet.of(path1));
+        Set<Path> pathsAC = service.getPaths(did("A"), did("C"), new TestWeigher());
+        checkPaths(pathsAC);
+    }
+
+    /**
+     * Tests K Shortest Path computation.
+     */
+    @Test
+    public void testKShortestPath() {
+        topoMgr.definePaths(ImmutableSet.of(path1));
+        List<Path> paths = service.getKShortestPaths(did("A"), did("C"), new TestWeigher())
+                .collect(Collectors.toList());
+        checkPaths(paths);
+    }
+
+    /**
+     * Tests disjoint paths.
+     */
+    @Test
+    public void testDisjointPaths() {
+        topoMgr.definePaths(ImmutableSet.of(path1));
+        Set<DisjointPath> paths = service.getDisjointPaths(did("A"), did("C"), new TestWeigher());
+        checkDisjointPaths(paths);
+    }
+
+    /**
+     * Tests disjoint paths with a risk profile.
+     */
+    @Test
+    public void testDisjointPathsWithRiskProfile() {
+        topoMgr.definePaths(ImmutableSet.of(path1));
+        Map<Link, Object> riskProfile = ImmutableMap.of();
+
+        Set<DisjointPath> paths =
+                service.getDisjointPaths(did("A"), did("C"), new TestWeigher(),
+                                         riskProfile);
+
+        checkDisjointPaths(paths);
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
index 92709cb..55c198e 100644
--- a/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
+++ b/core/common/src/main/java/org/onosproject/common/app/ApplicationArchive.java
@@ -328,10 +328,22 @@
         // put short description to description field
         String desc = compactDescription(readme);
 
-        return new DefaultApplicationDescription(name, version, title, desc, origin,
-                                                 category, url, readme, icon,
-                                                 role, perms, featuresRepo,
-                                                 features, requiredApps);
+        return DefaultApplicationDescription.builder()
+            .withName(name)
+            .withVersion(version)
+            .withTitle(title)
+            .withDescription(desc)
+            .withOrigin(origin)
+            .withCategory(category)
+            .withUrl(url)
+            .withReadme(readme)
+            .withIcon(icon)
+            .withRole(role)
+            .withPermissions(perms)
+            .withFeaturesRepo(featuresRepo)
+            .withFeatures(features)
+            .withRequiredApps(requiredApps)
+            .build();
     }
 
     // Expands the specified ZIP stream into app-specific directory.
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
index ec05afb..c904455 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleApplicationStore.java
@@ -76,20 +76,11 @@
             ApplicationId appId = idStore.registerApplication(name);
             ApplicationDescription appDesc = getApplicationDescription(name);
             DefaultApplication app =
-                    new DefaultApplication(appId,
-                            appDesc.version(),
-                            appDesc.title(),
-                            appDesc.description(),
-                            appDesc.origin(),
-                            appDesc.category(),
-                            appDesc.url(),
-                            appDesc.readme(),
-                            appDesc.icon(),
-                            appDesc.role(),
-                            appDesc.permissions(),
-                            appDesc.featuresRepo(),
-                            appDesc.features(),
-                            appDesc.requiredApps());
+                DefaultApplication
+                    .builder(appDesc)
+                    .withAppId(appId)
+                    .build();
+
             apps.put(appId, app);
             states.put(appId, isActive(name) ? INSTALLED : ACTIVE);
             // load app permissions
@@ -129,20 +120,11 @@
         ApplicationDescription appDesc = saveApplication(appDescStream);
         ApplicationId appId = idStore.registerApplication(appDesc.name());
         DefaultApplication app =
-                new DefaultApplication(appId,
-                        appDesc.version(),
-                        appDesc.title(),
-                        appDesc.description(),
-                        appDesc.origin(),
-                        appDesc.category(),
-                        appDesc.url(),
-                        appDesc.readme(),
-                        appDesc.icon(),
-                        appDesc.role(),
-                        appDesc.permissions(),
-                        appDesc.featuresRepo(),
-                        appDesc.features(),
-                        appDesc.requiredApps());
+            DefaultApplication
+                .builder(appDesc)
+                .withAppId(appId)
+                .build();
+
         apps.put(appId, app);
         states.put(appId, INSTALLED);
         delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java
index 29e9a65..02cd8ec 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java
@@ -39,11 +39,11 @@
 import org.onosproject.net.flow.FlowEntry.FlowEntryState;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleEvent.Type;
 import org.onosproject.net.flow.FlowRuleStore;
diff --git a/core/common/src/test/java/org/onosproject/utils/ComparatorsTest.java b/core/common/src/test/java/org/onosproject/utils/ComparatorsTest.java
index 8562d7e..6047719 100644
--- a/core/common/src/test/java/org/onosproject/utils/ComparatorsTest.java
+++ b/core/common/src/test/java/org/onosproject/utils/ComparatorsTest.java
@@ -161,9 +161,22 @@
     }
 
     private Application app(int id, String name) {
-        return new DefaultApplication(new DefaultApplicationId(id, name), VER, TITLE, DESC, ORIGIN,
-                CATEGORY, URL, README, ICON, ROLE,
-                PERMS, Optional.of(FURL), FEATURES, APPS);
+        return DefaultApplication.builder()
+                .withAppId(new DefaultApplicationId(id, name))
+                .withVersion(VER)
+                .withTitle(TITLE)
+                .withDescription(DESC)
+                .withOrigin(ORIGIN)
+                .withCategory(CATEGORY)
+                .withUrl(URL)
+                .withReadme(README)
+                .withIcon(ICON)
+                .withRole(ROLE)
+                .withPermissions(PERMS)
+                .withFeaturesRepo(Optional.of(FURL))
+                .withFeatures(FEATURES)
+                .withRequiredApps(APPS)
+                .build();
     }
 
     @Test
diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java
index c4dc883..c058ab6 100644
--- a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java
+++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java
@@ -30,8 +30,8 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.CompletedBatchOperation;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleProgrammable;
 import org.onosproject.net.flow.FlowRuleProvider;
 import org.onosproject.net.flow.FlowRuleProviderService;
@@ -50,7 +50,7 @@
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.device.DeviceEvent.Type.*;
-import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.*;
+import static org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation.*;
 
 /**
  * Driver-based flow rule provider.
diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
index 04b73d0..47623a7 100644
--- a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
@@ -45,10 +45,10 @@
 import org.onosproject.net.flow.DefaultFlowEntry;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleOperation;
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
index 25465e9..f8775c8 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
@@ -131,8 +131,8 @@
         Collection<PiFieldMatch> fieldMatches = buildFieldMatches(interpreter, rule.selector(), table);
 
         /* Translate treatment */
-        PiAction piAction = buildAction(rule.treatment(), interpreter, piTableId);
-        piAction = typeCheckAction(piAction, table);
+        PiTableAction piTableAction = buildAction(rule.treatment(), interpreter, piTableId);
+        piTableAction = typeCheckAction(piTableAction, table);
 
         PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
 
@@ -154,7 +154,7 @@
                 .withMatchKey(PiMatchKey.builder()
                                       .addFieldMatches(fieldMatches)
                                       .build())
-                .withAction(piAction);
+                .withAction(piTableAction);
 
         if (!rule.isPermanent()) {
             if (table.supportsAging()) {
@@ -172,7 +172,7 @@
     /**
      * Builds a PI action out of the given treatment, optionally using the given interpreter.
      */
-    private static PiAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
+    private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
                                         PiTableId tableId)
             throws PiFlowRuleTranslationException {
 
@@ -208,22 +208,28 @@
                             + "protocol-independent instruction were provided.");
         }
 
-        if (piTableAction.type() != PiTableAction.Type.ACTION) {
-            // TODO: implement handling of other table action types, e.g. action profiles.
-            throw new PiFlowRuleTranslationException(format(
-                    "PiTableAction type %s is not supported yet.", piTableAction.type()));
-        }
-
-        return (PiAction) piTableAction;
+        return piTableAction;
     }
 
     /**
-     * Checks that the given PI action is suitable for the given table model and returns a new action instance with
-     * parameters well-sized, according to the table model. If not suitable, throws an exception explaining why.
+     * Checks that the given PI table action is suitable for the given table
+     * model and returns a new action instance with parameters well-sized,
+     * according to the table model. If not suitable, throws an exception explaining why.
      */
-    private static PiAction typeCheckAction(PiAction piAction, PiTableModel table)
+    private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
             throws PiFlowRuleTranslationException {
+        switch (piTableAction.type()) {
+            case ACTION:
+                return checkPiAction((PiAction) piTableAction, table);
+            default:
+                // FIXME: should we check? how?
+                return piTableAction;
 
+        }
+    }
+
+    private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
+            throws PiFlowRuleTranslationException  {
         // Table supports this action?
         PiActionModel actionModel = table.action(piAction.id().name()).orElseThrow(
                 () -> new PiFlowRuleTranslationException(format("Not such action '%s' for table '%s'",
diff --git a/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java b/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
index 5fd7701..5528f26 100644
--- a/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/app/impl/ApplicationManagerTest.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.app.impl;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.junit.After;
 import org.junit.Before;
@@ -138,9 +137,22 @@
 
         @Override
         public Application create(InputStream appDescStream) {
-            app = new DefaultApplication(APP_ID, VER, TITLE, DESC, ORIGIN, CATEGORY,
-                                         URL, README, ICON, ROLE, PERMS,
-                                         Optional.of(FURL), FEATURES, ImmutableList.of());
+            app = DefaultApplication.builder()
+                    .withAppId(APP_ID)
+                    .withVersion(VER)
+                    .withTitle(TITLE)
+                    .withDescription(DESC)
+                    .withOrigin(ORIGIN)
+                    .withCategory(CATEGORY)
+                    .withUrl(URL)
+                    .withReadme(README)
+                    .withIcon(ICON)
+                    .withRole(ROLE)
+                    .withPermissions(PERMS)
+                    .withFeaturesRepo(Optional.of(FURL))
+                    .withFeatures(FEATURES)
+                    .withRequiredApps(APPS)
+                    .build();
             state = INSTALLED;
             delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
             return app;
@@ -211,4 +223,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
index 08f3373..825ef1a 100644
--- a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
@@ -51,7 +51,7 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowEntry.FlowEntryState;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleProgrammable;
diff --git a/core/security/src/test/java/org/onosproject/security/impl/SecurityModeManagerTest.java b/core/security/src/test/java/org/onosproject/security/impl/SecurityModeManagerTest.java
index 15155ff..328d903 100644
--- a/core/security/src/test/java/org/onosproject/security/impl/SecurityModeManagerTest.java
+++ b/core/security/src/test/java/org/onosproject/security/impl/SecurityModeManagerTest.java
@@ -69,11 +69,22 @@
         testFeatures.add("testFeature");
         testRequiredApps = new ArrayList<String>();
         testRequiredApps.add("testRequiredApp");
-        app = new DefaultApplication(appId, Version.version(1, 1, "patch", "build"), "testTitle",
-                "testDes", "testOri", "testCT",
-                "testurl", "test", null,
-                ApplicationRole.ADMIN, testPermissions,
-                Optional.ofNullable(null), testFeatures, testRequiredApps);
+        app = DefaultApplication.builder()
+                .withAppId(appId)
+                .withVersion(Version.version(1, 1, "patch", "build"))
+                .withTitle("testTitle")
+                .withDescription("testDes")
+                .withOrigin("testOri")
+                .withCategory("testCT")
+                .withUrl("testurl")
+                .withReadme("test")
+                .withIcon(null)
+                .withRole(ApplicationRole.ADMIN)
+                .withPermissions(testPermissions)
+                .withFeaturesRepo(Optional.ofNullable(null))
+                .withFeatures(testFeatures)
+                .withRequiredApps(testRequiredApps)
+                .build();
 
         store.registerApplication(appId);
     }
diff --git a/core/security/src/test/java/org/onosproject/security/store/DistributedSecurityModeStoreTest.java b/core/security/src/test/java/org/onosproject/security/store/DistributedSecurityModeStoreTest.java
index f3e38bf..220b44e 100644
--- a/core/security/src/test/java/org/onosproject/security/store/DistributedSecurityModeStoreTest.java
+++ b/core/security/src/test/java/org/onosproject/security/store/DistributedSecurityModeStoreTest.java
@@ -79,11 +79,22 @@
         testFeatures.add("testFeature");
         testRequiredApps = new ArrayList<String>();
         testRequiredApps.add("testRequiredApp");
-        app = new DefaultApplication(appId, Version.version(1, 1, "patch", "build"), "testTitle",
-                "testDes", "testOri", "testCT",
-                "testurl", "test", null,
-                ApplicationRole.ADMIN, testPermissions,
-                Optional.ofNullable(null), testFeatures, testRequiredApps);
+        app = DefaultApplication.builder()
+                        .withAppId(appId)
+                        .withVersion(Version.version(1, 1, "patch", "build"))
+                        .withTitle("testTitle")
+                        .withDescription("testDes")
+                        .withOrigin("testOri")
+                        .withCategory("testCT")
+                        .withUrl("testurl")
+                        .withReadme("test")
+                        .withIcon(null)
+                        .withRole(ApplicationRole.ADMIN)
+                        .withPermissions(testPermissions)
+                        .withFeaturesRepo(Optional.ofNullable(null))
+                        .withFeatures(testFeatures)
+                        .withRequiredApps(testRequiredApps)
+                        .build();
 
         testLocations = new HashSet<String>();
         testLocations.add("locationA");
diff --git a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
index 532b279..c65d1aa 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
@@ -571,20 +571,10 @@
      */
     private Application registerApp(ApplicationDescription appDesc) {
         ApplicationId appId = idStore.registerApplication(appDesc.name());
-        return new DefaultApplication(appId,
-                                      appDesc.version(),
-                                      appDesc.title(),
-                                      appDesc.description(),
-                                      appDesc.origin(),
-                                      appDesc.category(),
-                                      appDesc.url(),
-                                      appDesc.readme(),
-                                      appDesc.icon(),
-                                      appDesc.role(),
-                                      appDesc.permissions(),
-                                      appDesc.featuresRepo(),
-                                      appDesc.features(),
-                                      appDesc.requiredApps());
+        return DefaultApplication
+                .builder(appDesc)
+                .withAppId(appId)
+                .build();
     }
 
     /**
diff --git a/core/store/dist/src/main/java/org/onosproject/store/flow/impl/DistributedFlowRuleStore.java b/core/store/dist/src/main/java/org/onosproject/store/flow/impl/DistributedFlowRuleStore.java
index e2d485f..c4c7485 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/flow/impl/DistributedFlowRuleStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/flow/impl/DistributedFlowRuleStore.java
@@ -55,11 +55,11 @@
 import org.onosproject.net.flow.FlowEntry.FlowEntryState;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleEvent.Type;
 import org.onosproject.net.flow.FlowRuleService;
diff --git a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
index 8144d22..449190d 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
@@ -36,8 +36,8 @@
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleOperation;
 import org.onosproject.net.intent.IntentTestsMocks;
 import org.onosproject.store.cluster.messaging.ClusterCommunicationServiceAdapter;
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 6413037..54dd14c 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -100,10 +100,10 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleExtPayLoad;
 import org.onosproject.net.flow.IndexTableId;
@@ -212,6 +212,7 @@
 import org.onosproject.net.pi.runtime.PiActionParamId;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
@@ -388,7 +389,6 @@
                     DefaultFlowRule.class,
                     TableId.class,
                     IndexTableId.class,
-                    PiTableId.class,
                     FlowRule.FlowRemoveReason.class,
                     DefaultPacketRequest.class,
                     PacketPriority.class,
@@ -620,6 +620,7 @@
                     PiTableId.class,
                     PiTernaryFieldMatch.class,
                     PiValidFieldMatch.class,
+                    PiActionProfileId.class,
                     // Other
                     PiCriterion.class,
                     PiInstruction.class
diff --git a/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java b/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
index a2e4670..ac6072b 100644
--- a/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
+++ b/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
@@ -54,7 +54,7 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
 import org.onosproject.net.intent.IntentId;
 import org.onosproject.net.resource.ResourceAllocation;
 import org.onosproject.net.resource.ResourceConsumerId;
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
index cd89bba..b0b55a8 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
@@ -57,7 +57,6 @@
 
         final URL jsonUrl = Bmv2DefaultPipeconfFactory.class.getResource(JSON_PATH);
         final URL p4InfoUrl = Bmv2DefaultPipeconfFactory.class.getResource(P4INFO_PATH);
-
         return DefaultPiPipeconf.builder()
                 .withId(new PiPipeconfId(PIPECONF_ID))
                 .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
diff --git a/drivers/ciena/BUCK b/drivers/ciena/BUCK
index f6b7fa7..be8746e 100644
--- a/drivers/ciena/BUCK
+++ b/drivers/ciena/BUCK
@@ -6,6 +6,7 @@
     '//protocols/rest/api:onos-protocols-rest-api',
     '//apps/optical-model:onos-apps-optical-model',
     '//lib:javax.ws.rs-api',
+    '//drivers/optical:onos-drivers-optical',
 ]
 
 TEST_DEPS = [
diff --git a/drivers/ciena/pom.xml b/drivers/ciena/pom.xml
index 671148a..9c6df7d 100644
--- a/drivers/ciena/pom.xml
+++ b/drivers/ciena/pom.xml
@@ -59,6 +59,11 @@
             <artifactId>onos-restsb-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-drivers-optical</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
new file mode 100644
index 0000000..e514188
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.ciena;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.driver.optical.flowrule.CrossConnectFlowRule;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+
+import org.slf4j.Logger;
+
+
+import java.util.List;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CienaFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+    private CienaRestDevice restCiena;
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public Collection<FlowEntry> getFlowEntries() {
+        DeviceId deviceId = handler().data().deviceId();
+        log.debug("getting flow entries for device {}", deviceId);
+        log.debug("getFlowEntries not supported for device {}", deviceId);
+        return Collections.EMPTY_LIST;
+    }
+
+    @Override
+    public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+        log.debug("installing flow rules: {}", rules);
+        // Apply the valid rules on the device
+        Collection<FlowRule> added = rules.stream()
+                .map(r -> createCrossConnectFlowRule(r))
+                .filter(xc -> installCrossConnect(xc))
+                .collect(Collectors.toList());
+        return added;
+    }
+
+    @Override
+    public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+        log.debug("removing flow rules: {}", rules);
+        Collection<FlowRule> removed = rules.stream()
+                .map(r -> createCrossConnectFlowRule(r))
+                .filter(xc -> removeCrossConnect(xc))
+                .collect(Collectors.toList());
+        return removed;
+    }
+
+    private CrossConnectFlowRule createCrossConnectFlowRule(FlowRule r) {
+        List<PortNumber> linePorts = CienaWaveserverDeviceDescription.getLinesidePortId().stream()
+                .map(p -> PortNumber.portNumber(p))
+                .collect(Collectors.toList());
+        try {
+            return new CrossConnectFlowRule(r, linePorts);
+        } catch (IllegalArgumentException e) {
+            log.debug("unable to create cross connect for rule:\n{}", r);
+        }
+        return null;
+    }
+
+    private boolean installCrossConnect(CrossConnectFlowRule xc) {
+        if (xc == null) {
+            return false;
+        }
+        // only handling lineside rule
+        if (xc.isAddRule()) {
+            PortNumber outPort = xc.addDrop();
+            OchSignal signal = xc.ochSignal();
+            return install(outPort, signal);
+        }
+        return false;
+    }
+
+    private boolean removeCrossConnect(CrossConnectFlowRule xc) {
+        //for now setting channel to 0 for remove rule
+        if (xc == null) {
+            return false;
+        }
+        // only handling lineside rule
+        if (xc.isAddRule()) {
+            PortNumber outPort = xc.addDrop();
+            OchSignal signal = OchSignal.newDwdmSlot(xc.ochSignal().channelSpacing(),
+                                                     -CienaRestDevice.getMultiplierOffset());
+            return install(outPort, signal);
+        }
+        return false;
+    }
+
+    private boolean install(PortNumber outPort, OchSignal signal) {
+        /*
+         * rule is installed in three steps
+         * 1- disable port
+         * 2- change channel
+         * 3- enable port
+         */
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return false;
+        }
+        //1- disable port
+        //blindly disabling port
+        if (!restCiena.disablePort(outPort)) {
+            log.error("unable to disable port {}", outPort);
+            return false;
+        }
+        //2- change channel
+        if (!restCiena.changeChannel(signal, outPort)) {
+            log.error("unable to change the channel for port {}", outPort);
+            return false;
+        }
+        //3- enable port
+        if (!restCiena.enablePort(outPort)) {
+            log.error("unable to enable port {}", outPort);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
new file mode 100644
index 0000000..7627959
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.ciena;
+
+import org.onlab.util.Frequency;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.protocol.rest.RestSBController;
+import org.slf4j.Logger;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CienaRestDevice {
+    private DeviceId deviceId;
+    private RestSBController controller;
+
+    private final Logger log = getLogger(getClass());
+    private static final String ENABLED = "enabled";
+    private static final String DISABLED = "disabled";
+    private static final Frequency BASE_FREQUENCY = Frequency.ofGHz(193_950);
+    private static final int MULTIPLIER_OFFSET = 80;
+
+    //URIs
+    private static final String PORT_URI = "ws-ptps/ptps/%s";
+    private static final String TRANSMITTER_URI = PORT_URI + "/properties/transmitter";
+    private static final String PORT_STATE_URI = PORT_URI  + "/state";
+    private static final String WAVELENGTH_URI = TRANSMITTER_URI + "/wavelength";
+    private static final String FREQUENCY_URI = TRANSMITTER_URI + "/ciena-ws-ptp-modem:frequency";
+    private static final String CHANNEL_URI = TRANSMITTER_URI + "/ciena-ws-ptp-modem:line-system-channel-number";
+
+    public CienaRestDevice(DriverHandler handler) throws NullPointerException {
+        deviceId = handler.data().deviceId();
+        controller = checkNotNull(handler.get(RestSBController.class));
+    }
+
+    private final String genPortStateRequest(String state) {
+        String request = "{\n" +
+                "\"state\": {\n" +
+                "\"admin-state\": \"" + state + "\"\n}\n}";
+        log.debug("generated request: \n{}", request);
+        return request;
+    }
+
+    private String genWavelengthChangeRequest(String wavelength) {
+        String request = "{\n" +
+                "\"wavelength\": {\n" +
+                "\"value\": " + wavelength + "\n" +
+                "}\n" +
+                "}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+    private String genFrequencyChangeRequest(long wavelength) {
+        String request = "{\n" +
+                "\"ciena-ws-ptp-modem:frequency\": {\n" +
+                "\"value\": " + Long.toString(wavelength) + "\n" +
+                "}\n" +
+                "}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+    private String genChannelChangeRequest(int channel) {
+        String request = "{\n" +
+                "\"ciena-ws-ptp-modem:line-system-channel-number\": " +
+                Integer.toString(channel) + "\n}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+
+    private final String genUri(String uriFormat, PortNumber port) {
+        return String.format(uriFormat, port.name());
+    }
+
+    private boolean changePortState(PortNumber number, String state) {
+        log.debug("changing the port {} state to {}", number, state);
+        String uri = genUri(PORT_STATE_URI, number);
+        String request = genPortStateRequest(state);
+        return putNoReply(uri, request);
+    }
+
+    public boolean disablePort(PortNumber number) {
+        return changePortState(number, DISABLED);
+    }
+
+    public boolean enablePort(PortNumber number) {
+        return changePortState(number, ENABLED);
+    }
+
+    public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
+        String uri = genUri(FREQUENCY_URI, outPort);
+        long frequency = toFrequency(signal);
+        String request = genFrequencyChangeRequest(frequency);
+        return putNoReply(uri, request);
+    }
+
+    public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
+        String uri = genUri(CHANNEL_URI, outPort);
+        int channel = signal.spacingMultiplier() + MULTIPLIER_OFFSET;
+        log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
+        String request = genChannelChangeRequest(channel);
+        return putNoReply(uri, request);
+    }
+
+    private final long toFrequency(OchSignal signal) {
+        double frequency = BASE_FREQUENCY.asGHz() +
+                (signal.channelSpacing().frequency().asGHz() * (double) signal.slotGranularity());
+        return Double.valueOf(frequency).longValue();
+    }
+
+    public static int getMultiplierOffset() {
+        return MULTIPLIER_OFFSET;
+    }
+
+    private int put(String uri, String request) {
+        InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
+        int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
+        log.debug("response: {}", response);
+        return response;
+    }
+
+    private boolean putNoReply(String uri, String request) {
+        return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
+    }
+
+}
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
index 31de548..92aa0f5 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
@@ -51,6 +51,7 @@
 /**
  * Discovers the ports from a Ciena WaveServer Rest device.
  */
+//TODO: Use CienaRestDevice
 public class CienaWaveserverDeviceDescription extends AbstractHandlerBehaviour
         implements DeviceDescriptionDiscovery {
 
@@ -128,21 +129,15 @@
             String portId = sub.getString(PORT_ID);
             DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
             if (LINESIDE_PORT_ID.contains(portId)) {
-                // TX/OUT port
                 annotations.set(AnnotationKeys.CHANNEL_ID, sub.getString(CHANNEL_ID));
-                annotations.set(AnnotationKeys.PORT_NAME, portId + " TX");
+                // TX/OUT and RX/IN ports
+                annotations.set(AnnotationKeys.PORT_OUT, sub.getString(PORT_OUT));
+                annotations.set(AnnotationKeys.PORT_IN, sub.getString(PORT_IN));
                 ports.add(parseWaveServerCienaOchPorts(
-                        sub.getLong(PORT_OUT),
+                        Long.valueOf(portId),
                         sub,
                         annotations.build()));
 
-                // RX/IN port
-                annotations.set(AnnotationKeys.PORT_NAME, portId + " RX");
-                annotations.set(AnnotationKeys.CHANNEL_ID, sub.getString(CHANNEL_ID));
-                ports.add(parseWaveServerCienaOchPorts(
-                        sub.getLong(PORT_IN),
-                        sub,
-                        annotations.build()));
             } else if (!portId.equals("5") && !portId.equals("49")) {
                 DefaultAnnotations.builder()
                         .set(AnnotationKeys.PORT_NAME, portId);
@@ -184,6 +179,10 @@
                                   new OchSignal(gridType, chSpacing, spacingMult, 1), annotations);
     }
 
+    public static ArrayList<String> getLinesidePortId() {
+        return LINESIDE_PORT_ID;
+    }
+
     //FIXME remove when all optical types have two way information methods, see jira tickets
     private static long toGbps(long speed) {
         return speed * 1000;
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
index 0f3b6d7..a943f7a 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
@@ -17,110 +17,41 @@
 package org.onosproject.drivers.ciena;
 
 import org.onlab.util.Tools;
-import org.onosproject.net.AnnotationKeys;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.PortAdmin;
-import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.onosproject.protocol.rest.RestSBController;
 import org.slf4j.Logger;
 
-import javax.ws.rs.core.MediaType;
 import java.util.concurrent.CompletableFuture;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import javax.ws.rs.core.Response.Status;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
 public class CienaWaveserverPortAdmin extends AbstractHandlerBehaviour
         implements PortAdmin {
+    private CienaRestDevice restCiena;
     private final Logger log = getLogger(getClass());
-    private static final String APP_JSON = "application/json";
-    private static final String ENABLE = "enabled";
-    private static final String DISABLE = "disabled";
-
-    private final String generateUri(long number) {
-        return String.format("ws-ptps/ptps/%d/state", number);
-    }
-
-    private final String generateRequest(String state) {
-        String request = "{\n" +
-                "\"state\": {\n" +
-                "\"admin-state\": \"" + state + "\"\n}\n}";
-        log.debug("generated request: \n{}", request);
-        return request;
-    }
-
-    private final boolean put(long number, String state) {
-        String uri = generateUri(number);
-        String request = generateRequest(state);
-        DeviceId deviceId = handler().data().deviceId();
-        RestSBController controller =
-                checkNotNull(handler().get(RestSBController.class));
-        InputStream payload =
-                new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
-        int response = controller.put(deviceId, uri, payload,
-                                      MediaType.valueOf(APP_JSON));
-        log.debug("response: {}", response);
-        // expecting 204/NO_CONTENT_RESPONSE as successful response
-        return response == Status.NO_CONTENT.getStatusCode();
-    }
-
-    // returns null if specified port number was not a line side port
-    private Long getLineSidePort(PortNumber number) {
-        DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
-        DeviceId deviceId = handler().data().deviceId();
-        Port port = deviceService.getPort(deviceId, number);
-        if (port != null) {
-            String channelId = port.annotations().value(AnnotationKeys.CHANNEL_ID);
-            // any port that has channel is lineSidePort and will have TX and RX
-            if (channelId != null) {
-                String portName = port.annotations().value(AnnotationKeys.PORT_NAME);
-                // last three characters of portName will always be " TX" or " RX"
-                portName = portName.substring(0, portName.length() - 3);
-                log.debug("port number {} is mapped to {} lineside port",
-                          number, portName);
-                return new Long(portName);
-            }
-        }
-        // not a line-side port
-        return null;
-    }
 
     @Override
     public CompletableFuture<Boolean> disable(PortNumber number) {
-        log.debug("disabling port {}", number);
-        Long lineSidePort = getLineSidePort(number);
-        long devicePortNum;
-        if (lineSidePort != null) {
-            devicePortNum = lineSidePort.longValue();
-        } else {
-            devicePortNum = number.toLong();
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return CompletableFuture.completedFuture(false);
         }
-        CompletableFuture<Boolean> result =
-                CompletableFuture.completedFuture(put(devicePortNum, DISABLE));
-        return result;
+        return CompletableFuture.completedFuture(restCiena.disablePort(number));
     }
 
     @Override
     public CompletableFuture<Boolean> enable(PortNumber number) {
-        log.debug("enabling port {}", number);
-        Long lineSidePort = getLineSidePort(number);
-        long devicePortNum;
-        if (lineSidePort != null) {
-            devicePortNum = lineSidePort.longValue();
-        } else {
-            devicePortNum = number.toLong();
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return CompletableFuture.completedFuture(false);
         }
-        CompletableFuture<Boolean> result =
-                CompletableFuture.completedFuture(put(devicePortNum, ENABLE));
-        return result;
+        return CompletableFuture.completedFuture(restCiena.enablePort(number));
     }
 
     @Override
diff --git a/drivers/ciena/src/main/resources/ciena-drivers.xml b/drivers/ciena/src/main/resources/ciena-drivers.xml
index 6ff5d75..57ed4cf 100644
--- a/drivers/ciena/src/main/resources/ciena-drivers.xml
+++ b/drivers/ciena/src/main/resources/ciena-drivers.xml
@@ -23,6 +23,8 @@
                    impl="org.onosproject.drivers.ciena.CienaWaveserverDeviceDescription"/>
         <behaviour api="org.onosproject.net.behaviour.PortAdmin"
                    impl="org.onosproject.drivers.ciena.CienaWaveserverPortAdmin"/>
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.ciena.CienaFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
                    impl="org.onosproject.driver.optical.query.CBand50LambdaQuery"/>
     </driver>
diff --git a/drivers/p4runtime/BUCK b/drivers/p4runtime/BUCK
index 57079be..5c50f12 100644
--- a/drivers/p4runtime/BUCK
+++ b/drivers/p4runtime/BUCK
@@ -5,14 +5,22 @@
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
     '//incubator/grpc-dependencies:grpc-core-repkg-' + GRPC_VER,
     '//lib:grpc-netty-' + GRPC_VER,
+    '//core/store/serializers:onos-core-serializers',
+    '//lib:KRYO',
 ]
 
 BUNDLES = [
     ':onos-drivers-p4runtime',
 ]
 
-osgi_jar(
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+    '//core/api:onos-api-tests',
+]
+
+osgi_jar_with_tests (
     deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
 )
 
 onos_app (
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
index e14363e..75cf081 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
@@ -30,6 +30,7 @@
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.PiInstruction;
 import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
@@ -170,7 +171,7 @@
 
     @Override
     public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId) throws PiInterpreterException {
-
+        initTargetSpecificAttributes();
         if (treatment.allInstructions().size() == 0) {
             // No instructions means drop for us.
             return actionWithName(DROP);
@@ -184,17 +185,10 @@
         switch (instruction.type()) {
             case OUTPUT:
                 Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
-                PortNumber port = outInstruction.port();
-                if (!port.isLogical()) {
-                    return PiAction.builder()
-                            .withId(PiActionId.of(SET_EGRESS_PORT))
-                            .withParameter(new PiActionParam(PiActionParamId.of(PORT), copyFrom(port.toLong())))
-                            .build();
-                } else if (port.equals(CONTROLLER)) {
-                    return actionWithName(SEND_TO_CPU);
-                } else {
-                    throw new PiInterpreterException(format("Egress on logical port '%s' not supported", port));
-                }
+                return outputPiAction(outInstruction);
+            case PROTOCOL_INDEPENDENT:
+                PiInstruction piInstruction = (PiInstruction) instruction;
+                return (PiAction) piInstruction.action();
             case NOACTION:
                 return actionWithName(DROP);
             default:
@@ -202,6 +196,25 @@
         }
     }
 
+    private PiAction outputPiAction(Instructions.OutputInstruction outInstruction) throws PiInterpreterException {
+        PortNumber port = outInstruction.port();
+        if (!port.isLogical()) {
+            try {
+                return PiAction.builder()
+                        .withId(PiActionId.of(SET_EGRESS_PORT))
+                        .withParameter(new PiActionParam(PiActionParamId.of(PORT),
+                                                         fit(copyFrom(port.toLong()), portFieldBitWidth)))
+                        .build();
+            } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+                throw new PiInterpreterException(e.getMessage());
+            }
+        } else if (port.equals(CONTROLLER)) {
+            return actionWithName(SEND_TO_CPU);
+        } else {
+            throw new PiInterpreterException(format("Egress on logical port '%s' not supported", port));
+        }
+    }
+
     @Override
     public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
         return Optional.ofNullable(TABLE_COUNTER_MAP.get(piTableId));
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
index 15a40ca..e108123 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
@@ -16,71 +16,272 @@
 
 package org.onosproject.drivers.p4runtime;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.GroupId;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.group.Group;
 import org.onosproject.net.group.GroupBucket;
-import org.onosproject.net.group.GroupDescription;
 import org.onosproject.net.group.GroupOperation;
 import org.onosproject.net.group.GroupOperations;
 import org.onosproject.net.group.GroupProgrammable;
+import org.onosproject.net.group.GroupStore;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiTableAction;
 import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeGroupReference;
+import org.onosproject.p4runtime.api.P4RuntimeGroupWrapper;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
-public class P4RuntimeGroupProgrammable extends AbstractHandlerBehaviour implements GroupProgrammable {
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the group programmable behaviour for P4Runtime.
+ */
+public class P4RuntimeGroupProgrammable extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable {
+    private static final String ACT_GRP_MEMS = "action group members";
+    private static final String DELETE = "delete";
+    private static final String ACT_GRP = "action group";
+    private static final String INSERT = "insert";
+    private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
+    private static final int GROUP_ID_MASK = 0xffff;
+    public static final KryoNamespace KRYO = new KryoNamespace.Builder()
+            .register(KryoNamespaces.API)
+            .register(DefaultP4RuntimeGroupCookie.class)
+            .build("P4RuntimeGroupProgrammable");
 
     /*
-    Work in progress.
+     * About action groups in P4runtime:
+     * The type field is a place holder in p4runtime.proto right now, and we haven't defined it yet. You can assume all
+     * the groups are "select" as per the OF spec. As a remainder, in the P4 terminology a member corresponds to an OF
+     * bucket. Each member can also be used directly in the match table (kind of like an OF indirect group).
      */
 
-    private Device device;
-
+    // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
     /*
-    About action groups in P4runtime:
-    The type field is a place holder in p4runtime.proto right now, and we haven't defined it yet. You can assume all
-    the groups are "select" as per the OF spec. As a remainder, in the P4 terminology a member corresponds to an OF
-    bucket. Each member can also be used directly in the match table (kind of like an OF indirect group).
+    When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
+    issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
      */
+    private boolean deleteBeforeUpdate = true;
+
+    // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
+    /*
+    If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
+     */
+    private boolean checkStoreBeforeUpdate = true;
+
+    // Needed to synchronize operations over the same group.
+    private static final Map<P4RuntimeGroupReference, Lock> GROUP_LOCKS = Maps.newConcurrentMap();
+
+    // TODO: replace with distribute store
+    private static final Map<P4RuntimeGroupReference, P4RuntimeGroupWrapper> GROUP_STORE = Maps.newConcurrentMap();
+
+    private PiPipelineInterpreter interpreter;
+
+    protected boolean init() {
+        if (!setupBehaviour()) {
+            return false;
+        }
+        Device device = deviceService.getDevice(deviceId);
+        // Need an interpreter to map the bucket treatment to a PI action
+        if (!device.is(PiPipelineInterpreter.class)) {
+            log.warn("Can't find interpreter for device {}", device.id());
+        } else {
+            interpreter = device.as(PiPipelineInterpreter.class);
+        }
+        return true;
+    }
 
     @Override
     public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
+        if (!init()) {
+            // Ignore group operation of not initialized.
+            return;
+        }
+        Device device = handler().get(DeviceService.class).getDevice(deviceId);
 
         for (GroupOperation groupOp : groupOps.operations()) {
+            processGroupOp(device, groupOp);
+        }
+    }
+
+    private void processGroupOp(Device device, GroupOperation groupOp) {
+        GroupId groupId = groupOp.groupId();
+        GroupStore groupStore = handler().get(GroupStore.class);
+        Group group = groupStore.getGroup(device.id(), groupId);
+
+        // Most of this logic can go in a core service, e.g. PiGroupTranslationService
+        // From a P4Runtime perspective, we need first to insert members, then the group.
+        PiActionGroupId piActionGroupId = PiActionGroupId.of(groupOp.groupId().id());
+
+        PiActionGroup.Builder piActionGroupBuilder = PiActionGroup.builder()
+                .withId(piActionGroupId);
+
+        switch (group.type()) {
+            case SELECT:
+                piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
+                break;
+            default:
+                log.warn("Group type {} not supported, ignore group {}.", group.type(), groupId);
+                return;
+        }
+        /*
+            Problem:
+            In P4Runtime, action profiles (i.e. group tables) are specific to one or more tables.
+            Mapping of treatments depends on the target table. How do we derive the target table from here?
+
+            Solution:
+            - Add table information into app cookie and put into group description
+         */
+        // TODO: notify group service if we get deserialize error
+        DefaultP4RuntimeGroupCookie defaultP4RuntimeGroupCookie = KRYO.deserialize(group.appCookie().key());
+        PiTableId piTableId = defaultP4RuntimeGroupCookie.tableId();
+        PiActionProfileId piActionProfileId = defaultP4RuntimeGroupCookie.actionProfileId();
+        piActionGroupBuilder.withActionProfileId(piActionProfileId);
+
+        List<PiActionGroupMember> members = buildMembers(group, piActionGroupId, piTableId);
+        if (members == null) {
+            log.warn("Can't build members for group {} on {}", group, device.id());
+            return;
+        }
+
+        piActionGroupBuilder.addMembers(members);
+        PiActionGroup piActionGroup = piActionGroupBuilder.build();
+
+        P4RuntimeGroupReference groupRef =
+                new P4RuntimeGroupReference(deviceId, piActionProfileId, piActionGroupId);
+        Lock lock = GROUP_LOCKS.computeIfAbsent(groupRef, k -> new ReentrantLock());
+        lock.lock();
+
+
+        try {
+            P4RuntimeGroupWrapper oldGroupWrapper = GROUP_STORE.get(groupRef);
+            P4RuntimeGroupWrapper newGroupWrapper =
+                    new P4RuntimeGroupWrapper(piActionGroup, group, System.currentTimeMillis());
+            boolean success;
             switch (groupOp.opType()) {
                 case ADD:
-                    addGroup(deviceId, groupOp);
+                case MODIFY:
+                    success = writeGroupToDevice(oldGroupWrapper, piActionGroup, members);
+                    if (success) {
+                        GROUP_STORE.put(groupRef, newGroupWrapper);
+                    }
+                    break;
+                case DELETE:
+                    success = deleteGroupFromDevice(piActionGroup, members);
+                    if (success) {
+                        GROUP_STORE.remove(groupRef);
+                    }
                     break;
                 default:
                     throw new UnsupportedOperationException();
             }
+        } finally {
+            lock.unlock();
         }
     }
 
-    private void addGroup(DeviceId deviceId, GroupOperation groupOp) {
-
-        // Most of this logic can go in a core service, e.g. PiGroupTranslationService
-
-        // From a P4Runtime perspective, we need first to insert members, then the group.
-
-        PiActionGroupId piActionGroupId = PiActionGroupId.of(groupOp.groupId().id());
-
-        PiActionGroup.Builder piActionGroupBuilder = PiActionGroup.builder()
-                .withId(piActionGroupId)
-                .withType(PiActionGroup.Type.SELECT);
-
-        if (groupOp.groupType() != GroupDescription.Type.SELECT) {
-            // log error
+    /**
+     * Installs action group and members to device via client interface.
+     *
+     * @param oldGroupWrapper old group wrapper for the group; null if not exists
+     * @param piActionGroup the action group to be installed
+     * @param members members of the action group
+     * @return true if install success; false otherwise
+     */
+    private boolean writeGroupToDevice(P4RuntimeGroupWrapper oldGroupWrapper,
+                                       PiActionGroup piActionGroup,
+                                       Collection<PiActionGroupMember> members) {
+        boolean success = true;
+        CompletableFuture<Boolean> writeSuccess;
+        if (checkStoreBeforeUpdate && oldGroupWrapper != null &&
+                oldGroupWrapper.piActionGroup().equals(piActionGroup)) {
+            // Action group already exists, ignore it
+            return true;
         }
+        if (deleteBeforeUpdate && oldGroupWrapper != null) {
+            success = deleteGroupFromDevice(oldGroupWrapper.piActionGroup(),
+                                            oldGroupWrapper.piActionGroup().members());
+        }
+        writeSuccess = client.writeActionGroupMembers(piActionGroup,
+                                                      members,
+                                                      P4RuntimeClient.WriteOperationType.INSERT,
+                                                      pipeconf);
+        success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, INSERT);
+
+        writeSuccess = client.writeActionGroup(piActionGroup,
+                                               P4RuntimeClient.WriteOperationType.INSERT,
+                                               pipeconf);
+        success = success && completeSuccess(writeSuccess, ACT_GRP, INSERT);
+        return success;
+    }
+
+    private boolean deleteGroupFromDevice(PiActionGroup piActionGroup,
+                                          Collection<PiActionGroupMember> members) {
+        boolean success;
+        CompletableFuture<Boolean> writeSuccess;
+        writeSuccess = client.writeActionGroup(piActionGroup,
+                                P4RuntimeClient.WriteOperationType.DELETE,
+                                pipeconf);
+        success = completeSuccess(writeSuccess, ACT_GRP, DELETE);
+        writeSuccess = client.writeActionGroupMembers(piActionGroup,
+                                       members,
+                                       P4RuntimeClient.WriteOperationType.DELETE,
+                                       pipeconf);
+        success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, DELETE);
+        return success;
+    }
+
+    private boolean completeSuccess(CompletableFuture<Boolean> completableFuture,
+                                    String topic, String action) {
+        try {
+            return completableFuture.get();
+        } catch (InterruptedException | ExecutionException e) {
+            log.warn("Can't {} {} due to {}", action, topic, e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Build pi action group members from group.
+     *
+     * @param group the group
+     * @param piActionGroupId the PI action group id of the group
+     * @param piTableId the PI table related to the group
+     * @return list of PI action group members; null if can't build member list
+     */
+    private List<PiActionGroupMember> buildMembers(Group group, PiActionGroupId piActionGroupId, PiTableId piTableId) {
+        GroupId groupId = group.id();
+        ImmutableList.Builder<PiActionGroupMember> membersBuilder = ImmutableList.builder();
 
         int bucketIdx = 0;
-        for (GroupBucket bucket : groupOp.buckets().buckets()) {
+        for (GroupBucket bucket : group.buckets().buckets()) {
             /*
             Problem:
             In P4Runtime action group members, i.e. action buckets, are associated to a numeric ID chosen
@@ -95,40 +296,46 @@
             Hack:
             Statically derive member ID by combining groupId and position of the bucket in the list.
              */
-            int memberId = ByteBuffer.allocate(4)
-                    .putShort((short) (piActionGroupId.id() % 2 ^ 16))
-                    .putShort((short) (bucketIdx % 2 ^ 16))
-                    .getInt();
+            ByteBuffer bb = ByteBuffer.allocate(4)
+                    .putShort((short) (piActionGroupId.id() & GROUP_ID_MASK))
+                    .putShort((short) bucketIdx);
+            bb.rewind();
+            int memberId = bb.getInt();
 
-            // Need an interpreter to map the bucket treatment to a PI action
+            bucketIdx++;
+            PiAction action;
+            if (interpreter != null) {
+                // if we have interpreter, use interpreter
+                try {
+                    action = interpreter.mapTreatment(bucket.treatment(), piTableId);
+                } catch (PiPipelineInterpreter.PiInterpreterException e) {
+                    log.warn("Can't map treatment {} to action due to {}, ignore group {}",
+                             bucket.treatment(), e.getMessage(), groupId);
+                    return null;
+                }
+            } else {
+                // if we don't have interpreter, accept PiInstruction only
+                TrafficTreatment treatment = bucket.treatment();
 
-            if (!device.is(PiPipelineInterpreter.class)) {
-                // log error
-            }
+                if (treatment.allInstructions().size() > 1) {
+                    log.warn("Treatment {} has multiple instructions, ignore group {}",
+                             treatment, groupId);
+                    return null;
+                }
+                Instruction instruction = treatment.allInstructions().get(0);
+                if (instruction.type() != Instruction.Type.PROTOCOL_INDEPENDENT) {
+                    log.warn("Instruction {} is not a PROTOCOL_INDEPENDENT type, ignore group {}",
+                             instruction, groupId);
+                    return null;
+                }
 
-            PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
-
-            /*
-            Problem:
-            In P4Runtime, action profiles (i.e. group tables) are specific to one or more tables.
-            Mapping of treatments depends on the target table. How do we derive the target table from here?
-
-            Solution:
-            - Change GroupDescription to allow applications to specify a table where this group will be called from.
-
-            Hack:
-            Assume we support pipelines with only one action profile associated to only one table, i.e. derive the
-            table ID by looking at the P4Info.
-             */
-
-            PiTableId piTableId = PiTableId.of("derive from P4Info");
-
-
-            PiAction action = null;
-            try {
-                action = interpreter.mapTreatment(bucket.treatment(), piTableId);
-            } catch (PiPipelineInterpreter.PiInterpreterException e) {
-                // log error
+                PiInstruction piInstruction = (PiInstruction) instruction;
+                if (piInstruction.action().type() != PiTableAction.Type.ACTION) {
+                    log.warn("Action {} is not an ACTION type, ignore group {}",
+                             piInstruction.action(), groupId);
+                    return null;
+                }
+                action = (PiAction) piInstruction.action();
             }
 
             PiActionGroupMember member = PiActionGroupMember.builder()
@@ -137,15 +344,97 @@
                     .withWeight(bucket.weight())
                     .build();
 
-            piActionGroupBuilder.addMember(member);
+            membersBuilder.add(member);
+        }
+        return membersBuilder.build();
+    }
 
-            // Use P4RuntimeClient to install member;
-            // TODO: implement P4RuntimeClient method.
+    @Override
+    public Collection<Group> getGroups() {
+        if (!init()) {
+            return Collections.emptySet();
         }
 
-        PiActionGroup piActionGroup = piActionGroupBuilder.build();
+        Collection<Group> result = Sets.newHashSet();
+        Collection<PiActionProfileId> piActionProfileIds = Sets.newHashSet();
 
-        // Use P4RuntimeClient to insert group.
-        // TODO: implement P4RuntimeClient method.
+        // Collection action profile Ids
+        // TODO: find better way to get all action profile ids....
+        GROUP_STORE.forEach((groupRef, wrapper) -> piActionProfileIds.add(groupRef.actionProfileId()));
+
+        AtomicBoolean success = new AtomicBoolean(true);
+        piActionProfileIds.forEach(actionProfileId -> {
+            Collection<PiActionGroup> piActionGroups = Sets.newHashSet();
+            try {
+                Collection<PiActionGroup> groupsFromDevice =
+                        client.dumpGroups(actionProfileId, pipeconf).get();
+                if (groupsFromDevice == null) {
+                    // Got error
+                    success.set(false);
+                } else {
+                    piActionGroups.addAll(groupsFromDevice);
+                }
+            } catch (ExecutionException | InterruptedException e) {
+                log.error("Exception while dumping groups for action profile {}: {}",
+                          actionProfileId.id(), deviceId, e);
+                success.set(false);
+            }
+
+            piActionGroups.forEach(piActionGroup -> {
+                PiActionGroupId actionGroupId = piActionGroup.id();
+                P4RuntimeGroupReference groupRef =
+                        new P4RuntimeGroupReference(deviceId, actionProfileId, actionGroupId);
+                P4RuntimeGroupWrapper wrapper = GROUP_STORE.get(groupRef);
+
+                if (wrapper == null) {
+                    // group exists in client, but can't find in ONOS
+                    log.warn("Can't find action profile group {} from local store.",
+                             groupRef);
+                    return;
+                }
+                if (!wrapper.piActionGroup().equals(piActionGroup)) {
+                    log.warn("Group from device is different to group from local store.");
+                    return;
+                }
+                result.add(wrapper.group());
+
+            });
+        });
+
+        if (!success.get()) {
+            // Got error while dump groups from device.
+            return Collections.emptySet();
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * P4Runtime app cookie for group.
+     */
+    public static class DefaultP4RuntimeGroupCookie {
+        private PiTableId tableId;
+        private PiActionProfileId piActionProfileId;
+        private Integer groupId;
+
+        public DefaultP4RuntimeGroupCookie(PiTableId tableId,
+                                           PiActionProfileId piActionProfileId,
+                                           Integer groupId) {
+            this.tableId = tableId;
+            this.piActionProfileId = piActionProfileId;
+            this.groupId = groupId;
+        }
+
+        public PiTableId tableId() {
+            return tableId;
+        }
+
+        public PiActionProfileId actionProfileId() {
+            return piActionProfileId;
+        }
+
+        public Integer groupId() {
+            return groupId;
+        }
     }
 }
diff --git a/drivers/p4runtime/src/test/java/org/onosproject/drivers/p4runtime/P4runtimeGroupProgrammableTest.java b/drivers/p4runtime/src/test/java/org/onosproject/drivers/p4runtime/P4runtimeGroupProgrammableTest.java
new file mode 100644
index 0000000..c334218
--- /dev/null
+++ b/drivers/p4runtime/src/test/java/org/onosproject/drivers/p4runtime/P4runtimeGroupProgrammableTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.p4runtime;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.drivers.p4runtime.P4RuntimeGroupProgrammable.DefaultP4RuntimeGroupCookie;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeController;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.group.GroupDescription.Type.SELECT;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
+
+public class P4runtimeGroupProgrammableTest {
+    private static final String P4INFO_PATH = "/default.p4info";
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:p4runtime:1");
+    private static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("p4runtime-mock-pipeconf");
+    private static final PiPipeconf PIPECONF = buildPipeconf();
+    private static final PiTableId ECMP_TABLE_ID = PiTableId.of("ecmp");
+    private static final PiActionProfileId ACT_PROF_ID = PiActionProfileId.of("ecmp_selector");
+    private static final ApplicationId APP_ID = TestApplicationId.create("P4runtimeGroupProgrammableTest");
+    private static final GroupId GROUP_ID = GroupId.valueOf(1);
+    private static final PiActionId EGRESS_PORT_ACTION_ID = PiActionId.of("set_egress_port");
+    private static final PiActionParamId PORT_PARAM_ID = PiActionParamId.of("port");
+    private static final List<GroupBucket> BUCKET_LIST = ImmutableList.of(
+            outputBucket(1),
+            outputBucket(2),
+            outputBucket(3)
+    );
+    private static final DefaultP4RuntimeGroupCookie COOKIE =
+            new DefaultP4RuntimeGroupCookie(ECMP_TABLE_ID, ACT_PROF_ID, GROUP_ID.id());
+    private static final GroupKey GROUP_KEY =
+            new DefaultGroupKey(P4RuntimeGroupProgrammable.KRYO.serialize(COOKIE));
+    private static final GroupBuckets BUCKETS = new GroupBuckets(BUCKET_LIST);
+    private static final GroupDescription GROUP_DESC =
+            new DefaultGroupDescription(DEVICE_ID,
+                                        SELECT,
+                                        BUCKETS,
+                                        GROUP_KEY,
+                                        GROUP_ID.id(),
+                                        APP_ID);
+    private static final Group GROUP = new DefaultGroup(GROUP_ID, GROUP_DESC);
+    private static final int DEFAULT_MEMBER_WEIGHT = 1;
+    private static final int BASE_MEM_ID = 65535;
+    private static final Collection<PiActionGroupMember> EXPECTED_MEMBERS =
+            ImmutableSet.of(
+                    outputMember(1),
+                    outputMember(2),
+                    outputMember(3)
+            );
+
+    private P4RuntimeGroupProgrammable programmable;
+    private DriverHandler driverHandler;
+    private DriverData driverData;
+    private P4RuntimeController controller;
+    private P4RuntimeClient client;
+    private PiPipeconfService piPipeconfService;
+    private DeviceService deviceService;
+    private Device device;
+    private GroupStore groupStore;
+
+    private static PiPipeconf buildPipeconf() {
+        final URL p4InfoUrl = P4runtimeGroupProgrammableTest.class.getResource(P4INFO_PATH);
+        return DefaultPiPipeconf.builder()
+                .withId(PIPECONF_ID)
+                .withPipelineModel(niceMock(PiPipelineModel.class))
+                .addExtension(P4_INFO_TEXT, p4InfoUrl)
+                .build();
+    }
+
+    private static GroupBucket outputBucket(int portNum) {
+        ImmutableByteSequence paramVal = ImmutableByteSequence.copyFrom(portNum);
+        PiActionParam param = new PiActionParam(PiActionParamId.of(PORT_PARAM_ID.name()), paramVal);
+        PiTableAction action = PiAction.builder().withId(EGRESS_PORT_ACTION_ID).withParameter(param).build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .add(Instructions.piTableAction(action))
+                .build();
+
+        return DefaultGroupBucket.createSelectGroupBucket(treatment);
+    }
+
+    private static PiActionGroupMember outputMember(int portNum) {
+        PiActionParam param = new PiActionParam(PORT_PARAM_ID,
+                                                ImmutableByteSequence.copyFrom(portNum));
+        PiAction piAction = PiAction.builder()
+                .withId(EGRESS_PORT_ACTION_ID)
+                .withParameter(param).build();
+
+        return PiActionGroupMember.builder()
+                .withAction(piAction)
+                .withId(PiActionGroupMemberId.of(BASE_MEM_ID + portNum))
+                .withWeight(DEFAULT_MEMBER_WEIGHT)
+                .build();
+    }
+
+    @Before
+    public void setup() {
+        driverHandler = EasyMock.niceMock(DriverHandler.class);
+        driverData = EasyMock.niceMock(DriverData.class);
+        controller = EasyMock.niceMock(P4RuntimeController.class);
+        client = EasyMock.niceMock(P4RuntimeClient.class);
+        piPipeconfService = EasyMock.niceMock(PiPipeconfService.class);
+        deviceService = EasyMock.niceMock(DeviceService.class);
+        device = EasyMock.niceMock(Device.class);
+        groupStore = EasyMock.niceMock(GroupStore.class);
+
+        expect(controller.hasClient(DEVICE_ID)).andReturn(true).anyTimes();
+        expect(controller.getClient(DEVICE_ID)).andReturn(client).anyTimes();
+        expect(device.is(PiPipelineInterpreter.class)).andReturn(true).anyTimes();
+        expect(device.id()).andReturn(DEVICE_ID).anyTimes();
+        expect(deviceService.getDevice(DEVICE_ID)).andReturn(device).anyTimes();
+        expect(driverData.deviceId()).andReturn(DEVICE_ID).anyTimes();
+        expect(groupStore.getGroup(DEVICE_ID, GROUP_ID)).andReturn(GROUP).anyTimes();
+        expect(piPipeconfService.ofDevice(DEVICE_ID)).andReturn(Optional.of(PIPECONF_ID)).anyTimes();
+        expect(piPipeconfService.getPipeconf(PIPECONF_ID)).andReturn(Optional.of(PIPECONF)).anyTimes();
+        expect(driverHandler.data()).andReturn(driverData).anyTimes();
+        expect(driverHandler.get(P4RuntimeController.class)).andReturn(controller).anyTimes();
+        expect(driverHandler.get(PiPipeconfService.class)).andReturn(piPipeconfService).anyTimes();
+        expect(driverHandler.get(DeviceService.class)).andReturn(deviceService).anyTimes();
+        expect(driverHandler.get(GroupStore.class)).andReturn(groupStore).anyTimes();
+
+        programmable = new P4RuntimeGroupProgrammable();
+        programmable.setHandler(driverHandler);
+        programmable.setData(driverData);
+        EasyMock.replay(driverHandler, driverData, controller, piPipeconfService,
+                        deviceService, device, groupStore);
+    }
+
+    /**
+     * Test init function.
+     */
+    @Test
+    public void testInit() {
+        programmable.init();
+    }
+
+    /**
+     * Test add group with buckets.
+     */
+    @Test
+    public void testAddGroup() {
+        List<GroupOperation> ops = Lists.newArrayList();
+        ops.add(GroupOperation.createAddGroupOperation(GROUP_ID, SELECT, BUCKETS));
+        GroupOperations groupOps = new GroupOperations(ops);
+        CompletableFuture<Boolean> completeTrue = new CompletableFuture<>();
+        completeTrue.complete(true);
+
+        Capture<PiActionGroup> groupCapture1 = EasyMock.newCapture();
+        expect(client.writeActionGroup(EasyMock.capture(groupCapture1), EasyMock.eq(INSERT), EasyMock.eq(PIPECONF)))
+                .andReturn(completeTrue).anyTimes();
+
+        Capture<PiActionGroup> groupCapture2 = EasyMock.newCapture();
+        Capture<Collection<PiActionGroupMember>> membersCapture = EasyMock.newCapture();
+        expect(client.writeActionGroupMembers(EasyMock.capture(groupCapture2),
+                                                       EasyMock.capture(membersCapture),
+                                                       EasyMock.eq(INSERT),
+                                                       EasyMock.eq(PIPECONF)))
+                .andReturn(completeTrue).anyTimes();
+
+        EasyMock.replay(client);
+        programmable.performGroupOperation(DEVICE_ID, groupOps);
+
+        // verify group installed by group programmable
+        PiActionGroup group1 = groupCapture1.getValue();
+        PiActionGroup group2 = groupCapture2.getValue();
+        assertEquals("Groups should be equal", group1, group2);
+        assertEquals(GROUP_ID.id(), group1.id().id());
+        assertEquals(PiActionGroup.Type.SELECT, group1.type());
+        assertEquals(ACT_PROF_ID, group1.actionProfileId());
+
+        // members installed
+        Collection<PiActionGroupMember> members = group1.members();
+        assertEquals(3, members.size());
+
+        Assert.assertTrue(EXPECTED_MEMBERS.containsAll(members));
+        Assert.assertTrue(members.containsAll(EXPECTED_MEMBERS));
+    }
+
+    /**
+     * Test remove group with buckets.
+     */
+    @Test
+    public void testDelGroup() {
+        List<GroupOperation> ops = Lists.newArrayList();
+        ops.add(GroupOperation.createDeleteGroupOperation(GROUP_ID, SELECT));
+        GroupOperations groupOps = new GroupOperations(ops);
+        CompletableFuture<Boolean> completeTrue = new CompletableFuture<>();
+        completeTrue.complete(true);
+
+        Capture<PiActionGroup> groupCapture1 = EasyMock.newCapture();
+        expect(client.writeActionGroup(EasyMock.capture(groupCapture1), EasyMock.eq(DELETE), EasyMock.eq(PIPECONF)))
+                .andReturn(completeTrue).anyTimes();
+
+        Capture<PiActionGroup> groupCapture2 = EasyMock.newCapture();
+        Capture<Collection<PiActionGroupMember>> membersCapture = EasyMock.newCapture();
+        expect(client.writeActionGroupMembers(EasyMock.capture(groupCapture2),
+                                                       EasyMock.capture(membersCapture),
+                                                       EasyMock.eq(DELETE),
+                                                       EasyMock.eq(PIPECONF)))
+                .andReturn(completeTrue).anyTimes();
+
+        EasyMock.replay(client);
+        programmable.performGroupOperation(DEVICE_ID, groupOps);
+
+        // verify group installed by group programmable
+        PiActionGroup group1 = groupCapture1.getValue();
+        PiActionGroup group2 = groupCapture2.getValue();
+        assertEquals("Groups should be equal", group1, group2);
+        assertEquals(GROUP_ID.id(), group1.id().id());
+        assertEquals(PiActionGroup.Type.SELECT, group1.type());
+        assertEquals(ACT_PROF_ID, group1.actionProfileId());
+
+        // members installed
+        Collection<PiActionGroupMember> members = group1.members();
+        assertEquals(3, members.size());
+
+        Assert.assertTrue(EXPECTED_MEMBERS.containsAll(members));
+        Assert.assertTrue(members.containsAll(EXPECTED_MEMBERS));
+    }
+}
diff --git a/drivers/p4runtime/src/test/resources/default.p4info b/drivers/p4runtime/src/test/resources/default.p4info
new file mode 120000
index 0000000..8f71cbe
--- /dev/null
+++ b/drivers/p4runtime/src/test/resources/default.p4info
@@ -0,0 +1 @@
+../../../../../tools/test/p4src/p4-16/p4c-out/default.p4info
\ No newline at end of file
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java
index e41a5ff..6b532c3 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java
@@ -19,8 +19,8 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleStoreDelegate;
 import org.onosproject.net.flow.TableStatisticsEntry;
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java
index 89eb591..8a4ec34 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java
@@ -17,7 +17,7 @@
 
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 
 /**
  * Abstraction of a virtual flow rule provider.
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
index 56c9489..237c3e8 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
@@ -40,10 +40,10 @@
 import org.onosproject.net.flow.DefaultFlowEntry;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleOperation;
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
index bc519d4..8e497e8 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
@@ -56,8 +56,8 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleOperations;
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java
index 92dd4ab..d567a94 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java
@@ -43,7 +43,7 @@
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criteria;
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java
index 0a4cfed..c447755 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java
@@ -49,7 +49,7 @@
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleService;
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java
index e2fddf4..5684a72 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java
@@ -51,7 +51,7 @@
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flowobjective.FlowObjectiveServiceAdapter;
 import org.onosproject.net.flowobjective.ForwardingObjective;
diff --git a/incubator/protobuf/models/src/main/java/org/onosproject/incubator/protobuf/models/core/ApplicationProtoTranslator.java b/incubator/protobuf/models/src/main/java/org/onosproject/incubator/protobuf/models/core/ApplicationProtoTranslator.java
index a4b2ca6..eba30ee 100644
--- a/incubator/protobuf/models/src/main/java/org/onosproject/incubator/protobuf/models/core/ApplicationProtoTranslator.java
+++ b/incubator/protobuf/models/src/main/java/org/onosproject/incubator/protobuf/models/core/ApplicationProtoTranslator.java
@@ -23,7 +23,6 @@
 import org.onosproject.incubator.protobuf.models.security.PermissionProtoTranslator;
 import org.onosproject.security.Permission;
 
-import java.util.Optional;
 import java.util.Set;
 
 import static org.onosproject.grpc.core.models.ApplicationProtoOuterClass.ApplicationProto.getDefaultInstance;
@@ -46,12 +45,21 @@
         app.getPermissionsList().forEach(p ->
                 permissions.add(PermissionProtoTranslator.translate(p)));
 
-        return new DefaultApplication(ApplicationIdProtoTranslator.translate(app.getAppId()),
-                VersionProtoTranslator.translate(app.getVersion()), app.getTitle(),
-                app.getDescription(), app.getOrigin(), app.getCategory(), app.getUrl(),
-                app.getReadme(), app.toByteArray(),
-                (ApplicationRole) ApplicationEnumsProtoTranslator.translate(app.getRole()).get(),
-                permissions, Optional.empty(), app.getFeaturesList(), app.getRequiredAppsList());
+        return DefaultApplication.builder()
+                .withAppId(ApplicationIdProtoTranslator.translate(app.getAppId()))
+                .withVersion(VersionProtoTranslator.translate(app.getVersion()))
+                .withTitle(app.getTitle())
+                .withDescription(app.getDescription())
+                .withOrigin(app.getOrigin())
+                .withCategory(app.getCategory())
+                .withUrl(app.getUrl())
+                .withReadme(app.getReadme())
+                .withIcon(app.toByteArray())
+                .withRole((ApplicationRole) ApplicationEnumsProtoTranslator.translate(app.getRole()).get())
+                .withPermissions(permissions)
+                .withFeatures(app.getFeaturesList())
+                .withRequiredApps(app.getRequiredAppsList())
+                .build();
     }
 
     /**
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualFlowRuleStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualFlowRuleStore.java
index 943627a..aaaf766 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualFlowRuleStore.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualFlowRuleStore.java
@@ -54,10 +54,10 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.FlowRuleStoreDelegate;
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualFlowRuleStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualFlowRuleStore.java
index 3f02450..502796b 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualFlowRuleStore.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualFlowRuleStore.java
@@ -40,10 +40,10 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
-import org.onosproject.net.flow.FlowRuleBatchRequest;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleStoreDelegate;
 import org.onosproject.net.flow.StoredFlowEntry;
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchEvent.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchEvent.java
index 6ca341d..21275f3 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchEvent.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchEvent.java
@@ -17,7 +17,7 @@
 package org.onosproject.incubator.store.virtual.impl.primitives;
 
 import org.onosproject.incubator.net.virtual.NetworkId;
-import org.onosproject.net.flow.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
 
 import java.util.Objects;
 
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchOperation.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchOperation.java
index c390ff6..5268d5f 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchOperation.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/primitives/VirtualFlowRuleBatchOperation.java
@@ -17,7 +17,7 @@
 package org.onosproject.incubator.store.virtual.impl.primitives;
 
 import org.onosproject.incubator.net.virtual.NetworkId;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 
 import java.util.Objects;
 
diff --git a/lib/BUCK b/lib/BUCK
index b4eed7b..8b6dc1d 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -1,4 +1,4 @@
-# ***** This file was auto-generated at Wed, 13 Sep 2017 22:16:27 GMT. Do not edit this file manually. *****
+# ***** This file was auto-generated at Fri, 22 Sep 2017 06:46:04 GMT. Do not edit this file manually. *****
 # ***** Use onos-lib-gen *****
 
 pass_thru_pom(
@@ -50,6 +50,7 @@
     ':junit',
     ':easymock',
     ':hamcrest-all',
+    ':hamcrest-optional',
     ':guava-testlib',
     '//utils/junit:onlab-junit',
   ],
@@ -1509,3 +1510,12 @@
   visibility = [ 'PUBLIC' ],
 )
 
+remote_jar (
+  name = 'hamcrest-optional',
+  out = 'hamcrest-optional-1.1.0.jar',
+  url = 'mvn:com.spotify:hamcrest-optional:jar:1.1.0',
+  sha1 = 'c2dfe3a43794b15fb4c28de0027fe6e249855b3b',
+  maven_coords = 'com.spotify:hamcrest-optional:jar:NON-OSGI:1.1.0',
+  visibility = [ 'PUBLIC' ],
+)
+
diff --git a/lib/deps.json b/lib/deps.json
index 5a9124c..071d95f 100644
--- a/lib/deps.json
+++ b/lib/deps.json
@@ -29,6 +29,7 @@
       "junit",
       "easymock",
       "hamcrest-all",
+      "hamcrest-optional",
       "guava-testlib",
       "//utils/junit:onlab-junit"
     ],
@@ -268,6 +269,7 @@
     "google-errorprone-2.0.19": "mvn:com.google.errorprone:error_prone_annotations:2.0.19",
     "google-instrumentation-0.3.0": "mvn:com.google.instrumentation:instrumentation-api:0.3.0",
     "bcpkix-jdk15on": "mvn:org.bouncycastle:bcpkix-jdk15on:1.58",
-    "bcprov-jdk15on": "mvn:org.bouncycastle:bcprov-jdk15on:1.58"
+    "bcprov-jdk15on": "mvn:org.bouncycastle:bcprov-jdk15on:1.58",
+    "hamcrest-optional": "mvn:com.spotify:hamcrest-optional:1.1.0"
   }
 }
diff --git a/lib/pom.xml b/lib/pom.xml
index 07ef09b..bae6e67 100644
--- a/lib/pom.xml
+++ b/lib/pom.xml
@@ -77,6 +77,12 @@
                 <version>1.3</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>com.spotify</groupId>
+                <artifactId>hamcrest-optional</artifactId>
+                <version>1.1.0</version>
+                <scope>test</scope>
+            </dependency>
 
             <dependency>
                 <groupId>org.slf4j</groupId>
@@ -650,6 +656,12 @@
         </dependency>
 
         <dependency>
+            <groupId>com.spotify</groupId>
+            <artifactId>hamcrest-optional</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava-testlib</artifactId>
             <scope>test</scope>
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index 9760ad4..a1b984b 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -18,9 +18,12 @@
 
 import com.google.common.annotations.Beta;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
@@ -129,6 +132,42 @@
                                                                       PiPipeconf pipeconf);
 
     /**
+     * Performs the given write operation for the given action group members and pipeconf.
+     *
+     * @param group action group
+     * @param members the collection of action group members
+     * @param opType write operation type
+     * @param pipeconf the pipeconf currently deployed on the device
+     * @return true if the operation was successful, false otherwise
+     */
+    CompletableFuture<Boolean> writeActionGroupMembers(PiActionGroup group,
+                                                       Collection<PiActionGroupMember> members,
+                                                       WriteOperationType opType,
+                                                       PiPipeconf pipeconf);
+
+    /**
+     * Performs the given write operation for the given action group and pipeconf.
+     *
+     * @param group the action group
+     * @param opType write operation type
+     * @param pipeconf the pipeconf currently deployed on the device
+     * @return true if the operation was successful, false otherwise
+     */
+    CompletableFuture<Boolean> writeActionGroup(PiActionGroup group,
+                                                WriteOperationType opType,
+                                                PiPipeconf pipeconf);
+
+    /**
+     * Dumps all groups currently installed for the given action profile.
+     *
+     * @param actionProfileId the action profile id
+     * @param pipeconf the pipeconf currently deployed on the device
+     * @return completable future of a collection of groups
+     */
+    CompletableFuture<Collection<PiActionGroup>> dumpGroups(PiActionProfileId actionProfileId,
+                                                            PiPipeconf pipeconf);
+
+    /**
      * Shutdown the client by terminating any active RPC such as the stream channel.
      */
     void shutdown();
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java
new file mode 100644
index 0000000..543ae4d
--- /dev/null
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.api;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+
+/**
+ * Class containing the reference for a group in P4Runtime.
+ */
+@Beta
+public final class P4RuntimeGroupReference {
+    private final DeviceId deviceId;
+    private final PiActionProfileId piActionProfileId;
+    private final PiActionGroupId groupId;
+
+    /**
+     * Creates P4 runtime group reference.
+     *
+     * @param deviceId the device id of group
+     * @param piActionProfileId the action profile id
+     * @param groupId the group Id of group
+     */
+    public P4RuntimeGroupReference(DeviceId deviceId, PiActionProfileId piActionProfileId,
+                                   PiActionGroupId groupId) {
+        this.deviceId = deviceId;
+        this.piActionProfileId = piActionProfileId;
+        this.groupId = groupId;
+    }
+
+    /**
+     * Gets device id of this group.
+     *
+     * @return the device id
+     */
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    /**
+     * Gets action profile id of this group.
+     *
+     * @return the action profile id
+     */
+    public PiActionProfileId actionProfileId() {
+        return piActionProfileId;
+    }
+
+    /**
+     * Gets group id of this group.
+     *
+     * @return group id
+     */
+    public PiActionGroupId groupId() {
+        return groupId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        P4RuntimeGroupReference that = (P4RuntimeGroupReference) o;
+        return Objects.equal(deviceId, that.deviceId) &&
+                Objects.equal(piActionProfileId, that.piActionProfileId) &&
+                Objects.equal(groupId, that.groupId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(deviceId, piActionProfileId, groupId);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("deviceId", deviceId)
+                .add("piActionProfileId", piActionProfileId)
+                .add("groupId", groupId)
+                .toString();
+    }
+}
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupWrapper.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupWrapper.java
new file mode 100644
index 0000000..8cb4b83
--- /dev/null
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.api;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+
+/**
+ * A wrapper for a ONOS group installed on a P4Runtime device.
+ */
+@Beta
+public class P4RuntimeGroupWrapper {
+    private final PiActionGroup piActionGroup;
+    private final Group group;
+    private final long installMilliSeconds;
+
+    /**
+     * Creates new group wrapper.
+     *
+     * @param piActionGroup the Pi action group
+     * @param group the group
+     * @param installMilliSeconds the installation time
+     */
+    public P4RuntimeGroupWrapper(PiActionGroup piActionGroup, Group group,
+                                 long installMilliSeconds) {
+        this.piActionGroup = piActionGroup;
+        this.group = group;
+        this.installMilliSeconds = installMilliSeconds;
+    }
+
+    /**
+     * Gets PI action group from this wrapper.
+     *
+     * @return the PI action group
+     */
+    public PiActionGroup piActionGroup() {
+        return piActionGroup;
+    }
+
+    /**
+     * Gets group from this wrapper.
+     *
+     * @return the group
+     */
+    public Group group() {
+        return group;
+    }
+
+    /**
+     * Gets installation time of this wrapper.
+     *
+     * @return the installation time
+     */
+    public long installMilliSeconds() {
+        return installMilliSeconds;
+    }
+}
diff --git a/protocols/p4runtime/ctl/BUCK b/protocols/p4runtime/ctl/BUCK
index 899a1ff..cb22402 100644
--- a/protocols/p4runtime/ctl/BUCK
+++ b/protocols/p4runtime/ctl/BUCK
@@ -13,8 +13,9 @@
 
 TEST_DEPS = [
     '//lib:TEST',
+    '//lib:GRPC_TEST_1.3',
     '//lib:minimal-json',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//lib:grpc-protobuf-lite-' + GRPC_VER,
 ]
 
 osgi_jar_with_tests(
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
new file mode 100644
index 0000000..472bf8e
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import p4.P4RuntimeOuterClass.ActionProfileGroup;
+import p4.P4RuntimeOuterClass.ActionProfileGroup.Member;
+import p4.P4RuntimeOuterClass.ActionProfileMember;
+import p4.config.P4InfoOuterClass;
+
+import java.util.Collection;
+import java.util.Map;
+
+import static java.lang.String.format;
+
+/**
+ * Encoder/Decoder for action profile group.
+ */
+public final class ActionProfileGroupEncoder {
+    private ActionProfileGroupEncoder() {
+        // hide default constructor
+    }
+
+    /**
+     * Encode a PI action group to a action profile group.
+     *
+     * @param piActionGroup the action profile group
+     * @param pipeconf the pipeconf
+     * @return a action profile group encoded from PI action group
+     * @throws P4InfoBrowser.NotFoundException if can't find action profile from P4Info browser
+     * @throws EncodeException if can't find P4Info from pipeconf
+     */
+    static ActionProfileGroup encode(PiActionGroup piActionGroup, PiPipeconf pipeconf)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        if (browser == null) {
+            throw new EncodeException(format("Can't get P4 info browser from pipeconf %s", pipeconf));
+        }
+
+        PiActionProfileId piActionProfileId = piActionGroup.actionProfileId();
+        int actionProfileId;
+        P4InfoOuterClass.ActionProfile actionProfile =
+                browser.actionProfiles().getByName(piActionProfileId.id());
+        actionProfileId = actionProfile.getPreamble().getId();
+        ActionProfileGroup.Builder actionProfileGroupBuilder =
+                ActionProfileGroup.newBuilder()
+                        .setGroupId(piActionGroup.id().id())
+                        .setActionProfileId(actionProfileId);
+
+        switch (piActionGroup.type()) {
+            case SELECT:
+                actionProfileGroupBuilder.setType(ActionProfileGroup.Type.SELECT);
+                break;
+            default:
+                throw new EncodeException(format("Unsupported pi action group type %s",
+                                                 piActionGroup.type()));
+        }
+
+        piActionGroup.members().forEach(m -> {
+            // TODO: currently we don't set "watch" field of member
+            Member member = Member.newBuilder()
+                    .setMemberId(m.id().id())
+                    .setWeight(m.weight())
+                    .build();
+            actionProfileGroupBuilder.addMembers(member);
+        });
+
+        return actionProfileGroupBuilder.build();
+    }
+
+    /**
+     * Decode an action profile group with members information to a PI action group.
+     *
+     * @param actionProfileGroup the action profile group
+     * @param members members of the action profile group
+     * @param pipeconf the pipeconf
+     * @return decoded PI action group
+     * @throws P4InfoBrowser.NotFoundException if can't find action profile from P4Info browser
+     * @throws EncodeException if can't find P4Info from pipeconf
+     */
+    static PiActionGroup decode(ActionProfileGroup actionProfileGroup,
+                                        Collection<ActionProfileMember> members,
+                                        PiPipeconf pipeconf)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+        if (browser == null) {
+            throw new EncodeException(format("Can't get P4 info browser from pipeconf %s", pipeconf));
+        }
+        PiActionGroup.Builder piActionGroupBuilder = PiActionGroup.builder();
+
+        P4InfoOuterClass.ActionProfile actionProfile = browser.actionProfiles()
+                .getById(actionProfileGroup.getActionProfileId());
+        PiActionProfileId piActionProfileId = PiActionProfileId.of(actionProfile.getPreamble().getName());
+        piActionGroupBuilder.withActionProfileId(piActionProfileId)
+                .withId(PiActionGroupId.of(actionProfileGroup.getGroupId()));
+
+        switch (actionProfileGroup.getType()) {
+            case SELECT:
+                piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
+                break;
+            default:
+                throw new EncodeException(format("Unsupported action profile type %s",
+                                                 actionProfileGroup.getType()));
+        }
+
+        Map<Integer, Integer> memberWeights = Maps.newHashMap();
+        actionProfileGroup.getMembersList().forEach(member -> {
+            int weight = member.getWeight();
+            if (weight < 1) {
+                // FIXME: currently PI has a bug which will always return weight 0
+                // ONOS won't accept group member with weight 0
+                weight = 1;
+            }
+            memberWeights.put(member.getMemberId(), weight);
+        });
+
+        for (ActionProfileMember member : members) {
+            int weight = memberWeights.get(member.getMemberId());
+            piActionGroupBuilder
+                    .addMember(ActionProfileMemberEncoder.decode(member, weight, pipeconf));
+        }
+
+        return piActionGroupBuilder.build();
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java
new file mode 100644
index 0000000..2f08a59
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import p4.P4RuntimeOuterClass;
+import p4.P4RuntimeOuterClass.ActionProfileMember;
+import p4.config.P4InfoOuterClass;
+
+import static java.lang.String.format;
+import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decodeActionMsg;
+import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encodePiAction;
+
+/**
+ * Encoder/Decoder of action profile member.
+ */
+public final class ActionProfileMemberEncoder {
+    private ActionProfileMemberEncoder() {
+        // Hide default constructor
+    }
+
+    /**
+     * Encode a PiActionGroupMember to a ActionProfileMember.
+     *
+     * @param group the PI action group of members
+     * @param member the member to encode
+     * @param pipeconf the pipeconf
+     * @return encoded member
+     */
+    /**
+     * Encode a PiActionGroupMember to a ActionProfileMember.
+     *
+     * @param group the PI action group of members
+     * @param member the member to encode
+     * @param pipeconf the pipeconf, as encode spec
+     * @return encoded member
+     * @throws P4InfoBrowser.NotFoundException can't find action profile from P4Info browser
+     * @throws EncodeException can't find P4Info from pipeconf
+     */
+    static ActionProfileMember encode(PiActionGroup group,
+                                      PiActionGroupMember member,
+                                      PiPipeconf pipeconf)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        if (browser == null) {
+            throw new EncodeException(format("Can't get P4 info browser from pipeconf %s", pipeconf));
+        }
+
+        ActionProfileMember.Builder actionProfileMemberBuilder =
+                ActionProfileMember.newBuilder();
+
+        // member id
+        actionProfileMemberBuilder.setMemberId(member.id().id());
+
+        // action profile id
+        P4InfoOuterClass.ActionProfile actionProfile =
+                browser.actionProfiles().getByName(group.actionProfileId().id());
+
+        int actionProfileId = actionProfile.getPreamble().getId();
+        actionProfileMemberBuilder.setActionProfileId(actionProfileId);
+
+        // Action
+        P4RuntimeOuterClass.Action action = encodePiAction(member.action(), browser);
+        actionProfileMemberBuilder.setAction(action);
+
+        return actionProfileMemberBuilder.build();
+    }
+
+    /**
+     * Decode an action profile member to PI action group member.
+     *
+     * @param member the action profile member
+     * @param weight the weight of the member
+     * @param pipeconf the pipeconf, as decode spec
+     * @return decoded PI action group member
+     * @throws P4InfoBrowser.NotFoundException can't find definition of action from P4 info
+     * @throws EncodeException can't get P4 info browser from pipeconf
+     */
+    static PiActionGroupMember decode(ActionProfileMember member,
+                                      int weight,
+                                      PiPipeconf pipeconf)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        if (browser == null) {
+            throw new EncodeException(format("Can't get P4 info browser from pipeconf %s", pipeconf));
+        }
+        return PiActionGroupMember.builder().withId(PiActionGroupMemberId.of(member.getMemberId()))
+                .withWeight(weight)
+                .withAction(decodeActionMsg(member.getAction(), browser))
+                .build();
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 4dff7ea..d9da345 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -17,7 +17,10 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.protobuf.ByteString;
 import io.grpc.Context;
@@ -26,13 +29,17 @@
 import io.grpc.StatusRuntimeException;
 import io.grpc.stub.StreamObserver;
 import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.util.Tools;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiCounterId;
 import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -41,6 +48,8 @@
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.slf4j.Logger;
 import p4.P4RuntimeGrpc;
+import p4.P4RuntimeOuterClass.ActionProfileGroup;
+import p4.P4RuntimeOuterClass.ActionProfileMember;
 import p4.P4RuntimeOuterClass.Entity;
 import p4.P4RuntimeOuterClass.ForwardingPipelineConfig;
 import p4.P4RuntimeOuterClass.MasterArbitrationUpdate;
@@ -53,6 +62,7 @@
 import p4.P4RuntimeOuterClass.TableEntry;
 import p4.P4RuntimeOuterClass.Update;
 import p4.P4RuntimeOuterClass.WriteRequest;
+import p4.config.P4InfoOuterClass;
 import p4.config.P4InfoOuterClass.P4Info;
 import p4.tmp.P4Config;
 
@@ -70,6 +80,8 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
@@ -79,6 +91,8 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
 import static org.slf4j.LoggerFactory.getLogger;
+import static p4.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_GROUP;
+import static p4.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_MEMBER;
 import static p4.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
 import static p4.P4RuntimeOuterClass.PacketOut;
 import static p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
@@ -107,7 +121,14 @@
     private final Lock writeLock = new ReentrantLock();
     private final StreamObserver<StreamMessageRequest> streamRequestObserver;
 
-
+    /**
+     * Default constructor.
+     *
+     * @param deviceId the ONOS device id
+     * @param p4DeviceId the P4 device id
+     * @param channel gRPC channel
+     * @param controller runtime client controller
+     */
     P4RuntimeClientImpl(DeviceId deviceId, long p4DeviceId, ManagedChannel channel,
                         P4RuntimeControllerImpl controller) {
         this.deviceId = deviceId;
@@ -222,6 +243,30 @@
                                "readAllCounterCells-" + cellIds.hashCode());
     }
 
+    @Override
+    public CompletableFuture<Boolean> writeActionGroupMembers(PiActionGroup group,
+                                                              Collection<PiActionGroupMember> members,
+                                                              WriteOperationType opType,
+                                                              PiPipeconf pipeconf) {
+        return supplyInContext(() -> doWriteActionGroupMembers(group, members, opType, pipeconf),
+                               "writeActionGroupMembers-" + opType.name());
+    }
+
+    @Override
+    public CompletableFuture<Boolean> writeActionGroup(PiActionGroup group,
+                                                       WriteOperationType opType,
+                                                       PiPipeconf pipeconf) {
+        return supplyInContext(() -> doWriteActionGroup(group, opType, pipeconf),
+                               "writeActionGroup-" + opType.name());
+    }
+
+    @Override
+    public CompletableFuture<Collection<PiActionGroup>> dumpGroups(PiActionProfileId actionProfileId,
+                                                                   PiPipeconf pipeconf) {
+        return supplyInContext(() -> doDumpGroups(actionProfileId, pipeconf),
+                               "dumpGroups-" + actionProfileId.id());
+    }
+
     /* Blocking method implementations below */
 
     private boolean doInitStreamChannel() {
@@ -481,6 +526,191 @@
         return CounterEntryCodec.decodeCounterEntities(entities, counterIdMap, pipeconf);
     }
 
+    private boolean doWriteActionGroupMembers(PiActionGroup group, Collection<PiActionGroupMember> members,
+                                              WriteOperationType opType, PiPipeconf pipeconf) {
+        WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
+
+        Collection<ActionProfileMember> actionProfileMembers = Lists.newArrayList();
+        try {
+            for (PiActionGroupMember member : members) {
+                actionProfileMembers.add(
+                        ActionProfileMemberEncoder.encode(group, member, pipeconf)
+                );
+            }
+        } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
+            log.warn("Can't encode group member {} due to {}", members, e.getMessage());
+            return false;
+        }
+
+        Collection<Update> updateMsgs = actionProfileMembers.stream()
+                .map(actionProfileMember ->
+                             Update.newBuilder()
+                                     .setEntity(Entity.newBuilder()
+                                                        .setActionProfileMember(actionProfileMember)
+                                                        .build())
+                                     .setType(UPDATE_TYPES.get(opType))
+                                     .build())
+                .collect(Collectors.toList());
+
+        if (updateMsgs.size() == 0) {
+            // Nothing to update
+            return true;
+        }
+
+        writeRequestBuilder
+                .setDeviceId(p4DeviceId)
+                .addAllUpdates(updateMsgs);
+        try {
+            blockingStub.write(writeRequestBuilder.build());
+            return true;
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to write table entries ({}): {}", opType, e.getMessage());
+            return false;
+        }
+    }
+
+    private Collection<PiActionGroup> doDumpGroups(PiActionProfileId piActionProfileId, PiPipeconf pipeconf) {
+        log.debug("Dumping groups from action profile {} from {} (pipeconf {})...",
+                  piActionProfileId.id(), deviceId, pipeconf.id());
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+        if (browser == null) {
+            log.warn("Unable to get a P4Info browser for pipeconf {}, skipping dump action profile {}",
+                     pipeconf, piActionProfileId);
+            return Collections.emptySet();
+        }
+
+        int actionProfileId;
+        try {
+            P4InfoOuterClass.ActionProfile actionProfile =
+                    browser.actionProfiles().getByName(piActionProfileId.id());
+            actionProfileId = actionProfile.getPreamble().getId();
+        } catch (P4InfoBrowser.NotFoundException e) {
+            log.warn("Can't find action profile {} from p4info", piActionProfileId);
+            return Collections.emptySet();
+        }
+
+        ActionProfileGroup actionProfileGroup =
+                ActionProfileGroup.newBuilder()
+                        .setActionProfileId(actionProfileId)
+                        .build();
+
+        ReadRequest requestMsg = ReadRequest.newBuilder()
+                .setDeviceId(p4DeviceId)
+                .addEntities(Entity.newBuilder()
+                                     .setActionProfileGroup(actionProfileGroup)
+                                     .build())
+                .build();
+
+        Iterator<ReadResponse> responses;
+        try {
+            responses = blockingStub.read(requestMsg);
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to read action profile {} due to {}", piActionProfileId, e.getMessage());
+            return Collections.emptySet();
+        }
+
+        List<ActionProfileGroup> actionProfileGroups =
+                Tools.stream(() -> responses)
+                        .map(ReadResponse::getEntitiesList)
+                        .flatMap(List::stream)
+                        .filter(entity -> entity.getEntityCase() == ACTION_PROFILE_GROUP)
+                        .map(Entity::getActionProfileGroup)
+                        .collect(Collectors.toList());
+
+        log.debug("Retrieved {} groups from action profile {} on {}...",
+                  actionProfileGroups.size(), piActionProfileId.id(), deviceId);
+
+        // group id -> members
+        Multimap<Integer, ActionProfileMember> actionProfileMemberMap = HashMultimap.create();
+        AtomicLong memberCount = new AtomicLong(0);
+        AtomicBoolean success = new AtomicBoolean(true);
+        actionProfileGroups.forEach(actProfGrp -> {
+            actProfGrp.getMembersList().forEach(member -> {
+                ActionProfileMember actProfMember =
+                        ActionProfileMember.newBuilder()
+                                .setActionProfileId(actProfGrp.getActionProfileId())
+                                .setMemberId(member.getMemberId())
+                                .build();
+                Entity entity = Entity.newBuilder()
+                        .setActionProfileMember(actProfMember)
+                        .build();
+
+                ReadRequest reqMsg = ReadRequest.newBuilder().setDeviceId(p4DeviceId)
+                        .addEntities(entity)
+                        .build();
+
+                Iterator<ReadResponse> resps;
+                try {
+                    resps = blockingStub.read(reqMsg);
+                } catch (StatusRuntimeException e) {
+                    log.warn("Unable to read member {} from action profile {} due to {}",
+                             member, piActionProfileId, e.getMessage());
+                    success.set(false);
+                    return;
+                }
+                Tools.stream(() -> resps)
+                        .map(ReadResponse::getEntitiesList)
+                        .flatMap(List::stream)
+                        .filter(e -> e.getEntityCase() == ACTION_PROFILE_MEMBER)
+                        .map(Entity::getActionProfileMember)
+                        .forEach(m -> {
+                            actionProfileMemberMap.put(actProfGrp.getGroupId(), m);
+                            memberCount.incrementAndGet();
+                        });
+            });
+        });
+
+        if (!success.get()) {
+            // Can't read members
+            return Collections.emptySet();
+        }
+        log.info("Retrieved {} group members from action profile {} on {}...",
+                 memberCount.get(), piActionProfileId.id(), deviceId);
+
+        Collection<PiActionGroup> piActionGroups = Sets.newHashSet();
+
+        for (ActionProfileGroup apg : actionProfileGroups) {
+            try {
+                Collection<ActionProfileMember> members = actionProfileMemberMap.get(apg.getGroupId());
+                PiActionGroup decodedGroup =
+                        ActionProfileGroupEncoder.decode(apg, members, pipeconf);
+                piActionGroups.add(decodedGroup);
+            } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
+                log.warn("Can't decode group {} due to {}", apg, e.getMessage());
+                return Collections.emptySet();
+            }
+        }
+
+        return piActionGroups;
+    }
+
+    private boolean doWriteActionGroup(PiActionGroup group, WriteOperationType opType, PiPipeconf pipeconf) {
+        WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
+        ActionProfileGroup actionProfileGroup;
+        try {
+            actionProfileGroup = ActionProfileGroupEncoder.encode(group, pipeconf);
+        } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
+            log.warn("Can't encode group {} due to {}", e.getMessage());
+            return false;
+        }
+        Update updateMessage = Update.newBuilder()
+                .setEntity(Entity.newBuilder()
+                                   .setActionProfileGroup(actionProfileGroup)
+                                   .build())
+                .setType(UPDATE_TYPES.get(opType))
+                .build();
+        writeRequestBuilder
+                .setDeviceId(p4DeviceId)
+                .addUpdates(updateMessage);
+        try {
+            blockingStub.write(writeRequestBuilder.build());
+            return true;
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to write table entries ({}): {}", opType, e.getMessage());
+            return false;
+        }
+    }
+
     /**
      * Returns the internal P4 device ID associated with this client.
      *
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
index e26341b..383b857 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java
@@ -186,7 +186,7 @@
         }
     }
 
-    void postEvent(P4RuntimeEvent event) {
+    public void postEvent(P4RuntimeEvent event) {
         post(event);
     }
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java
new file mode 100644
index 0000000..411e1fb
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.protobuf.ByteString;
+
+import static java.lang.String.format;
+
+/**
+ * Utilities for P4 runtime control.
+ */
+public final class P4RuntimeUtils {
+
+    private P4RuntimeUtils() {
+        // Hide default construction
+    }
+
+    static void assertSize(String entityDescr, ByteString value, int bitWidth)
+            throws EncodeException {
+
+        int byteWidth = (int) Math.ceil((float) bitWidth / 8);
+        if (value.size() != byteWidth) {
+            throw new EncodeException(format("Wrong size for %s, expected %d bytes, but found %d",
+                                             entityDescr, byteWidth, value.size()));
+        }
+    }
+
+    static void assertPrefixLen(String entityDescr, int prefixLength, int bitWidth)
+            throws EncodeException {
+
+        if (prefixLength > bitWidth) {
+            throw new EncodeException(format(
+                    "wrong prefix length for %s, field size is %d bits, but found one is %d",
+                    entityDescr, bitWidth, prefixLength));
+        }
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
index 9ffc18f..8278b3e 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
@@ -17,10 +17,13 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.protobuf.ByteString;
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiActionParamId;
@@ -36,25 +39,26 @@
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import org.onosproject.net.pi.runtime.PiValidFieldMatch;
 import org.slf4j.Logger;
-import p4.P4RuntimeOuterClass.Action;
 import p4.P4RuntimeOuterClass.FieldMatch;
 import p4.P4RuntimeOuterClass.TableAction;
 import p4.P4RuntimeOuterClass.TableEntry;
+import p4.P4RuntimeOuterClass.Action;
 import p4.config.P4InfoOuterClass;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 import static java.lang.String.format;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.assertPrefixLen;
+import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.assertSize;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Encoder of table entries, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
+ * Encoder/Decoder of table entries, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
  */
 final class TableEntryEncoder {
-
-
     private static final Logger log = getLogger(TableEntryEncoder.class);
 
     private static final String HEADER_PREFIX = "hdr.";
@@ -422,7 +426,7 @@
         }
     }
 
-    private static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser)
+    static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser)
             throws P4InfoBrowser.NotFoundException, EncodeException {
 
         TableAction.Builder tableActionMsgBuilder = TableAction.newBuilder();
@@ -430,24 +434,17 @@
         switch (piTableAction.type()) {
             case ACTION:
                 PiAction piAction = (PiAction) piTableAction;
-                int actionId = browser.actions().getByName(piAction.id().name()).getPreamble().getId();
-
-                Action.Builder actionMsgBuilder = Action.newBuilder().setActionId(actionId);
-
-                for (PiActionParam p : piAction.parameters()) {
-                    P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().name());
-                    ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer());
-                    assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()),
-                               paramValue, paramInfo.getBitwidth());
-                    actionMsgBuilder.addParams(Action.Param.newBuilder()
-                                                       .setParamId(paramInfo.getId())
-                                                       .setValue(paramValue)
-                                                       .build());
-                }
-
-                tableActionMsgBuilder.setAction(actionMsgBuilder.build());
+                Action theAction = encodePiAction(piAction, browser);
+                tableActionMsgBuilder.setAction(theAction);
                 break;
-
+            case ACTION_GROUP_ID:
+                PiActionGroupId actionGroupId = (PiActionGroupId) piTableAction;
+                tableActionMsgBuilder.setActionProfileGroupId(actionGroupId.id());
+                break;
+            case GROUP_MEMBER_ID:
+                PiActionGroupMemberId actionGroupMemberId = (PiActionGroupMemberId) piTableAction;
+                tableActionMsgBuilder.setActionProfileMemberId(actionGroupMemberId.id());
+                break;
             default:
                 throw new EncodeException(
                         format("Building of table action type %s not implemented", piTableAction.type()));
@@ -456,50 +453,56 @@
         return tableActionMsgBuilder.build();
     }
 
-    private static PiTableAction decodeTableActionMsg(TableAction tableActionMsg, P4InfoBrowser browser)
+    static PiTableAction decodeTableActionMsg(TableAction tableActionMsg, P4InfoBrowser browser)
             throws P4InfoBrowser.NotFoundException, EncodeException {
-
         TableAction.TypeCase typeCase = tableActionMsg.getTypeCase();
-
         switch (typeCase) {
             case ACTION:
-                PiAction.Builder piActionBuilder = PiAction.builder();
                 Action actionMsg = tableActionMsg.getAction();
-                // Action ID.
-                int actionId = actionMsg.getActionId();
-                String actionName = browser.actions().getById(actionId).getPreamble().getName();
-                piActionBuilder.withId(PiActionId.of(actionName));
-                // Params.
-                for (Action.Param paramMsg : actionMsg.getParamsList()) {
-                    String paramName = browser.actionParams(actionId).getById(paramMsg.getParamId()).getName();
-                    ImmutableByteSequence paramValue = copyFrom(paramMsg.getValue().asReadOnlyByteBuffer());
-                    piActionBuilder.withParameter(new PiActionParam(PiActionParamId.of(paramName), paramValue));
-                }
-                return piActionBuilder.build();
-
+                return decodeActionMsg(actionMsg, browser);
             default:
                 throw new EncodeException(
                         format("Decoding of table action type %s not implemented", typeCase.name()));
         }
     }
 
-    private static void assertSize(String entityDescr, ByteString value, int bitWidth)
-            throws EncodeException {
+    static Action encodePiAction(PiAction piAction, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException, EncodeException {
 
-        int byteWidth = (int) Math.ceil((float) bitWidth / 8);
-        if (value.size() != byteWidth) {
-            throw new EncodeException(format("Wrong size for %s, expected %d bytes, but found %d",
-                                             entityDescr, byteWidth, value.size()));
+        int actionId = browser.actions().getByName(piAction.id().name()).getPreamble().getId();
+
+        Action.Builder actionMsgBuilder =
+                Action.newBuilder().setActionId(actionId);
+
+        for (PiActionParam p : piAction.parameters()) {
+            P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().name());
+            ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer());
+            assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()),
+                       paramValue, paramInfo.getBitwidth());
+            actionMsgBuilder.addParams(Action.Param.newBuilder()
+                                               .setParamId(paramInfo.getId())
+                                               .setValue(paramValue)
+                                               .build());
         }
+
+        return actionMsgBuilder.build();
     }
 
-    private static void assertPrefixLen(String entityDescr, int prefixLength, int bitWidth)
-            throws EncodeException {
+    static PiAction decodeActionMsg(Action action, P4InfoBrowser browser)
+            throws P4InfoBrowser.NotFoundException {
+        P4InfoBrowser.EntityBrowser<P4InfoOuterClass.Action.Param> paramInfo =
+                browser.actionParams(action.getActionId());
+        String actionName = browser.actions()
+                .getById(action.getActionId())
+                .getPreamble().getName();
+        PiActionId id = PiActionId.of(actionName);
+        List<PiActionParam> params = Lists.newArrayList();
 
-        if (prefixLength > bitWidth) {
-            throw new EncodeException(format(
-                    "wrong prefix length for %s, field size is %d bits, but found one is %d",
-                    entityDescr, bitWidth, prefixLength));
+        for (Action.Param p : action.getParamsList()) {
+            String paramName = paramInfo.getById(p.getParamId()).getName();
+            ImmutableByteSequence value = ImmutableByteSequence.copyFrom(p.getValue().toByteArray());
+            params.add(new PiActionParam(PiActionParamId.of(paramName), value));
         }
+        return PiAction.builder().withId(id).withParameters(params).build();
     }
 }
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java
new file mode 100644
index 0000000..4773458
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.Lists;
+import io.grpc.stub.StreamObserver;
+import p4.P4RuntimeGrpc;
+import p4.P4RuntimeOuterClass;
+import p4.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
+import p4.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
+import p4.P4RuntimeOuterClass.ReadRequest;
+import p4.P4RuntimeOuterClass.ReadResponse;
+import p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
+import p4.P4RuntimeOuterClass.SetForwardingPipelineConfigResponse;
+import p4.P4RuntimeOuterClass.StreamMessageResponse;
+import p4.P4RuntimeOuterClass.WriteRequest;
+import p4.P4RuntimeOuterClass.WriteResponse;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MockP4RuntimeServer extends P4RuntimeGrpc.P4RuntimeImplBase {
+    private CompletableFuture<Void> completeLock;
+    private AtomicLong counter;
+
+    // Requests
+    private List<WriteRequest> writeReqs;
+    private List<ReadRequest> readReqs;
+    private List<ReadResponse> readResps;
+
+    /**
+     * Expect N times request sent by client.
+     *
+     * @param times the number of request will sent by client.
+     * @return a completable future object, wll complete after client send N times requests.
+     */
+    public CompletableFuture<Void> expectRequests(long times) {
+        counter = new AtomicLong(times);
+        completeLock = new CompletableFuture<>();
+        readReqs = Lists.newArrayList();
+        writeReqs = Lists.newArrayList();
+        return completeLock;
+    }
+
+    private void complete() {
+        if (counter.decrementAndGet() == 0) {
+            completeLock.complete(null);
+        }
+    }
+
+    public void willReturnReadResult(Collection<ReadResponse> readResps) {
+        this.readResps = Lists.newArrayList(readResps);
+    }
+
+    public List<WriteRequest> getWriteReqs() {
+        return writeReqs;
+    }
+
+    public List<ReadRequest> getReadReqs() {
+        return readReqs;
+    }
+
+    @Override
+    public void write(WriteRequest request, StreamObserver<WriteResponse> responseObserver) {
+        writeReqs.add(request);
+        complete();
+    }
+
+    @Override
+    public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
+        readReqs.add(request);
+        if (readResps != null && !readResps.isEmpty()) {
+            ReadResponse readResp = readResps.remove(0); // get first response
+            responseObserver.onNext(readResp);
+            responseObserver.onCompleted();
+        }
+        complete();
+    }
+
+    @Override
+    public void setForwardingPipelineConfig(SetForwardingPipelineConfigRequest request,
+                                            StreamObserver<SetForwardingPipelineConfigResponse> responseObserver) {
+        throw new UnsupportedOperationException("Not implement yet");
+    }
+
+    @Override
+    public void getForwardingPipelineConfig(GetForwardingPipelineConfigRequest request,
+                                            StreamObserver<GetForwardingPipelineConfigResponse> responseObserver) {
+        throw new UnsupportedOperationException("Not implement yet");
+    }
+
+    @Override
+    public StreamObserver<P4RuntimeOuterClass.StreamMessageRequest>
+    streamChannel(StreamObserver<StreamMessageResponse> responseObserver) {
+        // TODO: not implement yet
+        return null;
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
new file mode 100644
index 0000000..8932a16
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.AbstractServerImplBuilder;
+import org.easymock.EasyMock;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import p4.P4RuntimeOuterClass.ActionProfileGroup;
+import p4.P4RuntimeOuterClass.ActionProfileMember;
+import p4.P4RuntimeOuterClass.Entity;
+import p4.P4RuntimeOuterClass.Update;
+import p4.P4RuntimeOuterClass.WriteRequest;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+import static org.onosproject.net.pi.runtime.PiActionGroup.Type.SELECT;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
+import static p4.P4RuntimeOuterClass.*;
+
+/**
+ * Tests for P4 Runtime Action Profile Group support.
+ */
+public class P4RuntimeGroupTest {
+    private static final String PIPECONF_ID = "p4runtime-mock-pipeconf";
+    private static final String P4INFO_PATH = "/default.p4info";
+    private static final PiPipeconf PIPECONF = buildPipeconf();
+    private static final int P4_INFO_ACT_PROF_ID = 285227860;
+    private static final PiActionProfileId ACT_PROF_ID = PiActionProfileId.of("ecmp_selector");
+    private static final PiActionGroupId GROUP_ID = PiActionGroupId.of(1);
+    private static final int DEFAULT_MEMBER_WEIGHT = 1;
+    private static final PiActionId EGRESS_PORT_ACTION_ID = PiActionId.of("set_egress_port");
+    private static final PiActionParamId PORT_PARAM_ID = PiActionParamId.of("port");
+    private static final int BASE_MEM_ID = 65535;
+    private static final List<Integer> MEMBER_IDS = ImmutableList.of(65536, 65537, 65538);
+    private static final Collection<PiActionGroupMember> GROUP_MEMBERS =
+            ImmutableSet.of(
+                    outputMember((short) 1),
+                    outputMember((short) 2),
+                    outputMember((short) 3)
+            );
+    private static final PiActionGroup GROUP = PiActionGroup.builder()
+            .withId(GROUP_ID)
+            .addMembers(GROUP_MEMBERS)
+            .withActionProfileId(ACT_PROF_ID)
+            .withType(SELECT)
+            .build();
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:p4runtime:1");
+    private static final int P4_DEVICE_ID = 1;
+    private static final int SET_EGRESS_PORT_ID = 16794308;
+    private static final String GRPC_SERVER_NAME = "P4RuntimeGroupTest";
+    private static final long DEFAULT_TIMEOUT_TIME = 5;
+
+    private P4RuntimeClientImpl client;
+    private P4RuntimeControllerImpl controller;
+    private static MockP4RuntimeServer p4RuntimeServerImpl = new MockP4RuntimeServer();
+    private static Server grpcServer;
+    private static ManagedChannel grpcChannel;
+
+    private static PiActionGroupMember outputMember(short portNum) {
+        PiActionParam param = new PiActionParam(PORT_PARAM_ID,
+                                                ImmutableByteSequence.copyFrom(portNum));
+        PiAction piAction = PiAction.builder()
+                .withId(EGRESS_PORT_ACTION_ID)
+                .withParameter(param).build();
+
+        return PiActionGroupMember.builder()
+                .withAction(piAction)
+                .withId(PiActionGroupMemberId.of(BASE_MEM_ID + portNum))
+                .withWeight(DEFAULT_MEMBER_WEIGHT)
+                .build();
+    }
+
+    private static PiPipeconf buildPipeconf() {
+        final URL p4InfoUrl = P4RuntimeGroupTest.class.getResource(P4INFO_PATH);
+        return DefaultPiPipeconf.builder()
+                .withId(new PiPipeconfId(PIPECONF_ID))
+                .withPipelineModel(EasyMock.niceMock(PiPipelineModel.class))
+                .addExtension(P4_INFO_TEXT, p4InfoUrl)
+                .build();
+    }
+
+    @BeforeClass
+    public static void globalSetup() throws IOException {
+        AbstractServerImplBuilder builder = InProcessServerBuilder
+                .forName(GRPC_SERVER_NAME).directExecutor();
+        builder.addService(p4RuntimeServerImpl);
+        grpcServer = builder.build().start();
+        grpcChannel = InProcessChannelBuilder.forName(GRPC_SERVER_NAME)
+                .directExecutor()
+                .usePlaintext(true)
+                .build();
+    }
+
+    @AfterClass
+    public static void globalTeerDown() {
+        grpcServer.shutdown();
+        grpcChannel.shutdown();
+    }
+
+
+    @Before
+    public void setup() {
+        controller = niceMock(P4RuntimeControllerImpl.class);
+        client = new P4RuntimeClientImpl(DEVICE_ID, P4_DEVICE_ID,
+                                         grpcChannel,
+                                         controller);
+    }
+
+    @Test
+    public void testInsertPiActionGroup() throws Exception {
+        CompletableFuture<Void> complete = p4RuntimeServerImpl.expectRequests(1);
+        client.writeActionGroup(GROUP, INSERT, PIPECONF);
+        complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
+        WriteRequest result = p4RuntimeServerImpl.getWriteReqs().get(0);
+        assertEquals(1, result.getDeviceId());
+        assertEquals(1, result.getUpdatesCount());
+
+        Update update = result.getUpdatesList().get(0);
+        assertEquals(Update.Type.INSERT, update.getType());
+
+        Entity entity = update.getEntity();
+        ActionProfileGroup actionProfileGroup = entity.getActionProfileGroup();
+        assertNotNull(actionProfileGroup);
+
+        assertEquals(P4_INFO_ACT_PROF_ID, actionProfileGroup.getActionProfileId());
+        assertEquals(3, actionProfileGroup.getMembersCount());
+        List<ActionProfileGroup.Member> members = actionProfileGroup.getMembersList();
+
+        for (ActionProfileGroup.Member member : members) {
+            // XXX: We can't guarantee the order of member, just make sure we
+            // have these member ids
+            assertTrue(MEMBER_IDS.contains(member.getMemberId()));
+            assertEquals(DEFAULT_MEMBER_WEIGHT, member.getWeight());
+        }
+    }
+
+    @Test
+    public void testInsertPiActionMembers() throws Exception {
+        CompletableFuture<Void> complete = p4RuntimeServerImpl.expectRequests(1);
+        client.writeActionGroupMembers(GROUP, GROUP_MEMBERS, INSERT, PIPECONF);
+        complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
+        WriteRequest result = p4RuntimeServerImpl.getWriteReqs().get(0);
+        assertEquals(1, result.getDeviceId());
+        assertEquals(3, result.getUpdatesCount());
+
+        List<Update> updates = result.getUpdatesList();
+        for (Update update : updates) {
+            assertEquals(Update.Type.INSERT, update.getType());
+            Entity entity = update.getEntity();
+            ActionProfileMember member = entity.getActionProfileMember();
+            assertNotNull(member);
+            assertEquals(P4_INFO_ACT_PROF_ID, member.getActionProfileId());
+            assertTrue(MEMBER_IDS.contains(member.getMemberId()));
+            Action action = member.getAction();
+            assertEquals(SET_EGRESS_PORT_ID, action.getActionId());
+            assertEquals(1, action.getParamsCount());
+            Action.Param param = action.getParamsList().get(0);
+            assertEquals(1, param.getParamId());
+            byte outPort = (byte) (member.getMemberId() - BASE_MEM_ID);
+            ByteString bs = ByteString.copyFrom(new byte[]{0, outPort});
+            assertEquals(bs, param.getValue());
+        }
+    }
+
+    @Test
+    public void testReadGroups() throws Exception {
+        ActionProfileGroup.Builder group = ActionProfileGroup.newBuilder()
+                .setGroupId(GROUP_ID.id())
+                .setType(ActionProfileGroup.Type.SELECT)
+                .setActionProfileId(P4_INFO_ACT_PROF_ID);
+
+        List<ActionProfileMember> members = Lists.newArrayList();
+
+        MEMBER_IDS.forEach(id -> {
+            ActionProfileGroup.Member member = ActionProfileGroup.Member.newBuilder()
+                    .setMemberId(id)
+                    .setWeight(DEFAULT_MEMBER_WEIGHT)
+                    .build();
+            group.addMembers(member);
+
+            byte outPort = (byte) (id - BASE_MEM_ID);
+            ByteString bs = ByteString.copyFrom(new byte[]{0, outPort});
+            Action.Param param = Action.Param.newBuilder()
+                    .setParamId(1)
+                    .setValue(bs)
+                    .build();
+
+            Action action = Action.newBuilder()
+                    .setActionId(SET_EGRESS_PORT_ID)
+                    .addParams(param)
+                    .build();
+
+
+            ActionProfileMember actProfMember =
+                    ActionProfileMember.newBuilder()
+                            .setMemberId(id)
+                            .setAction(action)
+                            .build();
+            members.add(actProfMember);
+        });
+
+        List<ReadResponse> responses = Lists.newArrayList();
+        responses.add(ReadResponse.newBuilder()
+                              .addEntities(Entity.newBuilder().setActionProfileGroup(group))
+                              .build()
+        );
+
+        members.forEach(m -> {
+            responses.add(ReadResponse.newBuilder()
+                                  .addEntities(Entity.newBuilder().setActionProfileMember(m))
+                                  .build());
+        });
+
+        p4RuntimeServerImpl.willReturnReadResult(responses);
+        CompletableFuture<Void> complete = p4RuntimeServerImpl.expectRequests(4);
+        CompletableFuture<Collection<PiActionGroup>> groupsComplete = client.dumpGroups(ACT_PROF_ID, PIPECONF);
+        complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
+
+        Collection<PiActionGroup> groups = groupsComplete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
+        assertEquals(1, groups.size());
+        PiActionGroup piActionGroup = groups.iterator().next();
+        assertEquals(ACT_PROF_ID, piActionGroup.actionProfileId());
+        assertEquals(GROUP_ID, piActionGroup.id());
+        assertEquals(SELECT, piActionGroup.type());
+        assertEquals(3, piActionGroup.members().size());
+        assertTrue(GROUP_MEMBERS.containsAll(piActionGroup.members()));
+        assertTrue(piActionGroup.members().containsAll(GROUP_MEMBERS));
+    }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
index 792e838..b3e9a1e 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
@@ -18,12 +18,13 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.testing.EqualsTester;
+import org.easymock.EasyMock;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
@@ -33,7 +34,6 @@
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
-import org.slf4j.Logger;
 import p4.P4RuntimeOuterClass.Action;
 import p4.P4RuntimeOuterClass.TableEntry;
 
@@ -47,19 +47,14 @@
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onlab.util.ImmutableByteSequence.fit;
 import static org.onlab.util.ImmutableByteSequence.ofOnes;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
 import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decode;
 import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encode;
-import static org.slf4j.LoggerFactory.getLogger;
 
-//import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
-//import org.onosproject.drivers.bmv2.Bmv2DefaultInterpreter;
-
+/**
+ * Test for P4 runtime table entry encoder.
+ */
 public class TableEntryEncoderTest {
-
-    private final Logger log = getLogger(getClass());
-
     private static final String TABLE_0 = "table0";
     private static final String SET_EGRESS_PORT = "set_egress_port";
     private static final String PORT = "port";
@@ -72,14 +67,11 @@
 
     private final Random rand = new Random();
     private final URL p4InfoUrl = this.getClass().getResource("/default.p4info");
-    private final URL jsonUrl = this.getClass().getResource("/default.json");
 
     private final PiPipeconf defaultPipeconf = DefaultPiPipeconf.builder()
             .withId(new PiPipeconfId("mock"))
-            .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
-//            .addBehaviour(PiPipelineInterpreter.class, Bmv2DefaultInterpreter.class)
+            .withPipelineModel(EasyMock.niceMock(PiPipelineModel.class))
             .addExtension(P4_INFO_TEXT, p4InfoUrl)
-            .addExtension(BMV2_JSON, jsonUrl)
             .build();
 
     private final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(defaultPipeconf);
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.json b/protocols/p4runtime/ctl/src/test/resources/default.json
deleted file mode 100644
index 3a46dcc..0000000
--- a/protocols/p4runtime/ctl/src/test/resources/default.json
+++ /dev/null
@@ -1,2697 +0,0 @@
-{
-  "program" : "default.p4",
-  "__meta__" : {
-    "version" : [2, 7],
-    "compiler" : "https://github.com/p4lang/p4c"
-  },
-  "header_types" : [
-    {
-      "name" : "scalars_0",
-      "id" : 0,
-      "fields" : [
-        ["tmp", 32, false],
-        ["tmp_0", 32, false]
-      ]
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 1,
-      "fields" : [
-        ["ingress_port", 9, false],
-        ["egress_spec", 9, false],
-        ["egress_port", 9, false],
-        ["clone_spec", 32, false],
-        ["instance_type", 32, false],
-        ["drop", 1, false],
-        ["recirculate_port", 16, false],
-        ["packet_length", 32, false],
-        ["enq_timestamp", 32, false],
-        ["enq_qdepth", 19, false],
-        ["deq_timedelta", 32, false],
-        ["deq_qdepth", 19, false],
-        ["ingress_global_timestamp", 48, false],
-        ["lf_field_list", 32, false],
-        ["mcast_grp", 16, false],
-        ["resubmit_flag", 1, false],
-        ["egress_rid", 16, false],
-        ["_padding", 5, false]
-      ]
-    },
-    {
-      "name" : "ethernet_t",
-      "id" : 2,
-      "fields" : [
-        ["dstAddr", 48, false],
-        ["srcAddr", 48, false],
-        ["etherType", 16, false]
-      ]
-    },
-    {
-      "name" : "ipv4_t",
-      "id" : 3,
-      "fields" : [
-        ["version", 4, false],
-        ["ihl", 4, false],
-        ["diffserv", 8, false],
-        ["totalLen", 16, false],
-        ["identification", 16, false],
-        ["flags", 3, false],
-        ["fragOffset", 13, false],
-        ["ttl", 8, false],
-        ["protocol", 8, false],
-        ["hdrChecksum", 16, false],
-        ["srcAddr", 32, false],
-        ["dstAddr", 32, false]
-      ]
-    },
-    {
-      "name" : "tcp_t",
-      "id" : 4,
-      "fields" : [
-        ["srcPort", 16, false],
-        ["dstPort", 16, false],
-        ["seqNo", 32, false],
-        ["ackNo", 32, false],
-        ["dataOffset", 4, false],
-        ["res", 3, false],
-        ["ecn", 3, false],
-        ["ctrl", 6, false],
-        ["window", 16, false],
-        ["checksum", 16, false],
-        ["urgentPtr", 16, false]
-      ]
-    },
-    {
-      "name" : "udp_t",
-      "id" : 5,
-      "fields" : [
-        ["srcPort", 16, false],
-        ["dstPort", 16, false],
-        ["length_", 16, false],
-        ["checksum", 16, false]
-      ]
-    },
-    {
-      "name" : "ecmp_metadata_t",
-      "id" : 6,
-      "fields" : [
-        ["groupId", 16, false],
-        ["selector", 16, false]
-      ]
-    },
-    {
-      "name" : "wcmp_meta_t",
-      "id" : 7,
-      "fields" : [
-        ["groupId", 16, false],
-        ["numBits", 8, false],
-        ["selector", 64, false]
-      ]
-    },
-    {
-      "name" : "intrinsic_metadata_t",
-      "id" : 8,
-      "fields" : [
-        ["ingress_global_timestamp", 32, false],
-        ["lf_field_list", 32, false],
-        ["mcast_grp", 16, false],
-        ["egress_rid", 16, false]
-      ]
-    }
-  ],
-  "headers" : [
-    {
-      "name" : "standard_metadata_3",
-      "id" : 0,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "standard_metadata_4",
-      "id" : 1,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "standard_metadata_5",
-      "id" : 2,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "scalars",
-      "id" : 3,
-      "header_type" : "scalars_0",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 4,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ethernet",
-      "id" : 5,
-      "header_type" : "ethernet_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ipv4",
-      "id" : 6,
-      "header_type" : "ipv4_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "tcp",
-      "id" : 7,
-      "header_type" : "tcp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "udp",
-      "id" : 8,
-      "header_type" : "udp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ecmp_metadata",
-      "id" : 9,
-      "header_type" : "ecmp_metadata_t",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "wcmp_meta",
-      "id" : 10,
-      "header_type" : "wcmp_meta_t",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "intrinsic_metadata",
-      "id" : 11,
-      "header_type" : "intrinsic_metadata_t",
-      "metadata" : true,
-      "pi_omit" : true
-    }
-  ],
-  "header_stacks" : [],
-  "header_union_types" : [],
-  "header_unions" : [],
-  "header_union_stacks" : [],
-  "field_lists" : [],
-  "errors" : [
-    ["NoError", 1],
-    ["PacketTooShort", 2],
-    ["NoMatch", 3],
-    ["StackOutOfBounds", 4],
-    ["HeaderTooShort", 5],
-    ["ParserTimeout", 6]
-  ],
-  "enums" : [],
-  "parsers" : [
-    {
-      "name" : "parser",
-      "id" : 0,
-      "init_state" : "start",
-      "parse_states" : [
-        {
-          "name" : "parse_ipv4",
-          "id" : 0,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ipv4"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x06",
-              "mask" : null,
-              "next_state" : "parse_tcp"
-            },
-            {
-              "value" : "0x11",
-              "mask" : null,
-              "next_state" : "parse_udp"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ipv4", "protocol"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_tcp",
-          "id" : 1,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "tcp"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "parse_udp",
-          "id" : 2,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "udp"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "start",
-          "id" : 3,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ethernet"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x0800",
-              "mask" : null,
-              "next_state" : "parse_ipv4"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ethernet", "etherType"]
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "deparsers" : [
-    {
-      "name" : "deparser",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "include/parsers.p4",
-        "line" : 43,
-        "column" : 8,
-        "source_fragment" : "DeparserImpl"
-      },
-      "order" : ["ethernet", "ipv4", "udp", "tcp"]
-    }
-  ],
-  "meter_arrays" : [],
-  "counter_arrays" : [
-    {
-      "name" : "table0_counter",
-      "id" : 0,
-      "is_direct" : true,
-      "binding" : "table0"
-    },
-    {
-      "name" : "port_counters_control.egress_port_counter",
-      "id" : 1,
-      "source_info" : {
-        "filename" : "include/port_counters.p4",
-        "line" : 6,
-        "column" : 38,
-        "source_fragment" : "egress_port_counter"
-      },
-      "size" : 254,
-      "is_direct" : false
-    },
-    {
-      "name" : "port_counters_control.ingress_port_counter",
-      "id" : 2,
-      "source_info" : {
-        "filename" : "include/port_counters.p4",
-        "line" : 7,
-        "column" : 38,
-        "source_fragment" : "ingress_port_counter"
-      },
-      "size" : 254,
-      "is_direct" : false
-    }
-  ],
-  "register_arrays" : [],
-  "calculations" : [],
-  "learn_lists" : [],
-  "actions" : [
-    {
-      "name" : "set_egress_port",
-      "id" : 0,
-      "runtime_data" : [
-        {
-          "name" : "port",
-          "bitwidth" : 9
-        }
-      ],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_spec"]
-            },
-            {
-              "type" : "runtime_data",
-              "value" : 0
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 9,
-            "column" : 4,
-            "source_fragment" : "standard_metadata.egress_spec = port"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_3", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 8,
-            "column" : 49,
-            "source_fragment" : "standard_metadata, Port port) { ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "send_to_cpu",
-      "id" : 1,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_spec"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x00ff"
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 5,
-            "column" : 4,
-            "source_fragment" : "standard_metadata.egress_spec = 9w255"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_4", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 4,
-            "column" : 45,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "drop",
-      "id" : 2,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_spec"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x01ff"
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 13,
-            "column" : 4,
-            "source_fragment" : "standard_metadata.egress_spec = 9w511"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "clone_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "clone_spec"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "instance_type"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "instance_type"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "drop"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "drop"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "recirculate_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "recirculate_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "packet_length"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "packet_length"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "enq_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "enq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "enq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_timedelta"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "deq_timedelta"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "deq_qdepth"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "deq_qdepth"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_global_timestamp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "ingress_global_timestamp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "lf_field_list"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "lf_field_list"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "mcast_grp"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "mcast_grp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "resubmit_flag"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "resubmit_flag"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_rid"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata_5", "egress_rid"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 12,
-            "column" : 38,
-            "source_fragment" : "standard_metadata) { ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "NoAction",
-      "id" : 3,
-      "runtime_data" : [],
-      "primitives" : []
-    },
-    {
-      "name" : "act",
-      "id" : 4,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
-                    "value" : ["standard_metadata", "ingress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_control.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 11,
-            "column" : 12,
-            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
-                    "value" : ["standard_metadata", "egress_spec"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_control.egress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 12,
-            "column" : 12,
-            "source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
-          }
-        }
-      ]
-    }
-  ],
-  "pipelines" : [
-    {
-      "name" : "ingress",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "default.p4",
-        "line" : 10,
-        "column" : 8,
-        "source_fragment" : "ingress"
-      },
-      "init_table" : "table0",
-      "tables" : [
-        {
-          "name" : "table0",
-          "id" : 0,
-          "source_info" : {
-            "filename" : "default.p4",
-            "line" : 13,
-            "column" : 10,
-            "source_fragment" : "table0"
-          },
-          "key" : [
-            {
-              "match_type" : "ternary",
-              "target" : ["standard_metadata", "ingress_port"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "dstAddr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "srcAddr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "etherType"],
-              "mask" : null
-            }
-          ],
-          "match_type" : "ternary",
-          "type" : "simple",
-          "max_size" : 1024,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [0, 1, 2, 3],
-          "actions" : ["set_egress_port", "send_to_cpu", "drop", "NoAction"],
-          "base_default_next" : "node_3",
-          "next_tables" : {
-            "set_egress_port" : "node_3",
-            "send_to_cpu" : "node_3",
-            "drop" : "node_3",
-            "NoAction" : "node_3"
-          },
-          "default_entry" : {
-            "action_id" : 3,
-            "action_const" : false,
-            "action_data" : [],
-            "action_entry_const" : false
-          }
-        },
-        {
-          "name" : "tbl_act",
-          "id" : 1,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [4],
-          "actions" : ["act"],
-          "base_default_next" : null,
-          "next_tables" : {
-            "act" : null
-          },
-          "default_entry" : {
-            "action_id" : 4,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        }
-      ],
-      "action_profiles" : [],
-      "conditionals" : [
-        {
-          "name" : "node_3",
-          "id" : 0,
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 10,
-            "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec < 254"
-          },
-          "expression" : {
-            "type" : "expression",
-            "value" : {
-              "op" : "<",
-              "left" : {
-                "type" : "field",
-                "value" : ["standard_metadata", "egress_spec"]
-              },
-              "right" : {
-                "type" : "hexstr",
-                "value" : "0x00fe"
-              }
-            }
-          },
-          "false_next" : null,
-          "true_next" : "tbl_act"
-        }
-      ]
-    },
-    {
-      "name" : "egress",
-      "id" : 1,
-      "source_info" : {
-        "filename" : "default.p4",
-        "line" : 36,
-        "column" : 8,
-        "source_fragment" : "egress"
-      },
-      "init_table" : null,
-      "tables" : [],
-      "action_profiles" : [],
-      "conditionals" : []
-    }
-  ],
-  "checksums" : [],
-  "force_arith" : [],
-  "extern_instances" : [],
-  "field_aliases" : [
-    [
-      "queueing_metadata.enq_timestamp",
-      ["standard_metadata", "enq_timestamp"]
-    ],
-    [
-      "queueing_metadata.enq_qdepth",
-      ["standard_metadata", "enq_qdepth"]
-    ],
-    [
-      "queueing_metadata.deq_timedelta",
-      ["standard_metadata", "deq_timedelta"]
-    ],
-    [
-      "queueing_metadata.deq_qdepth",
-      ["standard_metadata", "deq_qdepth"]
-    ],
-    [
-      "intrinsic_metadata.ingress_global_timestamp",
-      ["standard_metadata", "ingress_global_timestamp"]
-    ],
-    [
-      "intrinsic_metadata.lf_field_list",
-      ["standard_metadata", "lf_field_list"]
-    ],
-    [
-      "intrinsic_metadata.mcast_grp",
-      ["standard_metadata", "mcast_grp"]
-    ],
-    [
-      "intrinsic_metadata.resubmit_flag",
-      ["standard_metadata", "resubmit_flag"]
-    ],
-    [
-      "intrinsic_metadata.egress_rid",
-      ["standard_metadata", "egress_rid"]
-    ]
-  ]
-}
\ No newline at end of file
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.p4info b/protocols/p4runtime/ctl/src/test/resources/default.p4info
deleted file mode 100644
index b224b87..0000000
--- a/protocols/p4runtime/ctl/src/test/resources/default.p4info
+++ /dev/null
@@ -1,147 +0,0 @@
-tables {
-  preamble {
-    id: 33617813
-    name: "table0"
-    alias: "table0"
-  }
-  match_fields {
-    id: 1
-    name: "standard_metadata.ingress_port"
-    bitwidth: 9
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 2
-    name: "hdr.ethernet.dstAddr"
-    bitwidth: 48
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 3
-    name: "hdr.ethernet.srcAddr"
-    bitwidth: 48
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 4
-    name: "hdr.ethernet.etherType"
-    bitwidth: 16
-    match_type: TERNARY
-  }
-  action_refs {
-    id: 16794308
-  }
-  action_refs {
-    id: 16829080
-  }
-  action_refs {
-    id: 16793508
-  }
-  action_refs {
-    id: 16800567
-    annotations: "@defaultonly()"
-  }
-  direct_resource_ids: 301990488
-  size: 1024
-  with_entry_timeout: true
-}
-actions {
-  preamble {
-    id: 16794308
-    name: "set_egress_port"
-    alias: "set_egress_port"
-  }
-  params {
-    id: 1
-    name: "port"
-    bitwidth: 9
-  }
-}
-actions {
-  preamble {
-    id: 16829080
-    name: "send_to_cpu"
-    alias: "send_to_cpu"
-  }
-}
-actions {
-  preamble {
-    id: 16793508
-    name: "drop"
-    alias: "drop"
-  }
-}
-actions {
-  preamble {
-    id: 16800567
-    name: "NoAction"
-    alias: "NoAction"
-  }
-}
-counters {
-  preamble {
-    id: 302025528
-    name: "port_counters_control.egress_port_counter"
-    alias: "egress_port_counter"
-  }
-  spec {
-    unit: PACKETS
-  }
-  size: 254
-}
-counters {
-  preamble {
-    id: 301999025
-    name: "port_counters_control.ingress_port_counter"
-    alias: "ingress_port_counter"
-  }
-  spec {
-    unit: PACKETS
-  }
-  size: 254
-}
-direct_counters {
-  preamble {
-    id: 301990488
-    name: "table0_counter"
-    alias: "table0_counter"
-  }
-  spec {
-    unit: PACKETS
-  }
-  direct_table_id: 33617813
-}
-controller_packet_metadata {
-  preamble {
-    id: 2868941301
-    name: "packet_in"
-    annotations: "@controller_header(\"packet_in\")"
-  }
-  metadata {
-    id: 1
-    name: "ingress_port"
-    bitwidth: 9
-  }
-  metadata {
-    id: 2
-    name: "other1"
-    bitwidth: 32
-  }
-}
-controller_packet_metadata {
-  preamble {
-    id: 2868916615
-    name: "packet_out"
-    annotations: "@controller_header(\"packet_out\")"
-  }
-  metadata {
-    id: 1
-    name: "egress_port"
-    bitwidth: 9
-  }
-  metadata {
-    id: 2
-    name: "other2"
-    bitwidth: 32
-  }
-}
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.p4info b/protocols/p4runtime/ctl/src/test/resources/default.p4info
new file mode 120000
index 0000000..4dda381
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/resources/default.p4info
@@ -0,0 +1 @@
+../../../../../../tools/test/p4src/p4-16/p4c-out/default.p4info
\ No newline at end of file
diff --git a/providers/bgpcep/flow/src/main/java/org/onosproject/provider/bgpcep/flow/impl/BgpcepFlowRuleProvider.java b/providers/bgpcep/flow/src/main/java/org/onosproject/provider/bgpcep/flow/impl/BgpcepFlowRuleProvider.java
index 8868153..83aaac8 100644
--- a/providers/bgpcep/flow/src/main/java/org/onosproject/provider/bgpcep/flow/impl/BgpcepFlowRuleProvider.java
+++ b/providers/bgpcep/flow/src/main/java/org/onosproject/provider/bgpcep/flow/impl/BgpcepFlowRuleProvider.java
@@ -22,7 +22,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleProvider;
 import org.onosproject.net.flow.FlowRuleProviderRegistry;
 import org.onosproject.net.flow.FlowRuleProviderService;
diff --git a/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java b/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java
index 095bdc4..2cbb4d0 100644
--- a/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java
+++ b/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java
@@ -27,8 +27,8 @@
 import org.onosproject.net.flow.DefaultFlowEntry;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleProvider;
 import org.onosproject.net.flow.FlowRuleProviderService;
 import org.slf4j.Logger;
diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
index 9a14393..9dc03cd 100644
--- a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -41,8 +41,8 @@
 import org.onosproject.net.flow.DefaultTableStatisticsEntry;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleBatchEntry;
-import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleExtPayLoad;
 import org.onosproject.net.flow.FlowRuleProvider;
 import org.onosproject.net.flow.FlowRuleProviderRegistry;
diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
index 54e2f5f..083a74c 100644
--- a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
@@ -1121,9 +1121,15 @@
         return obj.getVersion().wireVersion >= OFVersion.OF_13.wireVersion;
     }
 
-    private DriverHandler getDriver(DeviceId devId) {
-        Driver driver = driverService.getDriver(devId);
-        DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, devId));
+    /**
+     * Retrieves the driver handler for the specified device.
+     *
+     * @param deviceId device identifier
+     * @return driver handler
+     */
+    protected DriverHandler getDriver(DeviceId deviceId) {
+        Driver driver = driverService.getDriver(deviceId);
+        DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, deviceId));
         return handler;
     }
 }
diff --git a/tools/build/onos-release-prerequisites b/tools/build/onos-release-prerequisites
index 1eb4cb9..f3d2b6b 100755
--- a/tools/build/onos-release-prerequisites
+++ b/tools/build/onos-release-prerequisites
@@ -92,11 +92,11 @@
     echo "OK"
 }
 
-# Test access to wiki.onosproject.org
+# Test access to api.onosproject.org
 function testWikiAccess {
     trap "echo 'FAILED'" ERR
     printf "Checking Wiki access... "
-    ssh $WIKI_USER@wiki.onosproject.org "test -w /var/www/api/index.html"
+    ssh $WIKI_USER@api.onosproject.org "test -w /var/www/api/index.html"
     echo "OK"
 }
 
diff --git a/tools/build/onos-upload-docs b/tools/build/onos-upload-docs
index 3133d41..79986b6 100755
--- a/tools/build/onos-upload-docs
+++ b/tools/build/onos-upload-docs
@@ -9,7 +9,7 @@
 . $ONOS_ROOT/tools/build/envDefaults
 
 user=${1:-${WIKI_USER:-$USER}}
-remote=$user@wiki.onosproject.org
+remote=$user@api.onosproject.org
 
 docs=$(onos-buck build //docs:external --show-output 2>/dev/null | tail -1 | cut -d\  -f2)
 
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
index 61e7174..48cbd2d 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/ApplicationsResourceTest.java
@@ -107,30 +107,55 @@
     private static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
     private static final Version VER = Version.version(1, 2, "a", null);
 
+    private DefaultApplication.Builder baseBuilder = DefaultApplication.builder()
+                .withVersion(VER)
+                .withIcon(new byte[0])
+                .withRole(ApplicationRole.ADMIN)
+                .withPermissions(ImmutableSet.of())
+                .withFeaturesRepo(Optional.of(FURL))
+                .withFeatures(ImmutableList.of("My Feature"))
+                .withRequiredApps(ImmutableList.of());
+
     private Application app1 =
-            new DefaultApplication(id1, VER, "title1",
-                    "desc1", "origin1", "category1", "url1",
-                    "readme1", new byte[0], ApplicationRole.ADMIN,
-                    ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableList.of("My Feature"), ImmutableList.of());
+            DefaultApplication.builder(baseBuilder)
+                .withAppId(id1)
+                .withTitle("title1")
+                .withDescription("desc1")
+                .withOrigin("origin1")
+                .withCategory("category1")
+                .withUrl("url1")
+                .withReadme("readme1")
+                .build();
     private Application app2 =
-            new DefaultApplication(id2, VER, "title2",
-                    "desc2", "origin2", "category2", "url2",
-                    "readme2", new byte[0], ApplicationRole.ADMIN,
-                    ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableList.of("My Feature"), ImmutableList.of());
+            DefaultApplication.builder(baseBuilder)
+                    .withAppId(id2)
+                    .withTitle("title2")
+                    .withDescription("desc2")
+                    .withOrigin("origin2")
+                    .withCategory("category2")
+                    .withUrl("url2")
+                    .withReadme("readme2")
+                    .build();
     private Application app3 =
-            new DefaultApplication(id3, VER, "title3",
-                    "desc3", "origin3", "category3", "url3",
-                    "readme3", new byte[0], ApplicationRole.ADMIN,
-                    ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableList.of("My Feature"), ImmutableList.of());
+            DefaultApplication.builder(baseBuilder)
+                    .withAppId(id3)
+                    .withTitle("title3")
+                    .withDescription("desc3")
+                    .withOrigin("origin3")
+                    .withCategory("category3")
+                    .withUrl("url3")
+                    .withReadme("readme3")
+                    .build();
     private Application app4 =
-            new DefaultApplication(id4, VER, "title4",
-                    "desc4", "origin4", "category4", "url4",
-                    "readme4", new byte[0], ApplicationRole.ADMIN,
-                    ImmutableSet.of(), Optional.of(FURL),
-                    ImmutableList.of("My Feature"), ImmutableList.of());
+            DefaultApplication.builder(baseBuilder)
+                    .withAppId(id4)
+                    .withTitle("title4")
+                    .withDescription("desc4")
+                    .withOrigin("origin4")
+                    .withCategory("category4")
+                    .withUrl("url4")
+                    .withReadme("readme4")
+                    .build();
 
     /**
      * Hamcrest matcher to check that an application representation in JSON matches
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
index 0c82575..b5c5398 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
@@ -129,12 +129,12 @@
 
 lp_label_a_type=A type
 lp_label_a_id=A id
-lp_label_a_friendly=A friendly
+lp_label_a_friendly=A name
 lp_label_a_port=A port
 
 lp_label_b_type=B type
 lp_label_b_id=B id
-lp_label_b_friendly=B friendly
+lp_label_b_friendly=B name
 lp_label_b_port=B port
 
 lp_label_a2b=A to B
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
index 6631bc5..3630c22 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
@@ -128,12 +128,12 @@
 
 lp_label_a_type=Tipo A
 lp_label_a_id=A id
-lp_label_a_friendly=A friendly
+lp_label_a_friendly=A name
 lp_label_a_port=Puerto A 
 
 lp_label_b_type=Tipo B
 lp_label_b_id=B id
-lp_label_b_friendly=B friendly
+lp_label_b_friendly=B name
 lp_label_b_port=Puerto B
 
 lp_label_a2b=A to B
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_ko.properties
index f09adb8..08d7dc6 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_ko.properties
@@ -129,12 +129,12 @@
 
 lp_label_a_type=A 타입
 lp_label_a_id=A 아이디
-lp_label_a_friendly=A friendly
+lp_label_a_friendly=A name
 lp_label_a_port=A 포트
 
 lp_label_b_type=B 타입
 lp_label_b_id=B 아이디
-lp_label_b_friendly=B friendly
+lp_label_b_friendly=B name
 lp_label_b_port=B 포트
 
 lp_label_a2b=A에서 B
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_CN.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_CN.properties
index 8aa7c5a..e02c978 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_CN.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_CN.properties
@@ -128,11 +128,11 @@
 lp_label_friendly=friendly
 lp_label_a_type=A 类型
 lp_label_a_id=A id
-lp_label_a_friendly=A friendly
+lp_label_a_friendly=A name
 lp_label_a_port=A 端口
 lp_label_b_type=B 类型
 lp_label_b_id=B id
-lp_label_b_friendly=B friendly
+lp_label_b_friendly=B name
 lp_label_b_port=B 端口
 lp_label_a2b=A 到 B
 lp_label_b2a=B 到 A
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_TW.properties
index 000bd12..9eabe7c 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_zh_TW.properties
@@ -127,12 +127,12 @@
 
 lp_label_a_type=A 種類
 lp_label_a_id=A ID
-lp_label_a_friendly=A friendly
+lp_label_a_friendly=A name
 lp_label_a_port=A 連接埠
 
 lp_label_b_type=B 種類
 lp_label_b_id=B ID
-lp_label_b_friendly=B friendly
+lp_label_b_friendly=B name
 lp_label_b_port=B 連接埠
 
 lp_label_a2b=A 到 B