Remove module for legacy download API (never used by hawkBit). (#500)

* Remove module for legacy download API (never used by hawkBit).

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Add stream aware error controller.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Fixed bean definition.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2017-05-03 09:21:14 +02:00
committed by GitHub
parent 525669724f
commit 1ee3d0c850
12 changed files with 97 additions and 560 deletions

View File

@@ -18,7 +18,6 @@ import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlRestConstants;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifactHash;
import org.eclipse.hawkbit.ddi.json.model.DdiChunk;
@@ -160,7 +159,7 @@ public final class DataConversionHelper {
final StringBuilder header = new StringBuilder();
header.append("attachment;filename=");
header.append(fileName);
header.append(DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX);
header.append(DdiRestConstants.ARTIFACT_MD5_DWNL_SUFFIX);
response.setContentLength(content.length);
response.setHeader("Content-Disposition", header.toString());

View File

@@ -1,156 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.rest.resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlArtifactStoreControllerRestApi;
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder;
import org.eclipse.hawkbit.rest.util.RestResourceConversionHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
/**
* The {@link DdiArtifactStoreController} of the HawkBit server controller API
* that is queried by the HawkBit target in order to download artifacts
* independent of their own individual resource. This is offered in addition to
* the {@link DdiRootController#downloadArtifact(String, Long, String)} for
* legacy controllers that can not be fed with a download URI at runtime.
*/
@RestController
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerRestApi {
private static final Logger LOG = LoggerFactory.getLogger(DdiArtifactStoreController.class);
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private RequestResponseContextHolder requestResponseContextHolder;
@Autowired
private EntityFactory entityFactory;
@Override
public ResponseEntity<InputStream> downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName) {
final Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
final Optional<Artifact> foundArtifacts = artifactManagement.findArtifactByFilename(fileName);
if (!foundArtifacts.isPresent()) {
LOG.warn("Software artifact with name {} could not be found.", fileName);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
ResponseEntity<InputStream> result;
final Artifact artifact = foundArtifacts.get();
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} else {
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
// we set a download status only if we are aware of the
// targetid, i.e. authenticated and not anonymous
if (principal instanceof UserPrincipal && ((UserPrincipal) principal).getUsername() != null
&& !"anonymous".equals(((UserPrincipal) principal).getUsername())) {
final ActionStatus actionStatus = checkAndReportDownloadByTarget(
requestResponseContextHolder.getHttpServletRequest(), ((UserPrincipal) principal).getUsername(),
artifact);
result = RestResourceConversionHelper.writeFileResponse(artifact,
requestResponseContextHolder.getHttpServletResponse(),
requestResponseContextHolder.getHttpServletRequest(), file, controllerManagement,
actionStatus.getId());
} else {
result = RestResourceConversionHelper.writeFileResponse(artifact,
requestResponseContextHolder.getHttpServletResponse(),
requestResponseContextHolder.getHttpServletRequest(), file);
}
}
return result;
}
@Override
public ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName) {
final Optional<Artifact> foundArtifacts = artifactManagement.findArtifactByFilename(fileName);
if (!foundArtifacts.isPresent()) {
LOG.warn("Software artifact with name {} could not be found.", fileName);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
try {
DataConversionHelper.writeMD5FileResponse(fileName, requestResponseContextHolder.getHttpServletResponse(),
foundArtifacts.get());
} catch (final IOException e) {
LOG.error("Failed to stream MD5 File", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(HttpStatus.OK);
}
private ActionStatus checkAndReportDownloadByTarget(final HttpServletRequest request, final String controllerId,
final Artifact artifact) {
final Target target = controllerManagement.findByControllerId(controllerId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
final Action action = controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(),
artifact.getSoftwareModule().getId())
.orElseThrow(() -> new SoftwareModuleNotAssignedToTargetException(artifact.getSoftwareModule().getId(),
target.getControllerId()));
final String range = request.getHeader("Range");
String message;
if (range != null) {
message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads range " + range + " of: "
+ request.getRequestURI();
} else {
message = RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads: " + request.getRequestURI();
}
return controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(action.getId()).status(Status.DOWNLOAD).message(message));
}
}

View File

@@ -31,8 +31,6 @@ import java.util.TimeZone;
import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.ddi.rest.resource.DdiArtifactDownloadTest.DownloadTestConfiguration;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
@@ -42,9 +40,8 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
@@ -66,8 +63,8 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
private static final int ARTIFACT_SIZE = 5 * 1024 * 1024;
private volatile static int downLoadProgress = 0;
private volatile static long shippedBytes = 0;
private static volatile int downLoadProgress = 0;
private static volatile long shippedBytes = 0;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
@@ -93,149 +90,71 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
ds.findFirstModuleByType(osType).get().getId(), "file1", false);
// no artifact available
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/123455",
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/123455",
target.getControllerId(), getOsModule(ds))).andExpect(status().isNotFound());
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/123455.MD5SUM",
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/123455.MD5SUM",
target.getControllerId(), getOsModule(ds))).andExpect(status().isNotFound());
// SM does not exist
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/1234567890/artifacts/{filename}",
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/1234567890/artifacts/{filename}",
target.getControllerId(), artifact.getFilename())).andExpect(status().isNotFound());
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/1234567890/artifacts/{filename}.MD5SUM",
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/1234567890/artifacts/{filename}.MD5SUM",
target.getControllerId(), artifact.getFilename())).andExpect(status().isNotFound());
// test now consistent data to test allowed methods
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())
.header(HttpHeaders.IF_MATCH, artifact.getSha1Hash()))
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isOk());
// test failed If-match
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())
.header("If-Match", "fsjkhgjfdhg"))
.andExpect(status().isPreconditionFailed());
// test invalid range
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())
.header("Range", "bytes=1-10,hdsfjksdh"))
.andExpect(header().string("Content-Range", "bytes */" + 5 * 1024))
.andExpect(status().isRequestedRangeNotSatisfiable());
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename())
.header("Range", "bytes=100-10"))
.andExpect(header().string("Content-Range", "bytes */" + 5 * 1024))
.andExpect(status().isRequestedRangeNotSatisfiable());
// not allowed methods
mvc.perform(put("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(put("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(delete("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
mvc.perform(post("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
mvc.perform(post("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
mvc.perform(put("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
mvc.perform(put("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
mvc.perform(delete("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
mvc.perform(post("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
mvc.perform(post("/{tenant}/controller/v1/{controllerId}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
.andExpect(status().isMethodNotAllowed());
}
@Test
@WithUser(principal = TestdataFactory.DEFAULT_CONTROLLER_ID, authorities = "ROLE_CONTROLLER", allSpPermissions = true)
@Description("Tests non allowed requests on the artifact ressource, e.g. invalid URI, wrong if-match, wrong command.")
public void invalidRequestsOnArtifactResourceByName() throws Exception {
// create target
final Target target = testdataFactory.createTarget();
final List<Target> targets = Lists.newArrayList(target);
// create ds
final DistributionSet ds = testdataFactory.createDistributionSet("");
assignDistributionSet(ds, targets);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
// Binary
// no artifact available
mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1")).andExpect(status().isNotFound());
// no artifact available
mvc.perform(get("/controller/artifacts/v1/filename/{filename}.MD5SUM", "file1"))
.andExpect(status().isNotFound());
// test now consistent data to test allowed methods
final Artifact artifact = artifactManagement.createArtifact(new ByteArrayInputStream(random), getOsModule(ds),
"file1", false);
mvc.perform(
get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1")
.header(HttpHeaders.IF_MATCH, artifact.getSha1Hash()))
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}.MD5SUM", tenantAware.getCurrentTenant(),
"file1")).andExpect(status().isOk());
// test failed If-match
mvc.perform(
get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1")
.header("If-Match", "fsjkhgjfdhg"))
.andExpect(status().isPreconditionFailed());
// test invalid range
mvc.perform(
get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1")
.header("Range", "bytes=1-10,hdsfjksdh"))
.andExpect(header().string("Content-Range", "bytes */" + 5 * 1024))
.andExpect(status().isRequestedRangeNotSatisfiable());
mvc.perform(
get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1")
.header("Range", "bytes=100-10"))
.andExpect(header().string("Content-Range", "bytes */" + 5 * 1024))
.andExpect(status().isRequestedRangeNotSatisfiable());
// not allowed methods
mvc.perform(
put("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1"))
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1")).andExpect(status().isMethodNotAllowed());
mvc.perform(
post("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(), "file1"))
.andExpect(status().isMethodNotAllowed());
// not allowed methods
mvc.perform(put("/{tenant}/controller/artifacts/v1/filename/{filename}.MD5SUM", tenantAware.getCurrentTenant(),
"file1")).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/artifacts/v1/filename/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), "file1")).andExpect(status().isMethodNotAllowed());
mvc.perform(post("/{tenant}/controller/artifacts/v1/filename/{filename}.MD5SUM", tenantAware.getCurrentTenant(),
"file1")).andExpect(status().isMethodNotAllowed());
}
@Test
@WithUser(principal = "4712", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
@Description("Tests valid downloads through the artifact resource by identifying the artifact not by ID but file name.")
@@ -257,14 +176,14 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
ds.findFirstModuleByType(osType).get().getId(), "file1", false);
// download fails as artifact is not yet assigned
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
target.getControllerId(), getOsModule(ds), artifact.getFilename())).andExpect(status().isNotFound());
// now assign and download successful
assignDistributionSet(ds, targets);
final MvcResult result = mvc
.perform(
get("/{tenant}/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds),
artifact.getFilename()))
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
@@ -298,7 +217,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// download
final MvcResult result = mvc
.perform(
get("/{tenant}/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds),
artifact.getFilename()))
.andExpect(status().isOk()).andExpect(header().string("Content-Disposition",
@@ -309,95 +228,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
new String(artifact.getMd5Hash() + " " + artifact.getFilename()).getBytes(Charsets.US_ASCII));
}
@Test
@WithUser(authorities = "ROLE_CONTROLLER_ANONYMOUS", allSpPermissions = true)
@Description("Ensures that even an authenticated controller is not permitted to download if "
+ "anonymous as authorization is notpossible, e.g. chekc if the controller has the artifact assigned.")
public void downloadArtifactByNameFailsIfNotAuthenticated() throws Exception {
downLoadProgress = 1;
shippedBytes = 0;
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
// create target
final Target target = testdataFactory.createTarget();
final List<Target> targets = Lists.newArrayList(target);
// create ds
final DistributionSet ds = testdataFactory.createDistributionSet("");
// create artifact
final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE);
artifactManagement.createArtifact(new ByteArrayInputStream(random), getOsModule(ds), "file1.tar.bz2", false);
// download fails as artifact is not yet assigned to target
assignDistributionSet(ds, targets);
mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1.tar.bz2"))
.andExpect(status().isNotFound());
assertThat(downLoadProgress).isEqualTo(1);
assertThat(shippedBytes).isEqualTo(0L);
}
@Test
@WithUser(principal = TestdataFactory.DEFAULT_CONTROLLER_ID, authorities = "ROLE_CONTROLLER", allSpPermissions = true)
@Description("Ensures that an authenticated and named controller is permitted to download.")
public void downloadArtifactByNameByNamedController() throws Exception {
downLoadProgress = 1;
shippedBytes = 0;
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
// create target
final Target target = testdataFactory.createTarget();
final List<Target> targets = Lists.newArrayList(target);
// create ds
final DistributionSet ds = testdataFactory.createDistributionSet("");
// create artifact
final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE);
final Artifact artifact = artifactManagement.createArtifact(new ByteArrayInputStream(random), getOsModule(ds),
"file1", false);
// download fails as artifact is not yet assigned to target
mvc.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1.tar.bz2")).andExpect(status().isNotFound());
// now assign and download successful
assignDistributionSet(ds, targets);
final MvcResult result = mvc
.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1"))
.andExpect(status().isOk()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
.andExpect(header().string("Last-Modified", dateFormat.format(new Date(artifact.getCreatedAt()))))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
assertTrue("The same file that was uploaded is expected when downloaded",
Arrays.equals(result.getResponse().getContentAsByteArray(), random));
// one (update) action
assertThat(deploymentManagement.countActionsByTarget(target.getControllerId())).isEqualTo(1);
final Action action = deploymentManagement.findActionsByTarget(target.getControllerId(), pageReq).getContent()
.get(0);
// one status - download
assertThat(action.getActionStatus()).hasSize(2);
assertThat(deploymentManagement
.findActionStatusByAction(new PageRequest(0, 400, Direction.DESC, "id"), action.getId()).getContent()
.get(0).getStatus()).isEqualTo(Status.DOWNLOAD);
// download complete
assertThat(downLoadProgress).isEqualTo(10);
assertThat(shippedBytes).isEqualTo(ARTIFACT_SIZE);
}
@Test
@WithUser(principal = TestdataFactory.DEFAULT_CONTROLLER_ID, authorities = "ROLE_CONTROLLER", allSpPermissions = true)
@Description("Test various HTTP range requests for artifact download, e.g. chunk download or download resume.")
public void rangeDownloadArtifactByName() throws Exception {
public void rangeDownloadArtifact() throws Exception {
// create target
final Target target = testdataFactory.createTarget();
final List<Target> targets = Lists.newArrayList(target);
@@ -425,8 +259,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
final String rangeString = "" + i * range + "-" + ((i + 1) * range - 1);
final MvcResult result = mvc
.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}",
tenantAware.getCurrentTenant(), "file1").header("Range", "bytes=" + rangeString))
.perform(
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
.header("Range", "bytes=" + rangeString))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
@@ -442,8 +278,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// return last 1000 Bytes
MvcResult result = mvc
.perform(get("/${tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1").header("Range", "bytes=-1000"))
.perform(
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
.header("Range", "bytes=-1000"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
@@ -458,8 +296,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// skip first 1000 Bytes and return the rest
result = mvc
.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1").header("Range", "bytes=1000-"))
.perform(
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
.header("Range", "bytes=1000-"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
@@ -474,8 +314,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// multipart download - first 20 bytes in 2 parts
result = mvc
.perform(get("/{tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"file1").header("Range", "bytes=0-9,10-19"))
.perform(
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
.header("Range", "bytes=0-9,10-19"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType("multipart/byteranges; boundary=THIS_STRING_SEPARATES_MULTIPART"))
.andExpect(header().string("Accept-Ranges", "bytes"))
@@ -497,54 +339,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
}
@Test
@Description("Ensures that the download fails if te controller is not authenticated.")
public void faildDownloadArtifactByNameIfAuthenticationMissing() throws Exception {
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
// create target
final Target target = testdataFactory.createTarget();
final List<Target> targets = Lists.newArrayList(target);
// create ds
final DistributionSet ds = testdataFactory.createDistributionSet("");
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final Artifact artifact = artifactManagement.createArtifact(new ByteArrayInputStream(random), getOsModule(ds),
"file1.tar.bz2", false);
// download fails as artifact is not yet assigned to target
mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1.tar.bz2"))
.andExpect(status().isNotFound());
}
@Test
@Description("Downloads an MD5SUM file by the related artifacts filename.")
public void downloadMd5sumFileByName() throws Exception {
// create target
final Target target = testdataFactory.createTarget();
// create ds
final DistributionSet ds = testdataFactory.createDistributionSet("");
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final Artifact artifact = artifactManagement.createArtifact(new ByteArrayInputStream(random), getOsModule(ds),
"file1.tar.bz2", false);
// download
final MvcResult result = mvc
.perform(get("/{tenant}/controller/artifacts/v1/filename/file1.tar.bz2.MD5SUM",
tenantAware.getCurrentTenant()))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", "attachment;filename=file1.tar.bz2.MD5SUM"))
.andReturn();
assertThat(result.getResponse().getContentAsByteArray())
.isEqualTo(new String(artifact.getMd5Hash() + " file1.tar.bz2").getBytes(Charsets.US_ASCII));
}
@Configuration
public static class DownloadTestConfiguration {
@Bean
@@ -557,7 +352,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
private static class Listener {
@EventListener(classes = DownloadProgressEvent.class)
public void listen(final DownloadProgressEvent event) {
public static void listen(final DownloadProgressEvent event) {
downLoadProgress++;
shippedBytes += event.getShippedBytesSinceLast();