Added support for cdn download url for mgmt API and tests

Added support for cdn download url for mgmt API and tests
This commit is contained in:
Stanislav Trailov
2023-06-22 15:50:13 +03:00
committed by GitHub
15 changed files with 146 additions and 23 deletions

View File

@@ -21,4 +21,11 @@ hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.proto
hawkbit.artifact.url.protocols.md5sum-http.ip=${hawkbit.artifact.url.protocols.download-http.ip}
hawkbit.artifact.url.protocols.md5sum-http.port=${hawkbit.artifact.url.protocols.download-http.port}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
hawkbit.artifact.url.protocols.download-cdn-http.rel=download
hawkbit.artifact.url.protocols.download-cdn-http.hostname=localhost
hawkbit.artifact.url.protocols.download-cdn-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-cdn-http.protocol=http
hawkbit.artifact.url.protocols.download-cdn-http.port=8080
hawkbit.artifact.url.protocols.download-cdn-http.supports=MGMT
hawkbit.artifact.url.protocols.download-cdn-http.ref={protocol}://{hostnameRequest}:{portRequest}/rest/v1/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}

View File

@@ -22,5 +22,10 @@ public enum ApiType {
/**
* Support for Direct Device Integration API.
*/
DDI
DDI,
/**
* Support for Management API.
*/
MGMT
}

View File

@@ -54,7 +54,7 @@ public class ArtifactUrlHandlerProperties {
* artifactFileName,artifactSHA1,
* artifactIdBase62,artifactId,tenant,softwareModuleId,
* softwareModuleIdBase62.
*
*
* The update server itself supports
*/
private String ref = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
@@ -84,7 +84,7 @@ public class ArtifactUrlHandlerProperties {
/**
* Support for the following hawkBit API.
*/
private List<ApiType> supports = Arrays.asList(ApiType.DDI, ApiType.DMF);
private List<ApiType> supports = Arrays.asList(ApiType.DDI, ApiType.DMF, ApiType.MGMT);
public boolean isEnabled() {
return enabled;

View File

@@ -104,7 +104,9 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
StringUtils.isEmpty(entry.getValue()) ? "" : (":" + entry.getValue()));
} else {
urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
if(entry.getValue() != null) {
urlPattern = urlPattern.replace("{" + entry.getKey() + "}", entry.getValue());
}
}
}
return urlPattern;
@@ -135,7 +137,9 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.getControllerId());
replaceMap.put(TARGET_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTargetId()));
replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
if(placeholder.getTargetId() != null) {
replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
}
replaceMap.put(ARTIFACT_ID_BASE62_PLACEHOLDER,
Base62Util.fromBase10(placeholder.getSoftwareData().getArtifactId()));
replaceMap.put(ARTIFACT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getSoftwareData().getArtifactId()));

View File

@@ -34,7 +34,7 @@ public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest
private TargetFilterQuery filter2;
@BeforeEach
public void setupBeforeTest() throws InterruptedException {
public void setupBeforeTest() {
final String filterName1 = "filter_a";
final String filterName2 = "filter_b";
final String filterName3 = "filter_c";

View File

@@ -51,6 +51,13 @@ hawkbit.artifact.url.protocols.md5sum-http.ip=${hawkbit.artifact.url.protocols.d
hawkbit.artifact.url.protocols.md5sum-http.port=${hawkbit.artifact.url.protocols.download-http.port}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
hawkbit.artifact.url.protocols.download-cdn-http.rel=download
hawkbit.artifact.url.protocols.download-cdn-http.hostname=localhost
hawkbit.artifact.url.protocols.download-cdn-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-cdn-http.protocol=http
hawkbit.artifact.url.protocols.download-cdn-http.port=8080
hawkbit.artifact.url.protocols.download-cdn-http.supports=MGMT
hawkbit.artifact.url.protocols.download-cdn-http.ref={protocol}://{hostnameRequest}:{portRequest}/rest/v1/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
## Download URL Generation - END
# Quota - START

View File

@@ -271,6 +271,12 @@ public final class MgmtRestConstants {
*/
public static final String AUTH_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/userinfo";
/**
* Request parameter if the artifact url handler should be used
*/
public static final String REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER = "useartifacturlhandler";
// constant class, private constructor.
private MgmtRestConstants() {

View File

@@ -52,7 +52,7 @@ public interface MgmtSoftwareModuleRestApi {
* checksum for uploaded content check
* @param sha256Sum
* checksum for uploaded content check
*
*
* @return In case all sets could successful be created the ResponseEntity
* with status code 201 - Created but without ResponseBody. In any
* failure the JsonResponseExceptionHandler is handling the
@@ -98,9 +98,10 @@ public interface MgmtSoftwareModuleRestApi {
@GetMapping(value = MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING
+ "/{softwareModuleId}/artifacts/{artifactId}", produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId);
@ResponseBody ResponseEntity<MgmtArtifact> getArtifact(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER, required = false) final Boolean useArtifactUrlHandler);
/**
* Handles the DELETE request for a single SoftwareModule.

View File

@@ -16,6 +16,10 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifactHash;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
@@ -25,12 +29,14 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.rest.data.ResponseList;
import org.springframework.hateoas.Link;
/**
* A mapper which maps repository model to RESTful model representation and
@@ -143,7 +149,7 @@ public final class MgmtSoftwareModuleMapper {
MgmtRestModelMapper.mapBaseToBase(artifactRest, artifact);
artifactRest.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class)
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId())).withSelfRel().expand());
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null)).withSelfRel().expand());
return artifactRest;
}
@@ -155,6 +161,17 @@ public final class MgmtSoftwareModuleMapper {
.expand());
}
static void addLinks(final Artifact artifact, final MgmtArtifact response,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
final List<ArtifactUrl> urls = artifactUrlHandler.getUrls(
new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), null, null,
new URLPlaceholder.SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(),
artifact.getId(), artifact.getSha1Hash())), ApiType.MGMT, null);
urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand()));
}
static List<MgmtArtifact> artifactsToResponse(final Collection<Artifact> artifacts) {
if (artifacts == null) {
return Collections.emptyList();

View File

@@ -15,6 +15,9 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.validation.ValidationException;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
@@ -29,6 +32,7 @@ import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
@@ -51,8 +55,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.ValidationException;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
@@ -68,13 +70,21 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
private final ArtifactUrlHandler artifactUrlHandler;
private final SystemManagement systemManagement;
private final EntityFactory entityFactory;
MgmtSoftwareModuleResource(final ArtifactManagement artifactManagement, final SoftwareModuleManagement softwareModuleManagement,
final SoftwareModuleTypeManagement softwareModuleTypeManagement, final EntityFactory entityFactory) {
final SoftwareModuleTypeManagement softwareModuleTypeManagement,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement,
final EntityFactory entityFactory) {
this.artifactManagement = artifactManagement;
this.softwareModuleManagement = softwareModuleManagement;
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
this.artifactUrlHandler = artifactUrlHandler;
this.systemManagement = systemManagement;
this.entityFactory = entityFactory;
}
@@ -125,15 +135,21 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) {
@PathVariable("artifactId") final Long artifactId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER, required = false) final Boolean useArtifactUrlHandler) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
final MgmtArtifact reponse = MgmtSoftwareModuleMapper.toResponse(module.getArtifact(artifactId).get());
final MgmtArtifact response = MgmtSoftwareModuleMapper.toResponse(module.getArtifact(artifactId).get());
if (!module.isDeleted()) {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), reponse);
if(useArtifactUrlHandler != null && useArtifactUrlHandler) {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response, artifactUrlHandler,
systemManagement);
} else {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response);
}
}
return ResponseEntity.ok(reponse);
return ResponseEntity.ok(response);
}
@Override
@@ -192,7 +208,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
LOG.debug("creating {} softwareModules", softwareModules.size());
for (MgmtSoftwareModuleRequestBodyPost sm : softwareModules) {
for (final MgmtSoftwareModuleRequestBodyPost sm : softwareModules) {
final Optional<SoftwareModuleType> opt = softwareModuleTypeManagement.getByKey(sm.getType());
opt.ifPresent(smType -> {
if (smType.isDeleted()) {

View File

@@ -592,6 +592,37 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
"http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact.getId())));
}
@Test
@Description("Verifies the listing of one defined artifact assigned to a given software module. That includes the artifact metadata and cdn download links.")
void getArtifactWithCdnDownloadUrl() throws Exception {
// prepare data for test
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
// perform test
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}?useartifacturlhandler=true", sm.getId(), artifact.getId()).accept(
MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.id", equalTo(artifact.getId().intValue())))
.andExpect(jsonPath("$.size", equalTo(random.length)))
.andExpect(jsonPath("$.hashes.md5", equalTo(artifact.getMd5Hash())))
.andExpect(jsonPath("$.hashes.sha1", equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$.hashes.sha256", equalTo(artifact.getSha256Hash())))
.andExpect(jsonPath("$.providedFilename", equalTo("file1")))
.andExpect(jsonPath("$._links.download.href",
equalTo("http://localhost:8080/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/"
+ artifact.getFilename())))
.andExpect(jsonPath("$._links.self.href", equalTo(
"http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact.getId())));
}
@Test
@Description("Verifies the listing of an artifact that belongs to a soft deleted software module.")
void getArtifactSoftDeleted() throws Exception {

View File

@@ -417,6 +417,10 @@ include::{snippets}/softwaremodules/get-artifact/http-request.adoc[]
include::{snippets}/softwaremodules/get-artifact/path-parameters.adoc[]
==== Request query parameter
include::{snippets}/softwaremodules/get-artifact-with-parameters/request-parameters.adoc[]
=== Response (Status 200)
==== Response fields

View File

@@ -55,10 +55,9 @@ public final class MgmtApiModelProperties {
public static final String ARTIFACT_HASHES_SHA1 = "SHA1 hash of the artifact.";
public static final String ARTIFACT_HASHES_MD5 = "MD5 hash of the artifact.";
public static final String ARTIFACT_HASHES_SHA256 = "SHA256 hash of the artifact.";
public static final String ARTIFACT_DOWNLOAD_LINK = "Download link of the artifact.";
public static final String ARTIFACT_DOWNLOAD_LINK = "Download link of the artifact based on the chosen download url type parameter.";
public static final String ARTIFACT_LIST = "List of artifacts of given software module.";
public static final String ARTIFACT_DOWNLOAD_USE_URL_HANDLER = "Indicates if the artifact URL handler should be used e.g. for external links.";
// Distribution Set
public static final String DS_OS = "Operating system or firmware software module - DEPRECATED (use modules).";

View File

@@ -391,6 +391,27 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
.description(MgmtApiModelProperties.ARTIFACT_DOWNLOAD_LINK))));
}
@Test
@Description("Handles the GET request of retrieving a single Artifact meta data request. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public void getArtifactWithParameters() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(
get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts/{artifactId}",
sm.getId(), artifact.getId()).param("useartifacturlhandler", "true"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andDo(this.document.document(
requestParameters(
parameterWithName("useartifacturlhandler").description(MgmtApiModelProperties.ARTIFACT_DOWNLOAD_USE_URL_HANDLER))));
}
@Test
@Description("Handles the GET request for downloading an artifact. Required Permission: "
+ SpPermission.READ_REPOSITORY)

View File

@@ -19,6 +19,11 @@ hawkbit.artifact.url.protocols.download-http.protocol=https
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.protocols.download-http.hostname=hawkbit.eclipse.org
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.download-cdn-http.rel=download
hawkbit.artifact.url.protocols.download-cdn-http.protocol=https
hawkbit.artifact.url.protocols.download-cdn-http.supports=MGMT
hawkbit.artifact.url.protocols.download-cdn-http.hostname=hawkbit.eclipse.org
hawkbit.artifact.url.protocols.download-cdn-http.ref={protocol}://{hostnameRequest}:{portRequest}/rest/v1/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI