ONOS-6980 Adding support for download of application bits.

Change-Id: I742950690b50038cac0bb2ad2da4eaac5781da85
This commit is contained in:
Thomas Vachuska 2017-08-31 15:20:17 -07:00 committed by Simon Hunt
parent ac5f0fbf59
commit 08b4decc39
15 changed files with 116 additions and 14 deletions

View File

@ -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 @@ public class ApplicationCommand extends AbstractShellCommand {
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 @@ public class ApplicationCommand extends AbstractShellCommand {
}
}
} 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 @@ public class ApplicationCommand extends AbstractShellCommand {
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);

View File

@ -28,7 +28,7 @@ import static org.onosproject.cli.app.ApplicationCommand.*;
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);
}
}

View File

@ -63,7 +63,7 @@ public class ApplicationNameCompleter extends AbstractCompleter {
// 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());

View File

@ -20,6 +20,7 @@ import org.onosproject.core.ApplicationId;
import org.onosproject.event.ListenerService;
import org.onosproject.security.Permission;
import java.io.InputStream;
import java.util.Set;
/**
@ -74,4 +75,14 @@ public interface ApplicationService
* @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;
}
}

View File

@ -105,4 +105,14 @@ public interface ApplicationStore extends Store<ApplicationEvent, ApplicationSto
*/
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;
}
}

View File

@ -199,13 +199,11 @@ public class ApplicationArchive
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 @@ public class ApplicationArchive
/**
* 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

View File

@ -179,6 +179,12 @@ public class ApplicationManager
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);

View File

@ -437,6 +437,11 @@ public class DistributedApplicationStore extends ApplicationArchive
}
}
@Override
public InputStream getApplicationArchive(ApplicationId appId) {
return getApplicationInputStream(appId.name());
}
private class AppActivator implements Consumer<Application> {
@Override
public void accept(Application app) {

View File

@ -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="~"/>

View File

@ -207,6 +207,23 @@ public class ApplicationsWebResource extends AbstractWebResource {
return response(appId);
}
/**
* 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.
*

View File

@ -58,6 +58,26 @@ public class ApplicationResource extends BaseResource {
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")

View File

@ -26,6 +26,7 @@ tt_ctl_upload=Upload an application (.oar file)
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

View File

@ -23,7 +23,7 @@
}
#ov-app div.ctrl-btns {
width: 250px;
width: 290px;
}
/* -- Drag-n-Drop oar file upload -- */

View File

@ -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>

View File

@ -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;