Merge remote-tracking branch 'origin/master' into dev/murrelet

Change-Id: Ie0c18deb1f6faef08abbd54c7d0b20e7009624e8
diff --git a/Jenkinsfile b/Jenkinsfile
index 64b41db..11b889a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -7,10 +7,7 @@
     stages {
         stage('pull') {
             steps {
-                sh 'which warden-client && sum `which warden-client`'
-                sh 'warden-client list'
                 git url: 'https://gerrit.onosproject.org/onos'
-                sh 'warden-client --reqId CI-${BUILD_NUMBER} --timeout 5 --duration 10 --nodes 1 reserve'
             }
         }
 
@@ -48,32 +45,10 @@
                             docker build -t onosproject/onos-test-docker .
                         '''
                     },
-                    "stc": {
-                        timeout(10) {
-                            sh '''#!/bin/bash -l
-                                export stcColor=false
-                                ONOS_ROOT=`pwd`
-                                source tools/build/envDefaults
-                                onos-package-test
-                                echo "Waiting for cell..."
-                                warden-client --reqId CI-${BUILD_NUMBER} status > cell.txt
-                                source cell.txt
-                                rm -f cell.txt
-                                proxy-stc
-                            '''
-                        }
-                    }
                 )
             }
         }
     }
 
-    post {
-        always {
-            sh '''#!/bin/bash -l
-            warden-client --reqId CI-${BUILD_NUMBER} return
-            '''
-        }
-    }
 }
 
diff --git a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
index ccab5cf..227ec24 100644
--- a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
+++ b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
@@ -83,10 +83,11 @@
 
     private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
 
-    private static final int NUM_LEAFS = 2;
-    private static final int NUM_SPINES = 2;
-    protected static final int HASHED_LINKS = 2;
+    // TOPO_SIZE should be the same of the --size argument when running bmv2-demo.py
+    private static final int TOPO_SIZE = 2;
     private static final boolean WITH_IMBALANCED_STRIPING = false;
+    protected static final int HASHED_LINKS = TOPO_SIZE + (WITH_IMBALANCED_STRIPING ? 1 : 0);
+
     private static final int FLOW_PRIORITY = 100;
     private static final int CHECK_TOPOLOGY_INTERVAL_SECONDS = 5;
 
@@ -362,7 +363,7 @@
                 .map(TopologyVertex::deviceId)
                 .forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
 
-        if (spines.size() != NUM_SPINES || leafs.size() != NUM_LEAFS) {
+        if (spines.size() != TOPO_SIZE || leafs.size() != TOPO_SIZE) {
             log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
                      spines.size(), leafs.size());
             return;
@@ -371,8 +372,7 @@
         for (DeviceId did : spines) {
             int portCount = deviceService.getPorts(did).size();
             // Expected port count: num leafs + 1 redundant leaf link (if imbalanced)
-            int expectedSpinePortCount = NUM_LEAFS + (WITH_IMBALANCED_STRIPING ? 1 : 0);
-            if (portCount != expectedSpinePortCount) {
+            if (portCount != HASHED_LINKS) {
                 log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
                 return;
             }
@@ -380,8 +380,7 @@
         for (DeviceId did : leafs) {
             int portCount = deviceService.getPorts(did).size();
             // Expected port count: num spines + host port + 1 redundant spine link
-            int expectedLeafPortCount = NUM_LEAFS + (WITH_IMBALANCED_STRIPING ? 2 : 1);
-            if (portCount != expectedLeafPortCount) {
+            if (portCount != HASHED_LINKS + 1) {
                 log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
                 return;
             }
@@ -390,7 +389,7 @@
         // Check hosts, number and exactly one per leaf
         Map<DeviceId, Host> hostMap = Maps.newHashMap();
         hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
-        if (hosts.size() != NUM_LEAFS || !leafs.equals(hostMap.keySet())) {
+        if (hosts.size() != TOPO_SIZE || !leafs.equals(hostMap.keySet())) {
             log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
             return;
         }
diff --git a/bucklets/yang.bucklet b/bucklets/yang.bucklet
index 490c62a..5fbf2ab 100644
--- a/bucklets/yang.bucklet
+++ b/bucklets/yang.bucklet
@@ -52,7 +52,7 @@
     genrule(
       name = registrator,
       srcs = [ ':' + yangSrcs ],
-      cmd = '$(location //buck-tools:yang-registrator) $OUT app_name $SRCS',
+      cmd = '$(location //buck-tools:yang-registrator) $OUT ' + app_name + ' $SRCS',
       out = 'YangModelRegistrator.java'
     )
 
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationCommand.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationCommand.java
index 5916dbf..4db129d 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationCommand.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.cli.app;
 
+import com.google.common.io.ByteStreams;
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.app.ApplicationAdminService;
@@ -38,9 +39,10 @@
     static final String UNINSTALL = "uninstall";
     static final String ACTIVATE = "activate";
     static final String DEACTIVATE = "deactivate";
+    static final String DOWNLOAD = "download";
 
     @Argument(index = 0, name = "command",
-            description = "Command name (install|activate|deactivate|uninstall)",
+            description = "Command name (install|activate|deactivate|uninstall|download)",
             required = true, multiValued = false)
     String command = null;
 
@@ -58,14 +60,20 @@
                 }
             }
 
-        } else {
+        } else if (command.equals(DOWNLOAD)) {
             for (String name : names) {
-                if (!manageApp(service, name)) {
+                if (!downloadApp(service, name)) {
                     return;
                 }
             }
+        } else {
+                for (String name : names) {
+                    if (!manageApp(service, name)) {
+                        return;
+                    }
+                }
+            }
         }
-    }
 
     // Installs the application from input of the specified URL
     private boolean installApp(ApplicationAdminService service, String url) {
@@ -82,6 +90,18 @@
         return true;
     }
 
+    // Downloads the application bits to the standard output.
+    private boolean downloadApp(ApplicationAdminService service, String name) {
+        try {
+            ByteStreams.copy(service.getApplicationArchive(service.getId(name)),
+                             System.out);
+        } catch (IOException e) {
+            error("Unable to download bits for application %s", name);
+            return false;
+        }
+        return true;
+    }
+
     // Manages the specified application.
     private boolean manageApp(ApplicationAdminService service, String name) {
         ApplicationId appId = service.getId(name);
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationCommandCompleter.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationCommandCompleter.java
index eb53ec6..8164b0e 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationCommandCompleter.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationCommandCompleter.java
@@ -28,7 +28,7 @@
 public class ApplicationCommandCompleter extends AbstractChoicesCompleter {
     @Override
     public List<String> choices() {
-        return ImmutableList.of(INSTALL, UNINSTALL, ACTIVATE, DEACTIVATE);
+        return ImmutableList.of(INSTALL, UNINSTALL, ACTIVATE, DEACTIVATE, DOWNLOAD);
     }
 
 }
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
index 0efb1dd..1848bf3 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
@@ -63,7 +63,7 @@
 //            if (previousApps.contains(app.id().name())) {
 //                continue;
 //            }
-            if ("uninstall".equals(cmd) ||
+            if ("uninstall".equals(cmd) || "download".equals(cmd) ||
                     ("activate".equals(cmd) && state == INSTALLED) ||
                     ("deactivate".equals(cmd) && state == ACTIVE)) {
                 strings.add(app.id().name());
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationService.java b/core/api/src/main/java/org/onosproject/app/ApplicationService.java
index 683f90a..51d3eb4 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationService.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationService.java
@@ -20,6 +20,7 @@
 import org.onosproject.event.ListenerService;
 import org.onosproject.security.Permission;
 
+import java.io.InputStream;
 import java.util.Set;
 
 /**
@@ -74,4 +75,14 @@
      * @param hook  pre-deactivation hook
      */
     void registerDeactivateHook(ApplicationId appId, Runnable hook);
+
+    /**
+     * Returns stream that contains the application OAR/JAR file contents.
+     *
+     * @param appId application identifier
+     * @return input stream containing the app OAR/JAR file
+     */
+    default InputStream getApplicationArchive(ApplicationId appId) {
+        return null;
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/app/ApplicationStore.java b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
index 3544956..bd5a66d 100644
--- a/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
+++ b/core/api/src/main/java/org/onosproject/app/ApplicationStore.java
@@ -105,4 +105,14 @@
      */
     void setPermissions(ApplicationId appId, Set<Permission> permissions);
 
+    /**
+     * Returns stream that contains the application OAR/JAR file contents.
+     *
+     * @param appId application identifier
+     * @return input stream containing the app OAR/JAR file
+     */
+    default InputStream getApplicationArchive(ApplicationId appId) {
+        return null;
+    }
+
 }
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 ec38607..92709cb 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
@@ -199,13 +199,11 @@
             checkState(!appFile(desc.name(), APP_XML).exists(),
                     "Application %s already installed", desc.name());
 
-            boolean isSelfContainedJar = false;
-
             if (plainXml) {
                 expandPlainApplication(cache, desc);
             } else {
                 bis.reset();
-                isSelfContainedJar = expandZippedApplication(bis, desc);
+                boolean isSelfContainedJar = expandZippedApplication(bis, desc);
 
                 if (isSelfContainedJar) {
                     bis.reset();
@@ -254,7 +252,7 @@
 
     /**
      * Returns application archive stream for the specified application. This
-     * will be either the application ZIP file or the application XML file.
+     * will be either the application OAR file, JAR file or the plain XML file.
      *
      * @param appName application name
      * @return application archive stream
diff --git a/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java b/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
index 3c3d60b..d8b09b0 100644
--- a/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
+++ b/core/net/src/main/java/org/onosproject/app/impl/ApplicationManager.java
@@ -179,6 +179,12 @@
         store.setPermissions(appId, permissions);
     }
 
+    @Override
+    public InputStream getApplicationArchive(ApplicationId appId) {
+        checkNotNull(appId, APP_ID_NULL);
+        return store.getApplicationArchive(appId);
+    }
+
     private void updateStoreAndWaitForNotificationHandling(ApplicationId appId,
                                                            Consumer<ApplicationId> storeUpdateTask) {
         CountDownLatch latch = new CountDownLatch(1);
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 21450c0..c64942f 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
@@ -437,6 +437,11 @@
         }
     }
 
+    @Override
+    public InputStream getApplicationArchive(ApplicationId appId) {
+        return getApplicationInputStream(appId.name());
+    }
+
     private class AppActivator implements Consumer<Application> {
         @Override
         public void accept(Application app) {
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java
index cf8c084..86e8200 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java
@@ -156,7 +156,7 @@
                     .findFirst().get();
 
             if (output == null || !output.port().equals(PortNumber.CONTROLLER)) {
-                log.error("OLT can only filter packet to controller");
+                log.warn("OLT can only filter packet to controller");
                 fail(filter, ObjectiveError.UNSUPPORTED);
                 return;
             }
@@ -183,6 +183,11 @@
         } else if (ethType.ethType().equals(EthType.EtherType.IPV4.ethType())) {
             IPProtocolCriterion ipProto = (IPProtocolCriterion)
                     filterForCriterion(filter.conditions(), Criterion.Type.IP_PROTO);
+            if (ipProto == null) {
+                log.warn("OLT can only filter IGMP and DHCP");
+                fail(filter, ObjectiveError.UNSUPPORTED);
+                return;
+            }
             if (ipProto.protocol() == IPv4.PROTOCOL_IGMP) {
                 provisionIgmp(filter, ethType, ipProto, output);
             } else if (ipProto.protocol() == IPv4.PROTOCOL_UDP) {
@@ -193,16 +198,18 @@
                         filterForCriterion(filter.conditions(), Criterion.Type.UDP_DST);
 
                 if (udpSrcPort.udpPort().toInt() != 68 || udpDstPort.udpPort().toInt() != 67) {
-                    log.error("OLT can only filte DHCP, wrong UDP Src or Dst Port");
+                    log.warn("OLT can only filter DHCP, wrong UDP Src or Dst Port");
                     fail(filter, ObjectiveError.UNSUPPORTED);
                 }
                 provisionDhcp(filter, ethType, ipProto, udpSrcPort, udpDstPort, output);
             } else {
-                log.error("OLT can only filter igmp and DHCP");
+                log.warn("OLT can only filter IGMP and DHCP");
                 fail(filter, ObjectiveError.UNSUPPORTED);
             }
         } else {
-            log.error("OLT can only filter eapol igmp");
+            log.warn("\nOnly the following are Supported in OLT for filter ->\n"
+                    + "ETH TYPE : EAPOL and IPV4\n"
+                    + "IPV4 TYPE: IGMP and UDP (for DHCP)");
             fail(filter, ObjectiveError.UNSUPPORTED);
         }
 
diff --git a/protocols/p4runtime/proto/BUCK b/protocols/p4runtime/proto/BUCK
index 8239990..a50c71f 100644
--- a/protocols/p4runtime/proto/BUCK
+++ b/protocols/p4runtime/proto/BUCK
@@ -5,7 +5,7 @@
 PROTOBUF_VER = '3.0.2'
 GRPC_VER = '1.3.0'
 
-PI_COMMIT = 'b7053f7ad32eb8cddab2ed1f4a7e1e6a30fc5e57'
+PI_COMMIT = '9fc50cd0a0187eb1346272524d4b8bafb51bb513'
 PI_BASEURL = 'https://github.com/p4lang/PI.git'
 
 # Wondering which .proto files to build? Check p4runtime's Makefile:
diff --git a/tools/dev/bin/onos-setup-p4-dev b/tools/dev/bin/onos-setup-p4-dev
index 56eb6d3..b8483c5 100755
--- a/tools/dev/bin/onos-setup-p4-dev
+++ b/tools/dev/bin/onos-setup-p4-dev
@@ -15,8 +15,8 @@
 set -e
 
 BUILD_DIR=~/p4tools
-BMV2_COMMIT="1683392d8859907b8367cc0188d3d4ed8bea268c"
-PI_COMMIT="6b47641344c2b0bc77e51876517b98eda51eda45"
+BMV2_COMMIT="4eeb8dad8e8f062636f9d0d8296aa7f288c6f6dd"
+PI_COMMIT="9fc50cd0a0187eb1346272524d4b8bafb51bb513"
 P4C_COMMIT="55067fd0e5f9e25fef06e58e49033da3493f796d"
 PROTOBUF_COMMIT="tags/v3.0.2"
 GRPC_COMMIT="tags/v1.3.0"
diff --git a/tools/test/p4src/p4-16/p4c-out/default.json b/tools/test/p4src/p4-16/p4c-out/default.json
index 62e397a..2dd5c49 100644
--- a/tools/test/p4src/p4-16/p4c-out/default.json
+++ b/tools/test/p4src/p4-16/p4c-out/default.json
@@ -231,12 +231,12 @@
   "header_union_stacks" : [],
   "field_lists" : [],
   "errors" : [
-    ["NoError", 0],
-    ["PacketTooShort", 1],
-    ["NoMatch", 2],
-    ["StackOutOfBounds", 3],
-    ["HeaderTooShort", 4],
-    ["ParserTimeout", 5]
+    ["NoError", 1],
+    ["PacketTooShort", 2],
+    ["NoMatch", 3],
+    ["StackOutOfBounds", 4],
+    ["HeaderTooShort", 5],
+    ["ParserTimeout", 6]
   ],
   "enums" : [],
   "parsers" : [
diff --git a/tools/test/scenarios/yang-live-compile.xml b/tools/test/scenarios/yang-live-compile.xml
index 7ddad20..baca2fe 100644
--- a/tools/test/scenarios/yang-live-compile.xml
+++ b/tools/test/scenarios/yang-live-compile.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<scenario name="yang-lve-compile" description="Live YANG compilation">
+<scenario name="yang-live-compile" description="Live YANG compilation">
     <group name="YANG-Live-Compile">
         <step name="Activate-YANG-Runtime" exec="onos ${OC1} app activate org.onosproject.yang"/>
         <step name="Pre-Cleanup-YANG-Model" exec="onos ${OC1} app uninstall l3vpn" env="~"/>
@@ -21,11 +21,19 @@
               requires="Activate-YANG-Runtime"/>
         <step name="Compile-YANG-Model" exec="onos-compile-yang ${OC1} ${ONOS_ROOT}/tools/test/configs/yang"
               requires="Check-YANG-Runtime,Pre-Cleanup-YANG-Model"/>
-        <step name="Verify-YANG-Models" exec="onos ${OC1} models | grep l3vpn"
-              requires="Compile-YANG-Model"/>
-        <step name="Verify-YANG-Apps" exec="onos-check-apps ${OC1} drivers,yang,l3vpn includes"
-              requires="Compile-YANG-Model"/>
+
+        <group name="Verify-YANG-Models" requires="Compile-YANG-Model">
+            <parallel var="${OC#}">
+                <step name="Verify-Model-Registration-${#}"
+                      exec="onos ${OC#} models | grep l3vpn"/>
+                <step name="Verify-Model-Component-${#}"
+                      exec="onos-check-component ${OC#} yang.YangModelRegistrator ACTIVE"/>
+                <step name="Verify-YANG-Apps-${#}"
+                      exec="onos-check-apps ${OC#} drivers,yang,l3vpn includes"/>
+            </parallel>
+        </group>
+
         <step name="Cleanup-YANG-Model" exec="onos ${OC1} app uninstall l3vpn"
-              requires="Verify-YANG-Apps"/>
+              requires="Verify-YANG-Models"/>
     </group>
 </scenario>
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
index 3a4eb5c..4a71ffd 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
@@ -208,6 +208,23 @@
     }
 
     /**
+     * Get application OAR/JAR file.
+     * Returns the OAR/JAR file used to install the specified application.
+     *
+     * @param name application name
+     * @return 200 OK; 404; 401
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @Path("{name}/bits")
+    public Response getAppBits(@PathParam("name") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = nullIsNotFound(service.getId(name), APP_ID_NOT_FOUND);
+        InputStream bits = service.getApplicationArchive(appId);
+        return ok(bits).build();
+    }
+
+    /**
      * Gets applicationId entry by either id or name.
      *
      * @param id   id of application
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
index d245eff..16f288a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
@@ -58,6 +58,26 @@
         return Response.ok().build();
     }
 
+    /**
+     * Get application OAR/JAR file.
+     * Returns the OAR/JAR file used to install the specified application.
+     *
+     * @param name application name
+     * @return 200 OK; 404; 401
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @Path("{name}/download")
+    public Response download(@PathParam("name") String name) {
+        ApplicationAdminService service = get(ApplicationAdminService.class);
+        ApplicationId appId = service.getId(name);
+        InputStream bits = service.getApplicationArchive(appId);
+        String fileName = appId.name() + ".oar";
+        return Response.ok(bits)
+                .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
+                .build();
+    }
+
     @Path("{name}/icon")
     @GET
     @Produces("image/png")
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
index 35b89df..8d4b60f 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -32,6 +32,8 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.link.LinkService;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.table.TableModel;
@@ -42,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import static com.google.common.base.Strings.emptyToNull;
@@ -80,6 +83,7 @@
     private static final String HW = "hw";
     private static final String SW = "sw";
     private static final String PROTOCOL = "protocol";
+    private static final String PIPECONF = "pipeconf";
     private static final String MASTER_ID = "masterid";
     private static final String CHASSIS_ID = "chassisid";
     private static final String SERIAL = "serial";
@@ -202,6 +206,7 @@
             data.put(CHASSIS_ID, device.chassisId().toString());
             data.put(MASTER_ID, masterFor != null ? masterFor.toString() : NONE);
             data.put(PROTOCOL, deviceProtocol(device));
+            data.put(PIPECONF, devicePipeconf(device));
 
             ArrayNode ports = arrayNode();
 
@@ -257,8 +262,17 @@
 
             return port;
         }
-    }
 
+        private String devicePipeconf(Device device) {
+            PiPipeconfService service = get(PiPipeconfService.class);
+            Optional<PiPipeconfId> pipeconfId = service.ofDevice(device.id());
+            if (pipeconfId.isPresent()) {
+                return pipeconfId.get().id();
+            } else {
+                return NONE;
+            }
+        }
+    }
 
     // handler for changing device friendly name
     private final class NameChangeHandler extends RequestHandler {
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
index 7a93ea1..1f6d81c 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
@@ -30,7 +30,7 @@
 # --- Elements (Plural)
 nodes=노드
 topologies=토폴로지
-topology_sccs=Topology SCCs (ko)
+topology_sccs=토폴로지 강한 연결 요소
 networks=네트워크
 regions=리젼
 devices=장치
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_CN.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_CN.properties
index fb8bf1a..a8d5e11 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_CN.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_CN.properties
@@ -19,5 +19,14 @@
 # Tooltip for context-sensitive help button [?]
 tt_help=显示当前视图的帮助页面
 
+# unknown user
+unknown_user=未知用户
+
 # Logout button
 logout=注销
+
+# UI components added/removed, etc.
+uicomp_added=添加了新的用户界面组件
+uicomp_removed=移除了一些用户界面组件
+ui_ok_to_update=选择"允许"更新用户界面
+confirm_refresh_title=用户界面已正常刷新
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App.properties
index 5ce011a..b362edd 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=Activate selected application
 tt_ctl_deactivate=Deactivate selected application
 tt_ctl_uninstall=Uninstall selected application
+tt_ctl_download=Download selected application (.oar file)
 
 # Quick-Help panel
 qh_hint_esc=Deselect application
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_es.properties
index cadedf4..74ae11b 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_es.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_es.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=Activar aplicación seleccionada
 tt_ctl_deactivate=Desactivar aplicación seleccionada
 tt_ctl_uninstall=Desinstalar aplicación seleccionada
+tt_ctl_download=Download selected application (.oar file) (es)
 
 # Quick-Help panel
 qh_hint_esc=Deseleccionar aplicación
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_it.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_it.properties
index 9435e4c..15cd698 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_it.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_it.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=Attiva l'applicazione selezionata
 tt_ctl_deactivate=Disattiva l'applicazione selezionata
 tt_ctl_uninstall=Rimuovi l'applicazione selezionata
+tt_ctl_download=Download selected application (.oar file) (it)
 
 # Quick-Help panel
 qh_hint_esc=Deseleziona l'applicazione
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_ko.properties
index 5d7af01..040a9db 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_ko.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=선택된 어플리케이션 활성화
 tt_ctl_deactivate=선택된 어플리케이션 비활성화
 tt_ctl_uninstall=선택된 어플리케이션 삭제
+tt_ctl_download=Download selected application (.oar file) (ko)
 
 # Quick-Help panel
 qh_hint_esc=어플리케이션 선택 취소
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_CN.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_CN.properties
index f1120c8..44b60a2 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_CN.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_CN.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=激活选定的应用
 tt_ctl_deactivate=停用选定的应用
 tt_ctl_uninstall=卸载选定的应用
+tt_ctl_download=Download selected application (.oar file) (zh_CN)
 
 # Quick-Help panel
 qh_hint_esc=取消选定的应用
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_TW.properties
index abefa09..25ffe10 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/App_zh_TW.properties
@@ -26,6 +26,7 @@
 tt_ctl_activate=啟動選定的應用程式
 tt_ctl_deactivate=停用選定的應用程式
 tt_ctl_uninstall=移除選定的應用程式
+tt_ctl_download=Download selected application (.oar file) (zh_TW)
 
 # Quick-Help panel
 qh_hint_esc=取消選定的應用程式
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
new file mode 100644
index 0000000..f09adb8
--- /dev/null
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_ko.properties
@@ -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.
+#
+#
+
+# Text that appears in the navigation panel
+nav_item_topo=토폴로지
+
+# Message when no devices are connected
+no_devices_are_connected=연결된 장치가 없습니다
+
+# Action Key Toolbar Tooltips ...
+tbtt_tog_instances=ONOS 인스턴스 패널 보이기/숨기기
+tbtt_tog_summary=ONOS 인스턴스 요약 패널 보이기/숨기기
+tbtt_tog_use_detail=세부 사항 활성화 / 비활성화 패널 토글
+tbtt_tog_host=호스트 보이기/숨기기
+tbtt_tog_offline=오프라인 장치 보이기/숨기기
+tbtt_tog_porthi=포트 강조하기 토글
+tbtt_bad_links=잘못된 링크 보이기/숨기기
+tbtt_tog_map=배경지도 보이기/숨기기
+tbtt_sel_map=배경지도 선택하기
+tbtt_tog_sprite=스프라이트 레이어 토글
+tbtt_reset_loc=노드 위치 초기화
+tbtt_tog_oblique=다계층 뷰 보이기/숨기기 (실험기능)
+tbtt_cyc_layers=루프 노드 레이어
+tbtt_cyc_dev_labs=루프 장치 레이어
+tbtt_cyc_host_labs=루프 호스트 레이어
+tbtt_unpin_node=고정 노드 해제하기
+tbtt_reset_zoom=팬 / 줌 초기화
+tbtt_tog_toolbar=툴바 보이기/숨기기
+tbtt_eq_master=마스터쉽 역할 재분배
+
+# Quick Help Gestures
+qh_gest_click=아이템 선택하기 및 세부 사항 보이기
+qh_gest_shift_click=선택 상태 토글
+qh_gest_drag=장치 / 호스트 위치 바꾸기
+qh_gest_cmd_scroll=줌 확대 / 축
+qh_gest_cmd_drag=팬
+
+# Flash Messages
+fl_background_map=배경지도
+fl_sprite_layer=스프라이트 레이어
+fl_pan_zoom_reset=팬 및 줌 초기화
+fl_eq_masters=마스터 역할 재분배
+
+fl_device_labels_hide=장치 라벨 숨기기
+fl_device_labels_show_friendly=익숙한 장치 라벨 표시
+fl_device_labels_show_id=장치 ID 라벨 표시
+fl_host_labels_show_friendly=익숙한 호스트 라벨 표시
+fl_host_labels_show_ip=호스트 IP 주소 표시
+fl_host_labels_show_mac=호스트 MAC 주소 표시
+
+fl_offline_devices=오프라인 장치
+fl_bad_links=잘못된 링크
+fl_reset_node_locations=노드 위치 초기화
+
+fl_layer_all=모든 레이어 표시
+fl_layer_pkt=패킷 레이어 표시
+fl_layer_opt=광학 레이어 표시
+
+fl_panel_instances=인스턴스 패널
+fl_panel_summary=요약 패널
+fl_panel_details=세부 사항 패널
+
+fl_port_highlighting=포트 강조
+
+fl_oblique_view=다계층 뷰
+fl_normal_view=일반 뷰
+
+fl_monitoring_canceled=모니터링이 취소됨
+fl_selecting_intent=인텐트 선택
+
+# Core Overlays
+ov_tt_protected_intents=보호된 인텐트 오버레이
+ov_tt_traffic=트래픽 오버레이
+ov_tt_none=오버레이 없음
+
+# Traffic Overlay
+tr_btn_create_h2h_flow=호스트 대 호스트 플로우 생성
+tr_btn_create_msrc_flow=다중 소스 플로우 생성
+tr_btn_show_device_flows=장치 플로우 표시
+tr_btn_show_related_traffic=관련 트래픽 표시
+tr_btn_cancel_monitoring=트래픽 모니터링 취소
+tr_btn_monitor_all=모든 트래픽 모니터링
+tr_btn_show_dev_link_flows=장치 링크 플로우 표시
+tr_btn_show_all_rel_intents=모든 관련 인텐트 표시
+tr_btn_show_prev_rel_intent=이전 관련 인텐트 표시
+tr_btn_show_next_rel_intent=다음 관련 인텐트 표시
+tr_btn_monitor_sel_intent=선택된 인텐트의 트래픽 모니터링
+tr_fl_fstats_bytes=플로우 통계 (바이트)
+tr_fl_pstats_bits=포트 통계 (비트 / 초)
+tr_fl_pstats_pkts=포트 통계 (패킷 / 초)
+tr_fl_dev_flows=장치 플로우
+tr_fl_rel_paths=관련 패스
+tr_fl_prev_rel_int=이전 관련 인텐트
+tr_fl_next_rel_int=다음 관련 인텐트
+tr_fl_traf_on_path=선택된 패스의 트래픽
+tr_fl_h2h_flow_added=호스트 대 호스트 플로우가 추가됨
+tr_fl_multisrc_flow=다중 소스 플로우
+
+# Button tooltips
+btn_show_view_device=장치 뷰 보이기
+btn_show_view_flow=이 장치의 플로우 뷰 보이기
+btn_show_view_port=이 장치의 포트 뷰 보이기
+btn_show_view_group=이 장치의 그룹 뷰 보이기
+btn_show_view_meter=이 장치의 미터 뷰 보이기
+
+# Panel Titles
+title_select_map=지도 선택
+title_panel_summary=ONOS 요약
+title_selected_items=선택된 아이템
+title_edge_link=말단(엣지) 링크
+title_infra_link=인프라 링크
+
+# Custom Panel Labels / Values
+lp_label_friendly=friendly
+
+lp_label_a_type=A 타입
+lp_label_a_id=A 아이디
+lp_label_a_friendly=A friendly
+lp_label_a_port=A 포트
+
+lp_label_b_type=B 타입
+lp_label_b_id=B 아이디
+lp_label_b_friendly=B friendly
+lp_label_b_port=B 포트
+
+lp_label_a2b=A에서 B
+lp_label_b2a=B에서 A
+
+lp_value_no_link=[링크 없음]
+
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index 9eb8ba8..4d61f81 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -23,7 +23,7 @@
 }
 
 #ov-app div.ctrl-btns {
-    width: 250px;
+    width: 290px;
 }
 
 /* -- Drag-n-Drop oar file upload -- */
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 6eaa126..e371248 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -38,6 +38,12 @@
                  tooltip tt-msg="uninstallTip"
                  ng-class="{active: ctrlBtnState.selection}">
             </div>
+            <!-- FIXME: create proper download icon -->
+            <div icon icon-size="42" icon-id="downArrow"
+                 ng-click="downloadApp()"
+                 tooltip tt-msg="downloadTip"
+                 ng-class="{active: ctrlBtnState.selection}">
+            </div>
         </div>
     </div>
 
diff --git a/web/gui/src/main/webapp/app/view/app/app.js b/web/gui/src/main/webapp/app/view/app/app.js
index a01dd2e..5a2272e 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -45,8 +45,9 @@
         detailsResp = 'appDetailsResponse',
         fileUploadUrl = 'applications/upload',
         activateOption = '?activate=true',
-        iconUrlPrefix = 'rs/applications/',
+        appUrlPrefix = 'rs/applications/',
         iconUrlSuffix = '/icon',
+        downloadSuffix = '/download',
         dialogId = 'app-dialog',
         dialogOpts = {
             edge: 'right',
@@ -167,7 +168,7 @@
     }
 
     function addIcon(elem, value) {
-        elem.append('img').attr('src', iconUrlPrefix + value + iconUrlSuffix);
+        elem.append('img').attr('src', appUrlPrefix + value + iconUrlSuffix);
     }
 
     function populateTop(details) {
@@ -250,6 +251,7 @@
         $scope.activateTip = lion('tt_ctl_activate');
         $scope.deactivateTip = lion('tt_ctl_deactivate');
         $scope.uninstallTip = lion('tt_ctl_uninstall');
+        $scope.downloadTip = lion('tt_ctl_download');
 
 
         var handlers = {};
@@ -359,6 +361,12 @@
             }
         };
 
+        $scope.downloadApp = function () {
+            if ($scope.ctrlBtnState.selection) {
+                window.location = appUrlPrefix + $scope.selId + downloadSuffix;
+            }
+        };
+
         $scope.$on('FileChanged', function () {
             var formData = new FormData(),
                 url;
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 75bcee3..4fbb0df 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -40,6 +40,8 @@
         ctnrPdg = 24,
         scrollSize = 17,
         portsTblPdg = 50,
+        defaultLabelWidth = 110,
+        defaultValueWidth  = 80,
 
         pName = 'device-details-panel',
         detailsReq = 'deviceDetailsRequest',
@@ -49,6 +51,7 @@
         friendlyProps = [
             'URI', 'Type', 'Master ID', 'Chassis ID',
             'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #',
+            'Pipeconf',
         ],
         portCols = [
             'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name',
@@ -150,11 +153,11 @@
     function addProp(tbody, index, value) {
         var tr = tbody.append('tr');
 
-        function addCell(cls, txt) {
-            tr.append('td').attr('class', cls).text(txt);
+        function addCell(cls, txt, width) {
+            tr.append('td').attr('class', cls).attr('width', width).text(txt);
         }
-        addCell('label', friendlyProps[index] + ' :');
-        addCell('value', value);
+        addCell('label', friendlyProps[index] + ' :', defaultLabelWidth);
+        addCell('value', value, defaultValueWidth);
     }
 
     function populateTop(tblDiv, details) {
@@ -173,11 +176,12 @@
         addProp(leftTbl, 1, device.type);
         addProp(leftTbl, 2, details['masterid']);
         addProp(leftTbl, 3, details['chassid']);
-        addProp(rightTbl, 4, device.mfr);
+        addProp(leftTbl, 4, device.mfr);
         addProp(rightTbl, 5, device.hw);
         addProp(rightTbl, 6, device.sw);
         addProp(rightTbl, 7, details['protocol']);
         addProp(rightTbl, 8, device.serial);
+        addProp(rightTbl, 9, details['pipeconf']);
     }
 
     function addPortRow(tbody, port) {