Initial check in accordance with Parallel IP

This commit is contained in:
Kai Zimmermann
2016-01-21 13:18:55 +01:00
parent c5e4f1139f
commit 7497ab61ed
763 changed files with 114508 additions and 0 deletions

164
hawkbit-rest-resource/pom.xml Executable file
View File

@@ -0,0 +1,164 @@
<!--
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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>hawkbit-rest-resource</artifactId>
<name>hawkBit :: REST Resources</name>
<dependencies>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-rest-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-core</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-http-security</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ru.yandex.qatools.allure</groupId>
<artifactId>allure-junit-adaptor</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,97 @@
/**
* 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.cache;
import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
/**
* An service which combines the functionality for functional use cases to write
* into the cache an notify the writing to the cache to the {@link EventBus}.
*
*
*
*
*/
@Service
public class CacheWriteNotify {
/**
*
*/
private static final int DOWNLOAD_PROGRESS_MAX = 100;
@Autowired
private CacheManager cacheManager;
@Autowired
private EventBus eventBus;
@Autowired
private TenantAware tenantAware;
/**
* writes the download progress in percentage into the cache
* {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the
* {@link EventBus} with a {@link DownloadProgressEvent}.
*
* @param statusId
* the ID of the {@link ActionStatus}
* @param progressPercent
* the progress in percentage which must be between 0-100
*/
public void downloadProgressPercent(final long statusId, final int progressPercent) {
final Cache cache = cacheManager.getCache(Action.class.getName());
final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId),
CacheKeys.DOWNLOAD_PROGRESS_PERCENT);
if (progressPercent < DOWNLOAD_PROGRESS_MAX) {
cache.put(cacheKey, progressPercent);
} else {
// in case we reached progress 100 delete the cache value again
// because otherwise he will
// keep there forever
cache.evict(cacheKey);
}
eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent));
}
/**
* @param cacheManager
* the cacheManager to set
*/
void setCacheManager(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* @param eventBus
* the eventBus to set
*/
void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* @param tenantAware
* the tenantAware to set
*/
void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
}

View File

@@ -0,0 +1,200 @@
/**
* 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.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
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.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* The {@link ArtifactStoreController} of the SP server controller API that is
* queried by the SP target in order to download artifacts independent of their
* own individual resource. This is offered in addition to the
* {@link RootController#downloadArtifact(String, Long, Long, javax.servlet.http.HttpServletResponse)}
* for legacy controllers that can not be fed with a download URI at runtime.
*
*
*
*
*
*/
@RestController
@Transactional
@RequestMapping(ControllerConstants.ARTIFACTS_V1_REQUEST_MAPPING)
public class ArtifactStoreController implements EnvironmentAware {
private static final Logger LOG = LoggerFactory.getLogger(ArtifactStoreController.class);
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private CacheWriteNotify cacheWriteNotify;
private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server.";
private RelaxedPropertyResolver environment;
@Override
public void setEnvironment(final Environment environment) {
this.environment = new RelaxedPropertyResolver(environment, SP_SERVER_CONFIG_PREFIX);
}
/**
* Handles GET {@link Artifact} download request. This could be full or
* partial download request.
*
* @param fileName
* to search for
* @param response
* to write to
* @param request
* from the client
* @param targetid
* of authenticated target
*
* @return response of the servlet which in case of success is status code
* {@link HttpStatus#OK} or in case of partial download
* {@link HttpStatus#PARTIAL_CONTENT}.
*/
@RequestMapping(method = RequestMethod.GET, value = ControllerConstants.ARTIFACT_DOWNLOAD_BY_FILENAME
+ "/{fileName}")
@ResponseBody
public ResponseEntity<Void> downloadArtifactByFilename(@PathVariable final String fileName,
final HttpServletResponse response, final HttpServletRequest request,
@AuthenticationPrincipal final String targetid) {
ResponseEntity<Void> result = null;
final List<LocalArtifact> foundArtifacts = artifactManagement.findLocalArtifactByFilename(fileName);
if (foundArtifacts.isEmpty()) {
LOG.warn("Software artifact with name {} could not be found.", fileName);
result = new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
if (foundArtifacts.size() > 1) {
LOG.warn("Software artifact name {} is not unique. We will use the first entry.", fileName);
}
final LocalArtifact artifact = foundArtifacts.get(0);
final String ifMatch = request.getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} else {
final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact);
// we set a download status only if we are aware of the
// targetid, i.e.
// authenticated and not anonymous
if (targetid != null && !"anonymous".equals(targetid)) {
final Action action = checkAndReportDownloadByTarget(request, targetid, artifact);
result = RestResourceConversionHelper.writeFileResponse(artifact, response, request, file,
cacheWriteNotify, action.getId());
} else {
result = RestResourceConversionHelper.writeFileResponse(artifact, response, request, file);
}
}
}
return result;
}
private Action checkAndReportDownloadByTarget(final HttpServletRequest request, final String targetid,
final LocalArtifact artifact) {
final Target target = controllerManagement.updateLastTargetQuery(
targetid,
IpUtil.getClientIpFromRequest(request,
environment.getProperty("security.rp.remote_ip_header", String.class, "X-Forwarded-For")));
final Action action = controllerManagement.getActionForDownloadByTargetAndSoftwareModule(
target.getControllerId(), artifact.getSoftwareModule());
final String range = request.getHeader("Range");
final ActionStatus actionStatus = new ActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
actionStatus.setStatus(Status.DOWNLOAD);
if (range != null) {
actionStatus.addMessage("It is a partial download request: " + range);
} else {
actionStatus.addMessage("Target downloads");
}
controllerManagement.addActionStatusMessage(actionStatus);
return action;
}
/**
* Handles GET {@link Artifact} MD5 checksum file download request.
*
* @param fileName
* to search for
* @param response
* to write to
*
* @return response of the servlet
*/
@RequestMapping(method = RequestMethod.GET, value = ControllerConstants.ARTIFACT_DOWNLOAD_BY_FILENAME
+ "/{fileName}" + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX)
@ResponseBody
public ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable final String fileName,
final HttpServletResponse response) {
final List<LocalArtifact> foundArtifacts = artifactManagement.findLocalArtifactByFilename(fileName);
if (foundArtifacts.isEmpty()) {
LOG.warn("Softeare artifact with name {} could not be found.", fileName);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else if (foundArtifacts.size() > 1) {
LOG.error("Softeare artifact name {} is not unique.", fileName);
}
try {
DataConversionHelper.writeMD5FileResponse(fileName, response, foundArtifacts.get(0));
} catch (final IOException e) {
LOG.error("Failed to stream MD5 File", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,66 @@
/**
* 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.controller;
/**
*
*
*
*/
public final class ControllerConstants {
/**
* The base URL mapping of the direct device integration rest resources.
*/
public static final String BASE_V1_REQUEST_MAPPING = "/{tenant}/controller/v1";
/**
* The base URL mapping of the artifact repository rest resources.
*/
public static final String ARTIFACTS_V1_REQUEST_MAPPING = "/{tenant}/controller/artifacts/v1";
/**
* Deployment action resources.
*/
static final String DEPLOYMENT_BASE_ACTION = "deploymentBase";
/**
* Cancel action resources.
*/
static final String CANCEL_ACTION = "cancelAction";
/**
* Feedback channel.
*/
static final String FEEDBACK = "feedback";
/**
* Config data action resources.
*/
static final String CONFIG_DATA_ACTION = "configData";
/**
* The artifact URL mapping rest resource.
*/
static final String ARTIFACT_DOWNLOAD = "artifact";
/**
* The artifact by filename URL mapping rest resource.
*/
static final String ARTIFACT_DOWNLOAD_BY_FILENAME = "/filename";
/**
* File suffix for MDH hash download (see Linux md5sum).
*/
static final String ARTIFACT_MD5_DWNL_SUFFIX = ".MD5SUM";
// constant class, private constructor.
private ControllerConstants() {
}
}

View File

@@ -0,0 +1,184 @@
/**
* 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.controller;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.controller.model.Artifact;
import org.eclipse.hawkbit.controller.model.Chunk;
import org.eclipse.hawkbit.controller.model.Config;
import org.eclipse.hawkbit.controller.model.ControllerBase;
import org.eclipse.hawkbit.controller.model.Polling;
import org.eclipse.hawkbit.controller.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.hateoas.Link;
import com.google.common.base.Charsets;
/**
* Utility class for the Controller API.
*
*
*
*
*/
public final class DataConversionHelper {
// utility class, private constructor.
private DataConversionHelper() {
}
static List<Chunk> createChunks(final String targetid, final Action uAction, final TenantAware tenantAware) {
return uAction.getDistributionSet()
.getModules().stream().map(module -> new Chunk(mapChunkLegacyKeys(module.getType().getKey()),
module.getVersion(), module.getName(), createArtifacts(targetid, module, tenantAware)))
.collect(Collectors.toList());
}
/**
* Creates all available (rest) software modules for a given distributionSet
* set.
*
* @param targetid
* of the target
* @param assignedDistributionSet
* the assigned distribution set
* @param tenantAware
* of the tenant
* @return a list of software modules or a empty list. Cannot be <null>.
*/
public static List<SoftwareModule> createSoftwareModules(final String targetid,
final DistributionSet assignedDistributionSet, final TenantAware tenantAware) {
return assignedDistributionSet.getModules().stream()
.map(module -> createSoftwareModul(targetid, module, tenantAware)).collect(Collectors.toList());
}
private static SoftwareModule createSoftwareModul(final String targetid,
final org.eclipse.hawkbit.repository.model.SoftwareModule module, final TenantAware tenantAware) {
final List<Link> links = new ArrayList<Link>();
module.getLocalArtifacts().forEach(artifact -> createAndAddLinks(targetid, tenantAware, artifact, links));
return new SoftwareModule(module.getId(), module.getType().getKey(), module.getVersion(), module.getName(),
links);
}
private static String mapChunkLegacyKeys(final String key) {
if ("application".equals(key)) {
return "bApp";
}
if ("runtime".equals(key)) {
return "jvm";
}
return key;
}
/**
* Creates all (rest) artifacts for a given software module.
*
* @param targetid
* of the target
* @param module
* the software module
* @param tenantAware
* of the tenant
* @return a list of artifacts or a empty list. Cannot be <null>.
*/
public static List<Artifact> createArtifacts(final String targetid,
final org.eclipse.hawkbit.repository.model.SoftwareModule module, final TenantAware tenantAware) {
final List<Artifact> files = new ArrayList<Artifact>();
module.getLocalArtifacts().forEach(artifact -> {
final Artifact file = new Artifact();
file.setHashes(new ArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
file.setFilename(artifact.getFilename());
file.setSize(artifact.getSize());
createAndAddLinks(targetid, tenantAware, artifact, file.getLinks());
files.add(file);
});
return files;
}
private static void createAndAddLinks(final String targetid, final TenantAware tenantAware,
final LocalArtifact artifact, final List<Link> links) {
links.add(linkTo(methodOn(RootController.class, tenantAware.getCurrentTenant()).downloadArtifact(targetid,
artifact.getSoftwareModule().getId(), artifact.getFilename(), null, null)).withRel("download"));
links.add(linkTo(methodOn(RootController.class, tenantAware.getCurrentTenant()).downloadArtifactMd5(targetid,
artifact.getSoftwareModule().getId(), artifact.getFilename(), null, null)).withRel("md5sum"));
}
static ControllerBase fromTarget(final Target target, final List<Action> actions,
final String defaultControllerPollTime, final TenantAware tenantAware) {
final ControllerBase result = new ControllerBase(new Config(new Polling(defaultControllerPollTime)));
boolean addedUpdate = false;
boolean addedCancel = false;
final long countCancelingActions = actions.stream().filter(a -> a.getStatus() == Status.CANCELING).count();
for (final Action action : actions) {
if (countCancelingActions <= 0 && !action.isCancelingOrCanceled() && !addedUpdate) {
// we need to add the hashcode here of the actionWithStatus
// because the action might
// have changed from 'soft' to 'forced' type and we need to
// change the payload of the
// response because of eTags.
result.add(linkTo(methodOn(RootController.class, tenantAware.getCurrentTenant())
.getControllerBasedeploymentAction(target.getControllerId(), action.getId(), actions.hashCode(),
null)).withRel(ControllerConstants.DEPLOYMENT_BASE_ACTION));
addedUpdate = true;
} else if (action.isCancelingOrCanceled() && !addedCancel) {
result.add(linkTo(methodOn(RootController.class, tenantAware.getCurrentTenant())
.getControllerCancelAction(target.getControllerId(), action.getId(), null))
.withRel(ControllerConstants.CANCEL_ACTION));
addedCancel = true;
}
}
if (target.getTargetInfo().isRequestControllerAttributes()) {
result.add(linkTo(methodOn(RootController.class, tenantAware.getCurrentTenant()).putConfigData(null,
target.getControllerId(), null)).withRel(ControllerConstants.CONFIG_DATA_ACTION));
}
return result;
}
static void writeMD5FileResponse(final String fileName, final HttpServletResponse response,
final LocalArtifact artifact) throws IOException {
final StringBuilder builder = new StringBuilder();
builder.append(artifact.getMd5Hash());
builder.append(" ");
builder.append(fileName);
final byte[] content = builder.toString().getBytes(Charsets.US_ASCII);
final StringBuilder header = new StringBuilder();
header.append("attachment;filename=");
header.append(fileName);
header.append(ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX);
response.setContentLength(content.length);
response.setHeader("Content-Disposition", header.toString());
response.getOutputStream().write(content, 0, content.length);
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.controller;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
/**
* Annotation to enable {@link ComponentScan} in the resource package to setup
* all {@link Controller} annotated classes and setup the Direct Device API.
*
*
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface EnableDirectDeviceApi {
}

View File

@@ -0,0 +1,50 @@
/**
* 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.controller;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Thrown if artifact content streaming to client failed.
*
*
*
*
*/
public final class FileSteamingFailedException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new FileUploadFailedException with
* {@link SpServerError#SP_REST_BODY_NOT_READABLE} error.
*/
public FileSteamingFailedException() {
super(SpServerError.SP_ARTIFACT_LOAD_FAILED);
}
/**
* @param cause
* for the exception
*/
public FileSteamingFailedException(final Throwable cause) {
super(SpServerError.SP_ARTIFACT_LOAD_FAILED, cause);
}
/**
* @param message
* of the error
*/
public FileSteamingFailedException(final String message) {
super(message, SpServerError.SP_ARTIFACT_LOAD_FAILED);
}
}

View File

@@ -0,0 +1,664 @@
/**
* 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.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.eclipse.hawkbit.ControllerPollProperties;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.controller.model.ActionFeedback;
import org.eclipse.hawkbit.controller.model.Cancel;
import org.eclipse.hawkbit.controller.model.CancelActionToStop;
import org.eclipse.hawkbit.controller.model.Chunk;
import org.eclipse.hawkbit.controller.model.ConfigData;
import org.eclipse.hawkbit.controller.model.ControllerBase;
import org.eclipse.hawkbit.controller.model.Deployment;
import org.eclipse.hawkbit.controller.model.Deployment.HandlingType;
import org.eclipse.hawkbit.controller.model.DeploymentBase;
import org.eclipse.hawkbit.controller.model.Result.FinalResult;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.util.IpUtil;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* The {@link RootController} of the SP server controller API that is queried by
* the SP controller in order to pull {@link Action}s that have to be fullfilled
* and report status updates concerning the {@link Action} processing.
*
* Transactional (read-write) as all queries at least update the last poll time.
*
*
*
*
*
*/
@RestController
@RequestMapping(ControllerConstants.BASE_V1_REQUEST_MAPPING)
@Transactional
@Api(value = "/", description = "SP Direct Device Integration API")
public class RootController implements EnvironmentAware {
private static final Logger LOG = LoggerFactory.getLogger(RootController.class);
private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({}).";
private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server.";
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private ControllerPollProperties controllerPollProperties;
@Autowired
private CacheWriteNotify cacheWriteNotify;
@Autowired
private TenantAware tenantAware;
private String requestHeader;
@Override
public void setEnvironment(final Environment environment) {
final RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(environment,
SP_SERVER_CONFIG_PREFIX);
requestHeader = relaxedPropertyResolver.getProperty("security.rp.remote_ip_header", String.class,
"X-Forwarded-For");
}
/**
* Returns all artifacts of a given software module and target.
*
* @param targetid
* of the {@link Target} that matches to
* {@link Target#getControllerId()}
* @param softwareModuleId
* of the {@link SoftwareModule}
* @return the response
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = org.eclipse.hawkbit.controller.model.Artifact.class, value = "Returns artifacts of given software module", notes = "Returns all artifacts whichs is assigned to the software module")
public ResponseEntity<List<org.eclipse.hawkbit.controller.model.Artifact>> getSoftwareModulesArtifacts(
@PathVariable final String targetid, @PathVariable final Long softwareModuleId) {
LOG.debug("getSoftwareModulesArtifacts({})", targetid);
final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (softwareModule == null) {
LOG.warn("Software module with id {} could not be found.", softwareModuleId);
throw new EntityNotFoundException("Software module does not exist");
}
return new ResponseEntity<>(DataConversionHelper.createArtifacts(targetid, softwareModule, tenantAware),
HttpStatus.OK);
}
/**
* Returns all available software modules for a given target.
*
* @param targetid
* of the {@link Target} that matches to
* {@link Target#getControllerId()}
* @param request
* the HTTP request injected by spring
* @return the response
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModule.class, value = " Returns software modules of given target", notes = "Returns all available software modules for a given target")
public ResponseEntity<List<org.eclipse.hawkbit.controller.model.SoftwareModule>> getSoftwareModules(
@PathVariable final String targetid, final HttpServletRequest request) {
LOG.debug("getSoftwareModules({})", targetid);
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
final DistributionSet assignedDistributionSet = target.getAssignedDistributionSet();
if (assignedDistributionSet == null) {
return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK);
}
return new ResponseEntity<>(DataConversionHelper.createSoftwareModules(targetid, assignedDistributionSet,
tenantAware), HttpStatus.OK);
}
/**
* Root resource for an individual {@link Target}.
*
* @param targetid
* of the {@link Target} that matches to
* {@link Target#getControllerId()}
* @param request
* the HTTP request injected by spring
* @return the response
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ControllerBase.class, value = "Controller base poll resource", notes = "This base resource can be regularly polled by the controller on the provisiong target or device "
+ "in order to retrieve actions that need to be executed. The resource supports Etag based modification "
+ "checks in order to save traffic.")
public ResponseEntity<ControllerBase> getControllerBase(@PathVariable final String targetid,
final HttpServletRequest request) {
LOG.debug("getControllerBase({})", targetid);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
if (target.getTargetInfo().getUpdateStatus() == TargetUpdateStatus.UNKNOWN) {
LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", targetid);
controllerManagement.updateTargetStatus(target.getTargetInfo(), TargetUpdateStatus.REGISTERED,
System.currentTimeMillis(), IpUtil.getClientIpFromRequest(request, requestHeader));
}
return new ResponseEntity<ControllerBase>(DataConversionHelper.fromTarget(target,
controllerManagement.findActionByTargetAndActive(target), controllerPollProperties.getPollingTime(),
tenantAware), HttpStatus.OK);
}
/**
* Handles GET {@link Artifact} download request. This could be full or
* partial (as specified by RFC7233 (Range Requests)) download request.
*
* @param targetid
* of the related
* @param softwareModuleId
* of the parent {@link SoftwareModule}
* @param fileName
* of the related {@link LocalArtifact}
* @param response
* of the servlet
* @param request
* from the client
*
* @return response of the servlet which in case of success is status code
* {@link HttpStatus#OK} or in case of partial download
* {@link HttpStatus#PARTIAL_CONTENT}.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}")
@ApiOperation(response = Void.class, value = "Downstream of given artifact", notes = "Download resource for artifacts. The resource supports partial download "
+ "as specified by RFC7233 (range requests). Keep in mind that the controller "
+ "needs to have the artifact assigned in order to be granted permission to download.")
public ResponseEntity<Void> downloadArtifact(@PathVariable final String targetid,
@PathVariable final Long softwareModuleId, @PathVariable final String fileName,
final HttpServletResponse response, final HttpServletRequest request) {
ResponseEntity<Void> result = null;
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (checkModule(fileName, module)) {
LOG.warn("Softare module with id {} could not be found.", softwareModuleId);
result = new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
final LocalArtifact artifact = module.getLocalArtifactByFilename(fileName).get();
final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact);
final String ifMatch = request.getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} else {
final Action action = checkAndLogDownload(request, target, module);
result = RestResourceConversionHelper.writeFileResponse(artifact, response, request, file,
cacheWriteNotify, action.getId());
}
}
return result;
}
private Action checkAndLogDownload(final HttpServletRequest request, final Target target,
final SoftwareModule module) {
final Action action = controllerManagement.getActionForDownloadByTargetAndSoftwareModule(
target.getControllerId(), module);
final String range = request.getHeader("Range");
final ActionStatus statusMessage = new ActionStatus();
statusMessage.setAction(action);
statusMessage.setOccurredAt(System.currentTimeMillis());
statusMessage.setStatus(Status.DOWNLOAD);
if (range != null) {
statusMessage.addMessage("It is a partial download request: " + range);
} else {
statusMessage.addMessage("Controller downloads");
}
controllerManagement.addActionStatusMessage(statusMessage);
return action;
}
private boolean checkModule(final String fileName, final SoftwareModule module) {
return null == module || !module.getLocalArtifactByFilename(fileName).isPresent();
}
/**
* Handles GET {@link Artifact} MD5 checksum file download request.
*
* @param targetid
* of the related
* @param softwareModuleId
* of the parent {@link SoftwareModule}
* @param fileName
* of the related {@link LocalArtifact}
* @param response
* of the servlet
* @param request
* the HTTP request injected by spring
*
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}"
+ ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = MediaType.TEXT_PLAIN_VALUE)
@ApiOperation(response = Void.class, value = "Downstream of given artifacts MD5SUM file", notes = "Download resource for MD5SUM file is an optional functionally especially usefull for "
+ "Linux based devices on order to check artifact coonsitency after download by using the md5sum "
+ "command line tool. The MD5 and SHA1 are in addition available as metadata in the deployment command itself.")
public ResponseEntity<Void> downloadArtifactMd5(@PathVariable final String targetid,
@PathVariable final Long softwareModuleId, @PathVariable final String fileName,
final HttpServletResponse response, final HttpServletRequest request) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest(request, requestHeader));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (checkModule(fileName, module)) {
LOG.warn("Software module with id {} could not be found.", softwareModuleId);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
try {
DataConversionHelper.writeMD5FileResponse(fileName, response, module.getLocalArtifactByFilename(fileName)
.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);
}
/**
* Resource for {@link SoftwareModule} {@link UpdateAction}s.
*
* @param targetid
* of the {@link Target} that matches to
* {@link Target#getControllerId()}
* @param actionId
* of the {@link DeploymentBase} that matches to
* {@link Target#getActiveActions()}
* @param resource
* an hashcode of the resource which indicates if the action has
* been changed, e.g. from 'soft' to 'force' and the eTag needs
* to be re-generated
* @param request
* the HTTP request injected by spring
* @return the response
*/
@RequestMapping(value = "/{targetid}/" + ControllerConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(response = DeploymentBase.class, value = "Deployment or update action", notes = "Core resource for deployment operations. Contains all information necessary in order to execute the operation.")
public ResponseEntity<DeploymentBase> getControllerBasedeploymentAction(
@PathVariable @NotEmpty final String targetid, @PathVariable @NotEmpty final Long actionId,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource,
final HttpServletRequest request) {
LOG.debug("getControllerBasedeploymentAction({},{})", targetid, resource);
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn(GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET, action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
if (!action.isCancelingOrCanceled()) {
final List<Chunk> chunks = DataConversionHelper.createChunks(targetid, action, tenantAware);
final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
final DeploymentBase base = new DeploymentBase(Long.toString(action.getId()), new Deployment(handlingType,
handlingType, chunks));
LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", targetid, base);
controllerManagement.registerRetrieved(action,
"Controller retrieved update action and should start now the download.");
return new ResponseEntity<DeploymentBase>(base, HttpStatus.OK);
}
return new ResponseEntity<DeploymentBase>(HttpStatus.NOT_FOUND);
}
/**
* This is the feedback channel for the {@link DeploymentBase} action.
*
* @param feedback
* to provide
* @param targetid
* of the {@link Target} that matches to
* {@link Target#getControllerId()}
* @param actionId
* of the action we have feedback for
* @param request
* the HTTP request injected by spring
*
* @return the response
*/
@RequestMapping(value = "/{targetid}/" + ControllerConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/"
+ ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(response = ActionFeedback.class, value = "Feedback channel for update actions", notes = "Feedback channel. It is up to the device to decided how much intermediate feedback is "
+ "provided. However, the action will be kept open until the controller on the device reports a "
+ "finished (either successfull or error).")
public ResponseEntity<ActionFeedback> postBasedeploymentActionFeedback(
@Valid @RequestBody final ActionFeedback feedback, @PathVariable final String targetid,
@PathVariable @NotEmpty final Long actionId, final HttpServletRequest request) {
LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", targetid, actionId, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
if (!actionId.equals(feedback.getId())) {
LOG.warn(
"provideBasedeploymentActionFeedback: action in payload ({}) was not identical to action in path ({}).",
feedback.getId(), actionId);
return new ResponseEntity<ActionFeedback>(HttpStatus.NOT_FOUND);
}
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn(GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET, action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
if (!action.isActive()) {
LOG.warn("Updating action {} with feedback {} not possible since action not active anymore.",
action.getId(), feedback.getId());
return new ResponseEntity<ActionFeedback>(HttpStatus.GONE);
}
controllerManagement.addUpdateActionStatus(generateUpdateStatus(feedback, targetid, feedback.getId(), action),
action);
return new ResponseEntity<ActionFeedback>(HttpStatus.OK);
}
private ActionStatus generateUpdateStatus(final ActionFeedback feedback, final String targetid,
final Long actionid, final Action action) {
final ActionStatus actionStatus = new ActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
switch (feedback.getStatus().getExecution()) {
case CANCELED:
LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.CANCELED);
actionStatus.addMessage("Controller confirmed cancelation");
break;
case REJECTED:
LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING);
actionStatus.addMessage("Controller reported internal ERROR and REJECTED update.");
break;
case CLOSED:
LOG.debug("Controller reported closed (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR);
actionStatus.addMessage("Controller reported CLOSED with ERROR!");
} else {
actionStatus.setStatus(Status.FINISHED);
actionStatus.addMessage("Controller reported CLOSED with OK!");
}
break;
default:
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.",
actionid, targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING);
// MECS-400: we should not use the unstructed message list for
// the
// server comment on the
// status.
actionStatus.addMessage("Controller reported: " + feedback.getStatus().getExecution());
break;
}
action.setStatus(actionStatus.getStatus());
if (feedback.getStatus().getDetails() != null && !feedback.getStatus().getDetails().isEmpty()) {
final List<String> details = feedback.getStatus().getDetails();
for (final String detailMsg : details) {
actionStatus.addMessage(detailMsg);
}
}
return actionStatus;
}
/**
* This is the feedback channel for the config data action.
*
* @param configData
* as body
* @param targetid
* to provide data for
* @param request
* the HTTP request injected by spring
*
* @return status of the request
*/
@RequestMapping(value = "/{targetid}/" + ControllerConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(response = ConfigData.class, value = "Response to a requested metadata pull from the provisioning target device.", notes = "The usual behaviour is that when a new device resgisters at the server it is "
+ "requested to provide the meta information that will allow the server to identify the device on a "
+ "hardware level (e.g. hardware revision, mac address, serial number etc.).")
public ResponseEntity<ConfigData> putConfigData(@Valid @RequestBody final ConfigData configData,
@PathVariable final String targetid, final HttpServletRequest request) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest(request, requestHeader));
controllerManagement.updateControllerAttributes(targetid, configData.getData());
return new ResponseEntity<ConfigData>(HttpStatus.OK);
}
/**
* {@link RequestMethod.GET} method for the {@link Cancel} action.
*
* @param targetid
* ID of the calling target
* @param actionId
* of the action
* @param request
* the HTTP request injected by spring
*
* @return the {@link Cancel} response
*/
@RequestMapping(value = "/{targetid}/" + ControllerConstants.CANCEL_ACTION + "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(response = Cancel.class, value = "Cancel an action", notes = "The SP server might cancel an operation, e.g. an unfinished update has a sucessor. "
+ "It is up to the provisiong target to decide to accept the cancelation or reject it.")
public ResponseEntity<Cancel> getControllerCancelAction(@PathVariable @NotEmpty final String targetid,
@PathVariable @NotEmpty final Long actionId, final HttpServletRequest request) {
LOG.debug("getControllerCancelAction({})", targetid);
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn(GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET, action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
if (action.isCancelingOrCanceled()) {
final Cancel cancel = new Cancel(String.valueOf(action.getId()), new CancelActionToStop(
String.valueOf(action.getId())));
LOG.debug("Found an active CancelAction for target {}. returning cancel: {}", targetid, cancel);
controllerManagement.registerRetrieved(action,
"Controller retrieved cancel action and should start now the cancelation.");
return new ResponseEntity<Cancel>(cancel, HttpStatus.OK);
}
return new ResponseEntity<Cancel>(HttpStatus.NOT_FOUND);
}
/**
* {@link RequestMethod.POST} method receiving the {@link ActionFeedback}
* from the target.
*
* @param feedback
* the {@link ActionFeedback} from the target.
* @param targetid
* the ID of the calling target
* @param actionId
* of the action we have feedback for
* @param request
* the HTTP request injected by spring
*
* @return the {@link ActionFeedback} response
*/
@RequestMapping(value = "/{targetid}/" + ControllerConstants.CANCEL_ACTION + "/{actionId}/"
+ ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(response = Cancel.class, value = "Feedback channel for cancel actions", notes = "It is up to the device to decided how much intermediate feedback is "
+ "provided. However, the action will be kept open until the controller on the device reports a "
+ "finished (either successfull or error) or rejects the oprtioan, e.g. the canceled actions have been started already.")
public ResponseEntity<ActionFeedback> postCancelActionFeedback(@Valid @RequestBody final ActionFeedback feedback,
@PathVariable @NotEmpty final String targetid, @PathVariable @NotEmpty final Long actionId,
final HttpServletRequest request) {
LOG.debug("provideCancelActionFeedback for target [{}]: {}", targetid, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, requestHeader));
if (!actionId.equals(feedback.getId())) {
LOG.warn(
"provideBasedeploymentActionFeedback: action in payload ({}) was not identical to action in path ({}).",
feedback.getId(), actionId);
return new ResponseEntity<ActionFeedback>(HttpStatus.NOT_FOUND);
}
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn(GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET, action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
controllerManagement.addCancelActionStatus(
generateActionCancelStatus(feedback, target, feedback.getId(), action), action);
return new ResponseEntity<ActionFeedback>(HttpStatus.OK);
}
private static ActionStatus generateActionCancelStatus(final ActionFeedback feedback, final Target target,
final Long actionid, final Action action) {
final ActionStatus actionStatus = new ActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());
switch (feedback.getStatus().getExecution()) {
case CANCELED:
LOG.error(
"Controller reported cancel for a cancel which is not supported by the server (actionid: {}, targetid: {}) as we got {} report.",
actionid, target.getControllerId(), feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING);
break;
case REJECTED:
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, targetid: {}).", actionid,
target.getControllerId());
actionStatus.setStatus(Status.WARNING);
break;
case CLOSED:
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR);
} else {
actionStatus.setStatus(Status.CANCELED);
}
break;
default:
actionStatus.setStatus(Status.RUNNING);
break;
}
action.setStatus(actionStatus.getStatus());
if (feedback.getStatus().getDetails() != null && !feedback.getStatus().getDetails().isEmpty()) {
final List<String> details = feedback.getStatus().getDetails();
for (final String detailMsg : details) {
actionStatus.addMessage(detailMsg);
}
}
return actionStatus;
}
private Action findActionWithExceptionIfNotFound(final Long actionId) {
final Action findAction = controllerManagement.findActionWithDetails(actionId);
if (findAction == null) {
throw new EntityNotFoundException("Action with Id {" + actionId + "} does not exist");
}
return findAction;
}
}

View File

@@ -0,0 +1,101 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
*
* <p>
* After the SP Target has executed an action, received by a GET(URL) request it
* reports the completion of it to the SP Server with a action status message,
* i.e. with a PUT message to the feedback channel, i.e. PUT URL/feedback. This
* message could be used not only at the end of execution but also as status
* updates during a longer lasting execution period. The format of each action
* answer message is defined below at each action. But it is expected, that the
* contents of the message answers have all a similar structure: The content
* starts with a generic header and additional elements. *
* </p>
*
* <p>
* The answer header would look like: { "id": "51659181", "time":
* "20140511T121314", "status": { "execution": "closed", "result": { "final":
* "success", "progress": {} } "details": [], } }
* </p>
*
*
*
*
*
*
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel("SP Target Action Feedback")
public class ActionFeedback {
@ApiModelProperty(value = ApiModelProperties.ACTION_ID)
private final Long id;
@ApiModelProperty(value = ApiModelProperties.TARGET_TIME)
private final String time;
@ApiModelProperty(value = ApiModelProperties.TARGET_STATUS, required = true)
@NotNull
private final Status status;
/**
* Constructor.
*
* @param id
* of the actions the feedback is for
* @param time
* of the feedback
* @param status
* is the feedback itself
*/
@JsonCreator
public ActionFeedback(@JsonProperty("id") final Long id, @JsonProperty("time") final String time,
@JsonProperty("status") final Status status) {
this.id = id;
this.time = time;
this.status = status;
}
public Long getId() {
return id;
}
public String getTime() {
return time;
}
public Status getStatus() {
return status;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "ActionFeedback [id=" + id + ", time=" + time + ", status=" + status + "]";
}
}

View File

@@ -0,0 +1,90 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Download information for all artifacts related to a specific {@link Chunk}.
*
*
*
*
*/
@ApiModel(ApiModelProperties.ARTIFACTS)
public class Artifact extends ResourceSupport {
@ApiModelProperty(value = ApiModelProperties.ARTIFACT_PROVIDED_FILENAME, required = true)
@NotNull
@JsonProperty
private String filename;
@ApiModelProperty(value = ApiModelProperties.ARTIFACT_HASHES)
@JsonProperty
private ArtifactHash hashes;
@ApiModelProperty(value = ApiModelProperties.ARTIFACT_SIZE)
@JsonProperty
private Long size;
/**
* @return the hashes
*/
public ArtifactHash getHashes() {
return hashes;
}
/**
* @param hashes
* the hashes to set
*/
public void setHashes(final ArtifactHash hashes) {
this.hashes = hashes;
}
/**
* @return the fileName
*/
public String getFilename() {
return filename;
}
/**
* @param fileName
* the fileName to set
*/
public void setFilename(final String fileName) {
filename = fileName;
}
/**
* @return the size
*/
public Long getSize() {
return size;
}
/**
* @param size
* the size to set
*/
public void setSize(final Long size) {
this.size = size;
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Cancel action to be provided to the target.
*
*
*
*/
@ApiModel("SP Target Cancel Action")
public class Cancel {
@ApiModelProperty(value = ApiModelProperties.ACTION_ID)
private final String id;
@ApiModelProperty(value = ApiModelProperties.CANCEL_ACTION, required = true)
@NotNull
private final CancelActionToStop cancelAction;
/**
* Parameterized constructor.
*
* @param id
* of the {@link CancelAction}
* @param cancelAction
* the action
*/
public Cancel(final String id, final CancelActionToStop cancelAction) {
super();
this.id = id;
this.cancelAction = cancelAction;
}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @return the cancelAction
*/
public CancelActionToStop getCancelAction() {
return cancelAction;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Cancel [id=" + id + ", cancelAction=" + cancelAction + "]";
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* The {@link Action} that has to be stopped by the target.
*
*
*
*/
@ApiModel(ApiModelProperties.CANCEL_ACTION)
public class CancelActionToStop {
@ApiModelProperty(value = ApiModelProperties.ACTION_ID, required = true)
@NotNull
private final String stopId;
/**
* Parameterized constructor.
*
* @param stopId
* ID of the {@link Action} to be stoppedW
*/
public CancelActionToStop(final String stopId) {
super();
this.stopId = stopId;
}
/**
* @return the stopId
*/
public String getStopId() {
return stopId;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "CancelAction [stopId=" + stopId + "]";
}
}

View File

@@ -0,0 +1,89 @@
/**
* 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.controller.model;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Deployment chunks.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.CHUNK)
public class Chunk {
@ApiModelProperty(value = ApiModelProperties.CHUNK_TYPE, required = true)
@NotNull
private final String part;
@ApiModelProperty(value = ApiModelProperties.CHUNK_VERSION, required = true)
@NotNull
private final String version;
@ApiModelProperty(value = ApiModelProperties.CHUNK_NAME, required = true)
@NotNull
private final String name;
@ApiModelProperty(value = ApiModelProperties.ARTIFACTS)
private final List<Artifact> artifacts;
/**
* Constructor.
*
* @param part
* of the deployment chunk
* @param version
* of the artifact
* @param name
* of the artifact
* @param artifacts
* download information
*
*/
public Chunk(final String part, final String version, final String name, final List<Artifact> artifacts) {
super();
this.part = part;
this.version = version;
this.name = name;
this.artifacts = artifacts;
}
public String getPart() {
return part;
}
public String getVersion() {
return version;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the artifacts
*/
public List<Artifact> getArtifacts() {
return artifacts;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.controller.model;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Standard configuration for the target.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.TARGET_CONFIGURATION)
public class Config {
@ApiModelProperty(value = ApiModelProperties.TARGET_POLL_TIME)
private final Polling polling;
/**
* Constructor.
*
* @param polling
* configuration of the SP target
*/
public Config(final Polling polling) {
super();
this.polling = polling;
}
public Polling getPolling() {
return polling;
}
}

View File

@@ -0,0 +1,75 @@
/**
* 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.controller.model;
import java.util.Map;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* feedback channel for ConfigData action.
*
*
*
*
*
*/
@ApiModel("Configuration or metadata that is reponded by the target")
public class ConfigData extends ActionFeedback {
@ApiModelProperty(ApiModelProperties.TARGET_CONFIG_DATA)
@NotEmpty
private final Map<String, String> data;
/**
* Constructor.
*
* @param id
* of the actions the feedback is for
* @param time
* of the feedback
* @param status
* is the feedback itself
* @param data
* contains the attributes.
*
*/
@JsonCreator
public ConfigData(@JsonProperty(value = "id") final Long id, @JsonProperty(value = "time") final String time,
@JsonProperty(value = "status") final Status status,
@JsonProperty(value = "data") final Map<String, String> data) {
super(id, time, status);
this.data = data;
}
/**
* @return the data
*/
public Map<String, String> getData() {
return data;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "ConfigData [data=" + data + ", toString()=" + super.toString() + "]";
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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.controller.model;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.springframework.hateoas.ResourceSupport;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* {@link ControllerBase} ressource content.
*
*
*
*
*
*/
@ApiModel("SP target base poll resource")
public class ControllerBase extends ResourceSupport {
@ApiModelProperty(ApiModelProperties.TARGET_CONFIGURATION)
private final Config config;
/**
* Constructor.
*
* @param config
* configuration of the SP target
*/
public ControllerBase(final Config config) {
super();
this.config = config;
}
/**
* @return the config
*/
public Config getConfig() {
return config;
}
}

View File

@@ -0,0 +1,120 @@
/**
* 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.controller.model;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Detailed {@link UpdateAction} information.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.DEPLOYMENT)
public class Deployment {
@ApiModelProperty(value = ApiModelProperties.HANDLING_DOWNLOAD)
private final HandlingType download;
@ApiModelProperty(value = ApiModelProperties.HANDLING_UPDATE)
private final HandlingType update;
@ApiModelProperty(value = ApiModelProperties.CHUNK)
private final List<Chunk> chunks;
/**
* Constructor.
*
* @param download
* handling type
* @param update
* handling type
* @param chunks
* to handle.
*/
public Deployment(final HandlingType download, final HandlingType update, final List<Chunk> chunks) {
super();
this.download = download;
this.update = update;
this.chunks = chunks;
}
public HandlingType getDownload() {
return download;
}
public HandlingType getUpdate() {
return update;
}
public List<Chunk> getChunks() {
return chunks;
}
/**
* The handling type for the update {@link Action}.
*
*
*
*
*
*/
@ApiModel("Handling type for the deployment part")
public enum HandlingType {
/**
* Not necessary for the command.
*/
SKIP("skip"),
/**
* Try to execute (local applications may intervene by SP control API).
*/
ATTEMPT("attempt"),
/**
* Execution independent of local intervention attempts.
*/
FORCED("forced");
private String name;
private HandlingType(final String name) {
this.name = name;
}
/**
* @return the name
*/
@JsonValue
public String getName() {
return name;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Deployment [download=" + download + ", update=" + update + ", chunks=" + chunks + "]";
}
}

View File

@@ -0,0 +1,68 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* {@link UpdateAction} ressource.
*
*
*
*
*
*/
@ApiModel("Deployment or update action")
public class DeploymentBase extends ResourceSupport {
@ApiModelProperty(value = ApiModelProperties.ITEM_ID, required = false)
@JsonProperty("id")
@NotNull
private final String deplyomentId;
@ApiModelProperty(value = ApiModelProperties.DEPLOYMENT, required = false)
@NotNull
private final Deployment deployment;
/**
* Constructor.
*
* @param id
* of the {@link UpdateAction}
* @param deployment
* details.
*/
public DeploymentBase(final String id, final Deployment deployment) {
deplyomentId = id;
this.deployment = deployment;
}
public Deployment getDeployment() {
return deployment;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "DeploymentBase [id=" + deplyomentId + ", deployment=" + deployment + "]";
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.controller.model;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Polling interval for the SP target.
*
*
*
*
*
*/
@ApiModel(value = ApiModelProperties.TARGET_POLL_TIME)
public class Polling {
@ApiModelProperty(value = ApiModelProperties.TARGET_SLEEP)
private final String sleep;
/**
* Constructor.
*
* @param sleep
* between polls
*/
public Polling(final String sleep) {
super();
this.sleep = sleep;
}
public String getSleep() {
return sleep;
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.controller.model;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Action fulfillment progress by means of gives the achieved amount of maximal
* of possible levels.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.TARGET_RESULT_PROGRESS)
public class Progress {
@ApiModelProperty(value = ApiModelProperties.TARGET_PROGRESS_CNT, required = true)
@NotNull
private final Integer cnt;
@ApiModelProperty(value = ApiModelProperties.TARGET_PROGRESS_OF)
private final Integer of;
/**
* Constructor.
*
* @param cnt
* achieved amount
* @param of
* maximum levels
*/
@JsonCreator
public Progress(@JsonProperty("cnt") final Integer cnt, @JsonProperty("of") final Integer of) {
super();
this.cnt = cnt;
this.of = of;
}
public Integer getCnt() {
return cnt;
}
public Integer getOf() {
return of;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Progress [cnt=" + cnt + ", of=" + of + "]";
}
}

View File

@@ -0,0 +1,115 @@
/**
* 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.controller.model;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Result information of the action progress which can by an intermediate or
* final update.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.TARGET_RESULT_VALUE)
public class Result {
@ApiModelProperty(value = ApiModelProperties.TARGET_RESULT_FINISHED, required = true)
@NotEmpty
private final FinalResult finished;
@ApiModelProperty(value = ApiModelProperties.TARGET_RESULT_PROGRESS)
private final Progress progress;
/**
* Constructor.
*
* @param finished
* as final result
* @param progress
* if not yet finished
*/
@JsonCreator
public Result(@JsonProperty("finished") final FinalResult finished,
@JsonProperty("progress") final Progress progress) {
super();
this.finished = finished;
this.progress = progress;
}
public FinalResult getFinished() {
return finished;
}
public Progress getProgress() {
return progress;
}
/**
* Defined status of the final result.
*
*
*
*
*
*/
@ApiModel(value = ApiModelProperties.TARGET_RESULT_FINISHED)
public enum FinalResult {
/**
* Execution was successful.
*/
SUCESS("success"),
/**
* Execution terminated with errors or without the expected result.
*/
FAILURE("failure"),
/**
* No final result could be determined (yet).
*/
NONE("none");
private String name;
private FinalResult(final String name) {
this.name = name;
}
/**
* @return the name
*/
@JsonValue
public String getName() {
return name;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Result [finished=" + finished + ", progress=" + progress + "]";
}
}

View File

@@ -0,0 +1,94 @@
/**
* 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.controller.model;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import org.springframework.hateoas.Link;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
*
*
*
*/
@ApiModel(ApiModelProperties.SOFTWARE_MODUL)
public class SoftwareModule {
@ApiModelProperty(value = ApiModelProperties.SOFTWARE_MODUL_TYPE, required = true)
@NotNull
private final Long id;
@ApiModelProperty(value = ApiModelProperties.SOFTWARE_MODUL_TYPE, required = true)
@NotNull
private final String type;
@ApiModelProperty(value = ApiModelProperties.SOFTWARE_MODULE_VERSION, required = true)
@NotNull
private final String version;
@ApiModelProperty(value = ApiModelProperties.SOFTWARE_MODULE_NAME, required = true)
@NotNull
private final String name;
@ApiModelProperty(value = ApiModelProperties.SOFTWARE_MODULE_ARTIFACT_LINKS)
private final List<Link> artifactLinks;
/**
* Constructor.
*
* @param id
* of the software module
* @param type
* of the deployment software module
* @param version
* of the software module
* @param name
* of the software module
* @param artifactLinks
* the links to the artifacts of the software module
*/
public SoftwareModule(final Long id, final String type, final String version, final String name,
final List<Link> artifactLinks) {
this.id = id;
this.type = type;
this.version = version;
this.name = name;
this.artifactLinks = artifactLinks;
}
@JsonProperty("links")
public List<Link> getArtifactLinks() {
return artifactLinks;
}
public Long getId() {
return id;
}
public String getType() {
return type;
}
public String getVersion() {
return version;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,143 @@
/**
* 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.controller.model;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.rest.resource.model.doc.ApiModelProperties;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* Details status information concerning the action processing.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.TARGET_STATUS)
public class Status {
@ApiModelProperty(value = ApiModelProperties.TARGET_EXEC_STATUS, required = true)
@NotNull
private final ExecutionStatus execution;
@ApiModelProperty(value = ApiModelProperties.TARGET_RESULT_VALUE, required = true)
@NotNull
private final Result result;
@ApiModelProperty(value = ApiModelProperties.TARGET_RESULT_DETAILS)
private final List<String> details;
/**
* Constructor.
*
* @param execution
* status
* @param result
* information
* @param details
* as optional addition
*/
@JsonCreator
public Status(@JsonProperty("execution") final ExecutionStatus execution,
@JsonProperty("result") final Result result, @JsonProperty("details") final List<String> details) {
super();
this.execution = execution;
this.result = result;
this.details = details;
}
public ExecutionStatus getExecution() {
return execution;
}
public Result getResult() {
return result;
}
public List<String> getDetails() {
return details;
}
/**
* The element status contains information about the execution of the
* operation.
*
*
*
*
*
*/
@ApiModel(ApiModelProperties.TARGET_EXEC_STATUS)
public enum ExecutionStatus {
/**
* Execution of the action has finished.
*/
CLOSED("closed"),
/**
* Execution has started but has not yet finished.
*/
PROCEEDING("proceeding"),
/**
* Execution was suspended from outside.
*/
CANCELED("canceled"),
/**
* Action has been noticed and is intended to run.
*/
SCHEDULED("scheduled"),
/**
* Action was not accepted.
*/
REJECTED("rejected"),
/**
* Action is started after a reset, power loss, etc.
*/
RESUMED("resumed");
private String name;
private ExecutionStatus(final String name) {
this.name = name;
}
/**
* @return the name
*/
@JsonValue
public String getName() {
return name;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Status [execution=" + execution + ", result=" + result + ", details=" + details + "]";
}
}

View File

@@ -0,0 +1,222 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.rest.resource.model.MetadataRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyCreate;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.TargetAssignmentResponseBody;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class DistributionSetMapper {
private DistributionSetMapper() {
// Utility class
}
private static SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId,
final SoftwareManagement softwareManagement) {
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (module == null) {
throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist");
}
return module;
}
private static DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(
final String distributionSetTypekey, final DistributionSetManagement distributionSetManagement) {
final DistributionSetType module = distributionSetManagement
.findDistributionSetTypeByKey(distributionSetTypekey);
if (module == null) {
throw new EntityNotFoundException(
"DistributionSetType with key {" + distributionSetTypekey + "} does not exist");
}
return module;
}
/**
* {@link DistributionSetRequestBodyCreate}s to {@link DistributionSet}s.
*
* @param sets
* to convert
* @param softwareManagement
* to use for conversion
* @return converted list of {@link DistributionSet}s
*/
static List<DistributionSet> dsFromRequest(final Iterable<DistributionSetRequestBodyCreate> sets,
final SoftwareManagement softwareManagement, final DistributionSetManagement distributionSetManagement) {
final List<DistributionSet> mappedList = new ArrayList<>();
for (final DistributionSetRequestBodyCreate dsRest : sets) {
mappedList.add(fromRequest(dsRest, softwareManagement, distributionSetManagement));
}
return mappedList;
}
/**
* {@link DistributionSetRequestBodyCreate} to {@link DistributionSet}.
*
* @param dsRest
* to convert
* @param softwareManagement
* to use for conversion
* @return converted {@link DistributionSet}
*/
static DistributionSet fromRequest(final DistributionSetRequestBodyCreate dsRest,
final SoftwareManagement softwareManagement, final DistributionSetManagement distributionSetManagement) {
final DistributionSet result = new DistributionSet();
result.setDescription(dsRest.getDescription());
result.setName(dsRest.getName());
result.setType(findDistributionSetTypeWithExceptionIfNotFound(dsRest.getType(), distributionSetManagement));
result.setRequiredMigrationStep(dsRest.isRequiredMigrationStep());
result.setVersion(dsRest.getVersion());
if (dsRest.getOs() != null) {
result.addModule(findSoftwareModuleWithExceptionIfNotFound(dsRest.getOs().getId(), softwareManagement));
}
if (dsRest.getApplication() != null) {
result.addModule(
findSoftwareModuleWithExceptionIfNotFound(dsRest.getApplication().getId(), softwareManagement));
}
if (dsRest.getRuntime() != null) {
result.addModule(
findSoftwareModuleWithExceptionIfNotFound(dsRest.getRuntime().getId(), softwareManagement));
}
if (dsRest.getModules() != null) {
dsRest.getModules().forEach(module -> result
.addModule(findSoftwareModuleWithExceptionIfNotFound(module.getId(), softwareManagement)));
}
return result;
}
/**
* From {@link MetadataRest} to {@link DistributionSetMetadata}.
*
* @param ds
* @param metadata
* @return
*/
static List<DistributionSetMetadata> fromRequestDsMetadata(final DistributionSet ds,
final List<MetadataRest> metadata) {
final List<DistributionSetMetadata> mappedList = new ArrayList<>(metadata.size());
for (final MetadataRest metadataRest : metadata) {
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(new DistributionSetMetadata(metadataRest.getKey(), ds, metadataRest.getValue()));
}
return mappedList;
}
static DistributionSetRest toResponse(final DistributionSet distributionSet) {
if (distributionSet == null) {
return null;
}
final DistributionSetRest response = new DistributionSetRest();
RestModelMapper.mapNamedToNamed(response, distributionSet);
response.setDsId(distributionSet.getId());
response.setVersion(distributionSet.getVersion());
response.setComplete(distributionSet.isComplete());
response.setType(distributionSet.getType().getKey());
distributionSet.getModules()
.forEach(module -> response.getModules().add(SoftwareModuleMapper.toResponse(module)));
response.setRequiredMigrationStep(distributionSet.isRequiredMigrationStep());
response.add(
linkTo(methodOn(DistributionSetResource.class).getDistributionSet(response.getDsId())).withRel("self"));
response.add(linkTo(
methodOn(DistributionSetTypeResource.class).getDistributionSetType(distributionSet.getType().getId()))
.withRel("type"));
response.add(linkTo(methodOn(DistributionSetResource.class).getMetadata(response.getDsId(),
Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET),
Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null))
.withRel("metadata"));
return response;
}
static TargetAssignmentResponseBody toResponse(final DistributionSetAssignmentResult dsAssignmentResult) {
final TargetAssignmentResponseBody result = new TargetAssignmentResponseBody();
result.setAssigned(dsAssignmentResult.getAssigned());
result.setAlreadyAssigned(dsAssignmentResult.getAlreadyAssigned());
result.setTotal(dsAssignmentResult.getTotal());
return result;
}
static DistributionSetsRest toResponseDistributionSets(final Iterable<DistributionSet> sets) {
final DistributionSetsRest response = new DistributionSetsRest();
for (final DistributionSet set : sets) {
response.add(toResponse(set));
}
return response;
}
static MetadataRest toResponseDsMetadata(final DistributionSetMetadata metadata) {
final MetadataRest metadataRest = new MetadataRest();
metadataRest.setKey(metadata.getId().getKey());
metadataRest.setValue(metadata.getValue());
return metadataRest;
}
static List<MetadataRest> toResponseDsMetadata(final List<DistributionSetMetadata> metadata) {
final List<MetadataRest> mappedList = new ArrayList<>(metadata.size());
for (final DistributionSetMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseDsMetadata(distributionSetMetadata));
}
return mappedList;
}
static List<DistributionSetRest> toResponseFromDsList(final List<DistributionSet> sets) {
final List<DistributionSetRest> mappedList = new ArrayList<>();
if (sets != null) {
for (final DistributionSet set : sets) {
final DistributionSetRest response = toResponse(set);
mappedList.add(response);
}
}
return mappedList;
}
}

View File

@@ -0,0 +1,741 @@
/**
* 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.rest.resource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetWithActionType;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.MetadataRest;
import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetPagedList;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyCreate;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyUpdate;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.TargetAssignmentRequestBody;
import org.eclipse.hawkbit.rest.resource.model.distributionset.TargetAssignmentResponseBody;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleAssigmentRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulePagedList;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRest;
import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling for {@link DistributionSet} CRUD operations.
*
*
*
*
*/
@RestController
@Transactional(readOnly = true)
@RequestMapping(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING)
@Api(value = "distributionsets", description = "Distribution Set Management API")
public class DistributionSetResource {
private static final Logger LOG = LoggerFactory.getLogger(DistributionSetResource.class);
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private TargetManagement targetManagement;
@Autowired
private DeploymentManagement deployManagament;
@Autowired
private SystemManagement systemManagement;
@Autowired
private TenantAware currentTenant;
@Autowired
private DistributionSetManagement distributionSetManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles the GET request of retrieving all {@link DistributionSet}s within
* SP.
*
* @param pagingOffsetParam
* the offset of list of sets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return a list of all set for a defined or default page request with
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetPagedList.class, value = "Get paged list of Distribution Sets", notes = "Handles the GET request of retrieving all distribution sets within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<DistributionSetPagedList> getDistributionSets(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>version==1.0.0</td><td>distribution sets with version 1.0.0</td></tr>"
+ "<tr><td>version=ge=2.0.0</td><td>distribution sets with version greater-equal 2.0.0</td></tr>"
+ "<tr><td>name=li=%25hotfix%25,description=li=%25hotfix%25</td><td>distribution sets with either 'hotfix' in name or description ignore case</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeDistributionSetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<DistributionSet> findDsPage;
if (rsqlParam != null) {
findDsPage = distributionSetManagement.findDistributionSetsAll(
RSQLUtility.parse(rsqlParam, DistributionSetFields.class, entityManager), pageable, false);
} else {
findDsPage = distributionSetManagement.findDistributionSetsAll(pageable, false, null);
}
final List<DistributionSetRest> rest = DistributionSetMapper.toResponseFromDsList(findDsPage.getContent());
return new ResponseEntity<>(new DistributionSetPagedList(rest, findDsPage.getTotalElements()), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a single {@link DistributionSet}
* within SP.
*
* @param distributionSetId
* the ID of the set to retrieve
*
* @return a single {@link DistributionSet} with status OK.
*
* @throws EntityNotFoundException
* in case no {@link DistributionSet} with the given ID exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetRest.class, value = "Get Distribution Set", notes = "Handles the GET request of retrieving a single distribution set within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<DistributionSetRest> getDistributionSet(@PathVariable final Long distributionSetId) {
final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId);
return new ResponseEntity<DistributionSetRest>(DistributionSetMapper.toResponse(foundDs), HttpStatus.OK);
}
/**
* Handles the POST request of creating new distribution sets within SP. The
* request body must always be a list of sets. The requests is delegating to
* the {@link SoftwareManagement#createDistributionSet(DistributionSet))}.
*
* @param sets
* the {@link DistributionSet}s to be created.
* @return In case all sets could successful created the ResponseEntity with
* status code 201 - Created but without ResponseBody. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }, produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetsRest.class, value = "Create Distribution Sets", notes = "Handles the POST request of creating new distribution sets within SP. The request body must always be a list of sets. Required Permission: "
+ SpPermission.CREATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 409, message = "Conflict", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<DistributionSetsRest> createDistributionSets(
@RequestBody final List<DistributionSetRequestBodyCreate> sets) {
LOG.debug("creating {} distribution sets", sets.size());
// set default Ds type if ds type is null
sets.stream()
.filter(ds -> ds.getType() == null)
.forEach(
ds -> ds.setType(systemManagement.getTenantMetadata(currentTenant.getCurrentTenant())
.getDefaultDsType().getKey()));
final Iterable<DistributionSet> createdDSets = distributionSetManagement
.createDistributionSets(DistributionSetMapper.dsFromRequest(sets, softwareManagement,
distributionSetManagement));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
LOG.debug("{} distribution sets created, return status {}", sets.size(), HttpStatus.CREATED);
return new ResponseEntity<>(DistributionSetMapper.toResponseDistributionSets(createdDSets), HttpStatus.CREATED);
}
/**
* Handles the DELETE request for a single {@link DistributionSet} within
* SP.
*
* @param distributionSetId
* the ID of the {@link DistributionSet} to delete
* @return status OK if delete as successful.
*
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}")
@ApiOperation(value = "Delete Distribution Set", notes = "Handles the DELETE request for a single Distribution Set within SP. Required Permission: "
+ SpPermission.DELETE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteDistributionSet(@PathVariable final Long distributionSetId) {
final DistributionSet set = findDistributionSetWithExceptionIfNotFound(distributionSetId);
distributionSetManagement.deleteDistributionSet(set);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the UPDATE request for a single {@link DistributionSet} within
* SP.
*
* @param distributionSetId
* the ID of the {@link DistributionSet} to delete
* @param toUpdate
* with the data that needs updating
*
* @return status OK if update as successful with updated content.
*
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = DistributionSetRest.class, value = "Update Distribution Set", notes = "Handles the UPDATE request for a single Distribution Set within SP. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<DistributionSetRest> updateDistributionSet(@PathVariable final Long distributionSetId,
@RequestBody final DistributionSetRequestBodyUpdate toUpdate) {
final DistributionSet set = findDistributionSetWithExceptionIfNotFound(distributionSetId);
if (toUpdate.getDescription() != null) {
set.setDescription(toUpdate.getDescription());
}
if (toUpdate.getName() != null) {
set.setName(toUpdate.getName());
}
if (toUpdate.getVersion() != null) {
set.setVersion(toUpdate.getVersion());
}
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<DistributionSetRest>(DistributionSetMapper.toResponse(distributionSetManagement
.updateDistributionSet(set)), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving assigned targets to a specific
* distribution set.
*
* @param distributionSetId
* the ID of the distribution set to retrieve the assigned
* targets
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return status OK if get request is successful with the paged list of
* targets
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedTargets", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = TargetPagedList.class, value = "Get assigned targets", notes = "Handles the GET request for retrieving assigned targets of a single distribution set. Required Permission: "
+ SpPermission.READ_REPOSITORY + " and " + SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
public ResponseEntity<TargetPagedList> getAssignedTargets(
@PathVariable final Long distributionSetId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query" + "<table border=0>"
+ "<tr><td>controllerId==0815</td><td>targets with the id '0815'</td></tr>"
+ "<tr><td>name=li=%25ccu%25</td><td>targets which contains 'ccu' in their ignore case</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
// check if distribution set exists otherwise throw exception
// immediately
findDistributionSetWithExceptionIfNotFound(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> targetsAssignedDS;
if (rsqlParam != null) {
targetsAssignedDS = targetManagement.findTargetByAssignedDistributionSet(distributionSetId,
RSQLUtility.parse(rsqlParam, TargetFields.class, entityManager), pageable);
} else {
targetsAssignedDS = targetManagement.findTargetByAssignedDistributionSet(distributionSetId, pageable);
}
return new ResponseEntity<>(new TargetPagedList(TargetMapper.toResponse(targetsAssignedDS.getContent()),
targetsAssignedDS.getTotalElements()), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving installed targets to a specific
* distribution set.
*
* @param distributionSetId
* the ID of the distribution set to retrieve the assigned
* targets
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return status OK if get request is successful with the paged list of
* targets
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/installedTargets", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = TargetPagedList.class, value = "Get installed targets", notes = "Handles the GET request for retrieving installed targets of a single distribution set. Required Permission: "
+ SpPermission.READ_REPOSITORY + " and " + SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
public ResponseEntity<TargetPagedList> getInstalledTargets(
@PathVariable final Long distributionSetId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query" + "<table border=0>"
+ "<tr><td>controllerId==0815</td><td>targets with the id '0815'</td></tr>"
+ "<tr><td>name=li=%25ccu%25</td><td>targets which contains 'ccu' in their ignore case</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
// check if distribution set exists otherwise throw exception
// immediately
findDistributionSetWithExceptionIfNotFound(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> targetsInstalledDS;
if (rsqlParam != null) {
targetsInstalledDS = targetManagement.findTargetByInstalledDistributionSet(distributionSetId,
RSQLUtility.parse(rsqlParam, TargetFields.class, entityManager), pageable);
} else {
targetsInstalledDS = targetManagement.findTargetByInstalledDistributionSet(distributionSetId, pageable);
}
return new ResponseEntity<>(new TargetPagedList(TargetMapper.toResponse(targetsInstalledDS.getContent()),
targetsInstalledDS.getTotalElements()), HttpStatus.OK);
}
/**
* Handles the POST request of assigning multiple targets to a single
* distribution set.
*
* @param distributionSetId
* the ID of the distribution set within the URL path parameter
* @param targetIds
* the IDs of the target which should get assigned to the
* distribution set given in the response body
* @return status OK if the assignment of the targets was successful and a
* complex return body which contains information about the assigned
* targets and the already assigned targets counters
*/
@RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedTargets", consumes = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE,
"application/hal+json" })
@ApiOperation(response = TargetAssignmentResponseBody.class, value = "Assign targets to a distribution set", notes = "Handles the POST request for assigning multiple targets to a distribution set.The request body must always be a list of target IDs."
+ " Required Permission: " + SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<TargetAssignmentResponseBody> createAssignedTarget(
@PathVariable final Long distributionSetId,
@RequestBody @ApiParam(value = "List of target IDs", required = true) final List<TargetAssignmentRequestBody> targetIds) {
final DistributionSetAssignmentResult assignDistributionSet = deployManagament.assignDistributionSet(
distributionSetId,
targetIds
.stream()
.map(t -> new TargetWithActionType(t.getId(), RestResourceConversionHelper.convertActionType(t
.getType()), t.getForcetime())).collect(Collectors.toList()));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<TargetAssignmentResponseBody>(
DistributionSetMapper.toResponse(assignDistributionSet), HttpStatus.OK);
}
/**
* Gets a paged list of meta data for a distribution set.
*
* @param distributionSetId
* the ID of the distribution set for the meta data
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=key==abc}
* @return status OK if get request is successful with the paged list of
* meta data
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = MetadataRestPageList.class, value = "Get a paged list of meta data", notes = " Get a paged list of meta data for a distribution set."
+ " Required Permission: " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRestPageList> getMetadata(
@PathVariable final Long distributionSetId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>key==aKey,key==bKey</td><td>metadata with the key 'aKey' or 'bKey'</td></tr>"
+ "<tr><td>value=li=%25someValue%25</td><td>metadata which contains 'someValue' in the value ignore case</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
// check if distribution set exists otherwise throw exception
// immediately
findDistributionSetWithExceptionIfNotFound(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeDistributionSetMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<DistributionSetMetadata> metaDataPage;
if (rsqlParam != null) {
metaDataPage = distributionSetManagement.findDistributionSetMetadataByDistributionSetId(distributionSetId,
RSQLUtility.parse(rsqlParam, DistributionSetMetadataFields.class, entityManager), pageable);
} else {
metaDataPage = distributionSetManagement.findDistributionSetMetadataByDistributionSetId(distributionSetId,
pageable);
}
return new ResponseEntity<>(new MetadataRestPageList(DistributionSetMapper.toResponseDsMetadata(metaDataPage
.getContent()), metaDataPage.getTotalElements()), HttpStatus.OK);
}
/**
* Gets a single meta data value for a specific key of a distribution set.
*
* @param distributionSetId
* the ID of the distribution set to get the meta data from
* @param metadataKey
* the key of the meta data entry to retrieve the value from
* @return status OK if get request is successful with the value of the meta
* data
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata/{metadataKey}", produces = { MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = MetadataRest.class, value = "Get a single meta data value", notes = " Get a single meta data value for a meta data key."
+ " Required Permission: " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRest> getMetadataValue(@PathVariable final Long distributionSetId,
@PathVariable final String metadataKey) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final DistributionSetMetadata findOne = distributionSetManagement.findOne(new DsMetadataCompositeKey(ds,
metadataKey));
return ResponseEntity.<MetadataRest> ok(DistributionSetMapper.toResponseDsMetadata(findOne));
}
/**
* Updates a single meta data value of a distribution set.
*
* @param distributionSetId
* the ID of the distribution set to update the meta data entry
* @param metadataKey
* the key of the meta data to update the value
* @return status OK if the update request is successful and the updated
* meta data result
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}/metadata/{metadataKey}", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = MetadataRest.class, value = "Update a single meta data value", notes = " Update a single meta data value for speficic key."
+ " Required Permission: " + SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRest> updateMetadata(@PathVariable final Long distributionSetId,
@PathVariable final String metadataKey, @RequestBody final MetadataRest metadata) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final DistributionSetMetadata updated = distributionSetManagement
.updateDistributionSetMetadata(new DistributionSetMetadata(metadataKey, ds, metadata.getValue()));
return ResponseEntity.ok(DistributionSetMapper.toResponseDsMetadata(updated));
}
/**
* Deletes a single meta data entry from the distribution set.
*
* @param distributionSetId
* the ID of the distribution set to delete the meta data entry
* @param metadataKey
* the key of the meta data to delete
* @return status OK if the delete request is successful
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/metadata/{metadataKey}")
@ApiOperation(value = "Delete a single meta data", notes = " Delete a single meta data." + " Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Distribution Set or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteMetadata(@PathVariable final Long distributionSetId,
@PathVariable final String metadataKey) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
distributionSetManagement.deleteDistributionSetMetadata(new DsMetadataCompositeKey(ds, metadataKey));
return ResponseEntity.ok().build();
}
/**
* Creates a list of meta data for a specific distribution set.
*
* @param distributionSetId
* the ID of the distribution set to create meta data for
* @param metadataRest
* the list of meta data entries to create
* @return status created if post request is successful with the value of
* the created meta data
*/
@RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/metadata", consumes = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE,
"application/hal+json" })
@ApiOperation(response = MetadataRest.class, value = "Create a list of meta data entries", notes = "Create a list of meta data entries"
+ " Required Permission: " + SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_TARGET)
@ApiResponses({ @ApiResponse(code = 409, message = "Conflict", response = ExceptionInfo.class),
@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<List<MetadataRest>> createMetadata(@PathVariable final Long distributionSetId,
@RequestBody final List<MetadataRest> metadataRest) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final List<DistributionSetMetadata> created = distributionSetManagement
.createDistributionSetMetadata(DistributionSetMapper.fromRequestDsMetadata(ds, metadataRest));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<List<MetadataRest>>(DistributionSetMapper.toResponseDsMetadata(created),
HttpStatus.CREATED);
}
/**
* Assigns a list of software modules to a distribution set.
*
* @param distributionSetId
* the ID of the distribution set to assign software modules for
* @param softwareModuleIDs
* the list of software modules ids to assign
* @return {@link HttpStatus#OK}
*
* @throws EntityNotFoundException
* in case no distribution set with the given
* {@code distributionSetId} exists.
*/
@RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedSM", consumes = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }, produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleRest.class, value = "Assign Software Modules to Distribution Set", notes = "Handles the POST request for assigning multiple software modules to a distribution set.The request body must always be a list of software module IDs."
+ " Required Permission: " + SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_REPOSITORY)
@ApiResponses({
@ApiResponse(code = 423, message = "In use of Distribution Set by Target", response = ExceptionInfo.class),
@ApiResponse(code = 404, message = "Not Found Distribution Set key or Software Module IDs", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<Void> assignSoftwareModules(@PathVariable final Long distributionSetId,
@RequestBody final List<SoftwareModuleAssigmentRest> softwareModuleIDs) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final Set<SoftwareModule> softwareModuleToBeAssigned = new HashSet<SoftwareModule>();
for (final SoftwareModuleAssigmentRest sm : softwareModuleIDs) {
final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(sm.getId());
if (softwareModule != null) {
softwareModuleToBeAssigned.add(softwareModule);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
// Add Softwaremodules to DisSet only if all of them were found
distributionSetManagement.assignSoftwareModules(ds, softwareModuleToBeAssigned);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Deletes the assignment of the software module form the distribution set.
*
* @param distributionSetId
* the ID of the distribution set to reject the software module
* for
* @param softwareModuleId
* the software module id to get rejected form the distribution
* set
* @return status OK if rejection was successful.
* @throws EntityNotFoundException
* in case no distribution set with the given
* {@code distributionSetId} exists.
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/assignedSM/{softwareModuleId}")
@ApiOperation(value = "Delete assignment of Software Module", notes = " Delete a assignment."
+ " Required Permission: " + SpPermission.UPDATE_REPOSITORY)
@ApiResponses({
@ApiResponse(code = 423, message = "In use of Distribution Set by Target", response = ExceptionInfo.class),
@ApiResponse(code = 404, message = "Not Found Distribution Set key or Software Module key", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<Void> deleteAssignSoftwareModules(@PathVariable final Long distributionSetId,
@PathVariable final Long softwareModuleId) {
// check if distribution set and software module exist otherwise throw
// exception immediately
final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final SoftwareModule sm = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId);
distributionSetManagement.unassignSoftwareModule(ds, sm);
return ResponseEntity.ok().build();
}
/**
* Handles the GET request for retrieving the assigned software modules of a
* specific distribution set.
*
* @param distributionSetId
* the ID of the distribution to retrieve
* @param pagingOffsetParam
* the offset of list of sets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @return a list of the assigned software modules of a distribution set
* with status OK, if none is assigned than {@code null}
* @throws EntityNotFoundException
* in case no distribution set with the given
* {@code distributionSetId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedSM", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleRest.class, value = "Get assigned Software Modules", notes = "Handles the GET request of retrieving a single distribution set within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<SoftwareModulePagedList> getAssignedSoftwareModules(
@PathVariable final Long distributionSetId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<SoftwareModule> softwaremodules = softwareManagement.findSoftwareModuleByAssignedTo(pageable,
foundDs);
return new ResponseEntity<>(new SoftwareModulePagedList(SoftwareModuleMapper.toResponse(softwaremodules
.getContent()), softwaremodules.getTotalElements()), HttpStatus.OK);
}
private DistributionSet findDistributionSetWithExceptionIfNotFound(final Long distributionSetId) {
final DistributionSet set = distributionSetManagement.findDistributionSetById(distributionSetId);
if (set == null) {
throw new EntityNotFoundException("DistributionSet with Id {" + distributionSetId + "} does not exist");
}
return set;
}
private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId) {
final SoftwareModule sm = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (sm == null) {
throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist");
}
return sm;
}
}

View File

@@ -0,0 +1,116 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyCreate;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRest;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypesRest;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class DistributionSetTypeMapper {
// private constructor, utility class
private DistributionSetTypeMapper() {
}
static List<DistributionSetType> smFromRequest(final SoftwareManagement softwareManagement,
final Iterable<DistributionSetTypeRequestBodyCreate> smTypesRest) {
final List<DistributionSetType> mappedList = new ArrayList<>();
for (final DistributionSetTypeRequestBodyCreate smRest : smTypesRest) {
mappedList.add(fromRequest(softwareManagement, smRest));
}
return mappedList;
}
static DistributionSetType fromRequest(final SoftwareManagement softwareManagement,
final DistributionSetTypeRequestBodyCreate smsRest) {
final DistributionSetType result = new DistributionSetType(smsRest.getKey(), smsRest.getName(),
smsRest.getDescription());
// Add mandatory
smsRest.getMandatorymodules().stream().map(mand -> {
final SoftwareModuleType smType = softwareManagement.findSoftwareModuleTypeById(mand.getId());
if (smType == null) {
throw new EntityNotFoundException("SoftwareModuleType with ID " + mand.getId() + " not found");
}
return smType;
}).forEach(softmType -> result.addMandatoryModuleType(softmType));
// Add optional
smsRest.getOptionalmodules().stream().map(opt -> {
final SoftwareModuleType smType = softwareManagement.findSoftwareModuleTypeById(opt.getId());
if (smType == null) {
throw new EntityNotFoundException("SoftwareModuleType with ID " + opt.getId() + " not found");
}
return smType;
}).forEach(softmType -> result.addOptionalModuleType(softmType));
return result;
}
static DistributionSetTypesRest toTypesResponse(final List<DistributionSetType> types) {
final DistributionSetTypesRest response = new DistributionSetTypesRest();
for (final DistributionSetType dsType : types) {
response.add(toResponse(dsType));
}
return response;
}
static List<DistributionSetTypeRest> toListResponse(final List<DistributionSetType> types) {
final List<DistributionSetTypeRest> response = new ArrayList<DistributionSetTypeRest>();
for (final DistributionSetType dsType : types) {
response.add(toResponse(dsType));
}
return response;
}
static DistributionSetTypeRest toResponse(final DistributionSetType type) {
final DistributionSetTypeRest result = new DistributionSetTypeRest();
RestModelMapper.mapNamedToNamed(result, type);
result.setKey(type.getKey());
result.setModuleId(type.getId());
result.add(linkTo(methodOn(DistributionSetTypeResource.class).getDistributionSetType(result.getModuleId()))
.withRel("self"));
result.add(linkTo(methodOn(DistributionSetTypeResource.class).getMandatoryModules(result.getModuleId()))
.withRel(RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULES));
result.add(linkTo(methodOn(DistributionSetTypeResource.class).getOptionalModules(result.getModuleId()))
.withRel(RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULES));
return result;
}
}

View File

@@ -0,0 +1,495 @@
/**
* 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.rest.resource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.IdRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionPagedList;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypePagedList;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyCreate;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyUpdate;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRest;
import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypesRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
*
*
*
*
*/
@RestController
@Transactional(readOnly = true)
@RequestMapping(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)
@Api(value = "distributionsettypes", description = "Distribution Set Types Management API")
public class DistributionSetTypeResource {
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private DistributionSetManagement distributionSetManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles the GET request of retrieving all {@link DistributionSetType}s
* within SP.
*
* @param pagingOffsetParam
* the offset of list of modules for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
*
* @return a list of all {@link DistributionSetType} for a defined or
* default page request with status OK. The response is always
* paged. In any failure the JsonResponseExceptionHandler is
* handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetTypePagedList.class, value = "Get distribution det types", notes = "Handles the GET request of retrieving all distribution set types within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<DistributionSetTypePagedList> getTypes(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>id=1,key=vhicletypex2015</td><td>Distribution Set Types with id 1 or key vhicletypex2015</td></tr>"
+ "<tr><td>name=VehicleSeriesA</td><td>Distribution Set Types with name VehicleSeriesA</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSetType> findModuleTypessAll;
Long countModulesAll;
if (rsqlParam != null) {
findModuleTypessAll = distributionSetManagement.findDistributionSetTypesByPredicate(
RSQLUtility.parse(rsqlParam, DistributionSetTypeFields.class, entityManager), pageable);
countModulesAll = ((Page<DistributionSetType>) findModuleTypessAll).getTotalElements();
} else {
findModuleTypessAll = distributionSetManagement.findDistributionSetTypesAll(pageable);
countModulesAll = distributionSetManagement.countDistributionSetTypesAll();
}
final List<DistributionSetTypeRest> rest = DistributionSetTypeMapper.toListResponse(findModuleTypessAll
.getContent());
return new ResponseEntity<>(new DistributionSetTypePagedList(rest, countModulesAll), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a single
* {@link DistributionSetType} within SP.
*
* @param distributionSetTypeId
* the ID of the module type to retrieve
*
* @return a single softwareModule with status OK.
* @throws EntityNotFoundException
* in case no with the given {@code softwareModuleId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetTypeRest.class, value = "Get distribution set type", notes = "Handles the GET request of retrieving a single distribution set type within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not found distribution set type", response = ExceptionInfo.class))
public ResponseEntity<DistributionSetTypeRest> getDistributionSetType(@PathVariable final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return new ResponseEntity<>(DistributionSetTypeMapper.toResponse(foundType), HttpStatus.OK);
}
/**
* Handles the DELETE request for a single Distribution Set Type within SP.
*
* @param distributionSetTypeId
* the ID of the module to retrieve
* @return status OK if delete as sucessfull.
*
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}")
@ApiOperation(value = "Delete distribution set type", notes = "Handles the DELETE request for a single distribution set type within SP. Required Permission: "
+ SpPermission.DELETE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not found distribution set type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteDistributionSetType(@PathVariable final Long distributionSetTypeId) {
final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
distributionSetManagement.deleteDistributionSetType(module);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the PUT request of updating a Distribution Set Type within SP.
*
* @param distributionSetTypeId
* the ID of the software module in the URL
* @param restDistributionSetType
* the module type to be updated.
* @return status OK if update is successful
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetTypeId}", consumes = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(value = "Update distribution set type", notes = "Handles the PUT request for a single distribution set type within SP. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<DistributionSetTypeRest> updateDistributionSetType(
@PathVariable final Long distributionSetTypeId,
@RequestBody final DistributionSetTypeRequestBodyUpdate restDistributionSetType) {
final DistributionSetType type = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
// only description can be modified
if (restDistributionSetType.getDescription() != null) {
type.setDescription(restDistributionSetType.getDescription());
}
final DistributionSetType updatedDistributionSetType = distributionSetManagement
.updateDistributionSetType(type);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(DistributionSetTypeMapper.toResponse(updatedDistributionSetType), HttpStatus.OK);
}
/**
* Handles the POST request of creating new {@link DistributionSetType}s
* within SP. The request body must always be a list of types.
*
* @param distributionSetTypes
* the modules to be created.
* @return In case all modules could successful created the ResponseEntity
* with status code 201 - Created but without ResponseBody. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetTypesRest.class, value = "Create distribution set types", notes = "Handles the POST request for creating new distribution set types within SP. The request body must always be a list of types. Required Permission: "
+ SpPermission.CREATE_REPOSITORY)
@ApiResponses({
@ApiResponse(code = 404, message = "Not Found Distribution Set Type", response = ExceptionInfo.class),
@ApiResponse(code = 409, message = "Conflict Distribution Set Type already exists", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<DistributionSetTypesRest> createDistributionSetTypes(
@RequestBody final List<DistributionSetTypeRequestBodyCreate> distributionSetTypes) {
final List<DistributionSetType> createdSoftwareModules = distributionSetManagement
.createDistributionSetTypes(DistributionSetTypeMapper.smFromRequest(softwareManagement,
distributionSetTypes));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(DistributionSetTypeMapper.toTypesResponse(createdSoftwareModules),
HttpStatus.CREATED);
}
private DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(final Long distributionSetTypeId) {
final DistributionSetType module = distributionSetManagement.findDistributionSetTypeById(distributionSetTypeId);
if (module == null) {
throw new EntityNotFoundException("DistributionSetType with Id {" + distributionSetTypeId
+ "} does not exist");
}
return module;
}
/**
* Handles the GET request of retrieving the list of mandatory software
* module types in that distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @return Unpaged list of module types and OK in case of success.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Lists all mandatory software module types", notes = "Handles the GET request of retrieving the list of mandatory software module types in that distribution set type. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleTypesRest> getMandatoryModules(@PathVariable final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleTypesRest rest = new SoftwareModuleTypesRest();
rest.addAll(SoftwareModuleTypeMapper.toListResponse(foundType.getMandatoryModuleTypes()));
return new ResponseEntity<>(rest, HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the single mandatory software
* module type in that distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param softwareModuleTypeId
* of {@link SoftwareModuleType}.
* @return Unpaged list of module types and OK in case of success.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES + "/{softwareModuleTypeId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Retrieve mandatory software module type", notes = " Handles the GET request of retrieving the single mandatory software module type in that distribution set type. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleTypeRest> getMandatoryModule(@PathVariable final Long distributionSetTypeId,
@PathVariable final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsMandatoryModuleType(foundSmType)) {
throw new EntityNotFoundException(
"Software module with given ID is not part of this distribution set type!");
}
return new ResponseEntity<SoftwareModuleTypeRest>(SoftwareModuleTypeMapper.toResponse(foundSmType),
HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the single optional software module
* type in that distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param softwareModuleTypeId
* of {@link SoftwareModuleType}.
* @return Unpaged list of module types and OK in case of success.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES + "/{softwareModuleTypeId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Retrieve optional software module type", notes = " Handles the GET request of retrieving the single optional software module type in that distribution set type. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleTypeRest> getOptionalModule(@PathVariable final Long distributionSetTypeId,
@PathVariable final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsOptionalModuleType(foundSmType)) {
throw new EntityNotFoundException(
"Software module with given ID is not part of this distribution set type!");
}
return new ResponseEntity<SoftwareModuleTypeRest>(SoftwareModuleTypeMapper.toResponse(foundSmType),
HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the list of optional software
* module types in that distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @return Unpaged list of module types and OK in case of success.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Lists all optional software module types", notes = "Handles the GET request of retrieving the list of optional software module types in that distribution set type. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleTypesRest> getOptionalModules(@PathVariable final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleTypesRest rest = new SoftwareModuleTypesRest();
rest.addAll(SoftwareModuleTypeMapper.toListResponse(foundType.getOptionalModuleTypes()));
return new ResponseEntity<>(rest, HttpStatus.OK);
}
/**
* Handles DELETE request for removing a mandatory module from the
* {@link DistributionSetType}.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param softwareModuleTypeId
* of the {@link SoftwareModuleType} to remove
*
* @return OK if the request was successful
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES + "/{softwareModuleTypeId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Remove mandatory module from distribution set type", notes = "Handles the GET request of retrieving the list of software module types in that distribution set. "
+ "Note that a DS type cannot be changed after it has been used by a DS. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY + " and " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> removeMandatoryModule(@PathVariable final Long distributionSetTypeId,
@PathVariable final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
foundType.removeModuleType(softwareModuleTypeId);
distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles DELETE request for removing an optional module from the
* {@link DistributionSetType}.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param softwareModuleTypeId
* of the {@link SoftwareModuleType} to remove
*
* @return OK if the request was successful
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES + "/{softwareModuleTypeId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "Remove optional module from distribution set type", notes = "Handles DELETE request for removing an optional module from the distribution set type."
+ "Note that a DS type cannot be changed after it has been used by a DS. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY + " and " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found distribution set type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> removeOptionalModule(@PathVariable final Long distributionSetTypeId,
@PathVariable final Long softwareModuleTypeId) {
return removeMandatoryModule(distributionSetTypeId, softwareModuleTypeId);
}
/**
* Handles the POST request for adding a mandatory software module type to a
* distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param smtId
* of the {@link SoftwareModuleType} to add
*
* @return OK if the request was successful
*/
@RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = Void.class, value = "Add mandatory software module type", notes = "Handles the POST request for adding a mandatory software module type to a distribution set type."
+ "Note that a DS type cannot be changed after it has been used by a DS. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY + " and " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found DS type or SP type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> addMandatoryModule(@PathVariable final Long distributionSetTypeId,
@RequestBody @ApiParam(value = "Software module type ID", required = true) final IdRest smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addMandatoryModuleType(smType);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the POST request for adding an optional software module type to a
* distribution set type.
*
* @param distributionSetTypeId
* of the {@link DistributionSetType}.
* @param smtId
* of the {@link SoftwareModuleType} to add
*
* @return OK if the request was successful
*/
@RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/"
+ RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = Void.class, value = "Add optional software module type", notes = "Handles the POST request for adding an optional software module type to a distribution set type."
+ "Note that a DS type cannot be changed after it has been used by a DS. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY + " and " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found DS type or SP type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> addOptionalModule(@PathVariable final Long distributionSetTypeId,
@RequestBody @ApiParam(value = "Software module type ID", required = true) final IdRest smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addOptionalModuleType(smType);
return new ResponseEntity<>(HttpStatus.OK);
}
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId);
if (module == null) {
throw new EntityNotFoundException("SoftwareModuleType with Id {" + softwareModuleTypeId
+ "} does not exist");
}
return module;
}
}

View File

@@ -0,0 +1,103 @@
/**
* 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.rest.resource;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* A resource for download artifacts.
*
*
*
*/
@RequestMapping(RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE)
@RestController
public class DownloadResource {
private static final Logger LOGGER = LoggerFactory.getLogger(DownloadResource.class);
@Autowired
private ArtifactRepository artifactRepository;
@Autowired
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE)
private Cache cache;
/**
* Handles the GET request for downloading an artifact.
*
* @param downloadId
* the generated download id
* @param response
* of the servlet
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING)
@ResponseBody
public ResponseEntity<Void> downloadArtifactByDownloadId(@PathVariable final String downloadId,
final HttpServletResponse response) {
try {
final ValueWrapper cacheWrapper = cache.get(downloadId);
if (cacheWrapper == null) {
LOGGER.warn("Download Id {} could not be found", downloadId);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
final DownloadArtifactCache artifactCache = (DownloadArtifactCache) cacheWrapper.get();
DbArtifact artifact = null;
switch (artifactCache.getDownloadType()) {
case BY_SHA1:
artifact = artifactRepository.getArtifactBySha1(artifactCache.getId());
break;
default:
LOGGER.warn("Download Type {} not supported", artifactCache.getDownloadType());
break;
}
if (artifact == null) {
LOGGER.warn("Artifact with cached id {} and download type {} could not be found.",
artifactCache.getId(), artifactCache.getDownloadType());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
try {
IOUtils.copy(artifact.getFileInputStream(), response.getOutputStream());
} catch (final IOException e) {
LOGGER.error("Cannot copy streams", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
} finally {
cache.evict(downloadId);
}
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,34 @@
/**
* 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.rest.resource;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
/**
* Annotation to enable {@link ComponentScan} in the resource package to setup
* all {@link Controller} annotated classes and setup the REST-Resources for the
* Management API.
*
*
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface EnableRestResources {
}

View File

@@ -0,0 +1,36 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception which is thrown in case an request body is not well formaned and
* cannot be parsed.
*
*
*
*
*/
public class MessageNotReadableException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new MessageNotReadableException with
* {@link SpServerError#SP_REST_BODY_NOT_READABLE} error.
*/
public MessageNotReadableException() {
super(SpServerError.SP_REST_BODY_NOT_READABLE);
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.rest.resource;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
/**
* An implementation of the {@link PageRequest} which is offset based by means
* the offset is given and not the page number as in the original
* {@link PageRequest} implemntation where the offset is generated. Due that the
* REST-API is working with {@code offset} and {@code limit} parameter we need
* an offset based page request for JPA.
*
*
*
*
*/
public final class OffsetBasedPageRequest extends PageRequest {
private static final long serialVersionUID = 1L;
private final int offset;
/**
* Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed,
* thus providing 0 for {@code offset} will return the first entry.
*
* @param offset
* zero-based offset index.
* @param limit
* the limit of the page to be returned.
*/
public OffsetBasedPageRequest(final int offset, final int limit) {
this(offset, limit, null);
}
/**
* Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed,
* thus providing 0 for {@code offset} will return the first entry.
*
* @param offset
* zero-based offset index.
* @param limit
* the limit of the page to be returned.
* @param sort
* sort can be {@literal null}.
*/
public OffsetBasedPageRequest(final int offset, final int limit, final Sort sort) {
super(0, limit, sort);
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public String toString() {
return "OffsetBasedPageRequest [offset=" + offset + ", getPageSize()=" + getPageSize() + ", getPageNumber()="
+ getPageNumber() + "]";
}
}

View File

@@ -0,0 +1,130 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.TargetFields;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
/**
* Utility class for for paged body generation.
*
*
*
*
*
*
*/
public final class PagingUtility {
/*
* utility constructor private.
*/
private PagingUtility() {
}
static int sanitizeOffsetParam(final int offset) {
if (offset < 0) {
return Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET);
}
return offset;
}
static int sanitizePageLimitParam(final int pageLimit) {
if (pageLimit < 1) {
return Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT);
} else if (pageLimit > RestConstants.REQUEST_PARAMETER_PAGING_MAX_LIMIT) {
return RestConstants.REQUEST_PARAMETER_PAGING_MAX_LIMIT;
}
return pageLimit;
}
static Sort sanitizeTargetSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(TargetFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, TargetFields.NAME.getFieldName());
}
return sorting;
}
static Sort sanitizeSoftwareModuleSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(SoftwareModuleFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, SoftwareModuleFields.NAME.getFieldName());
}
return sorting;
}
static Sort sanitizeDistributionSetSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(DistributionSetFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, DistributionSetFields.NAME.getFieldName());
}
return sorting;
}
static Sort sanitizeActionSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(ActionFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, ActionFields.ID.getFieldName());
}
return sorting;
}
static Sort sanitizeActionStatusSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(ActionStatusFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, ActionStatusFields.ID.getFieldName());
}
return sorting;
}
static Sort sanitizeDistributionSetMetadataSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(DistributionSetMetadataFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, DistributionSetMetadataFields.KEY.getFieldName());
}
return sorting;
}
static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, SoftwareModuleMetadataFields.KEY.getFieldName());
}
return sorting;
}
}

View File

@@ -0,0 +1,169 @@
/**
* 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.rest.resource;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.tomcat.util.http.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MultipartException;
/**
* General controller advice for exception handling.
*
*
*
*
*/
@ControllerAdvice
public class ResponseExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(ResponseExceptionHandler.class);
private static final Map<SpServerError, HttpStatus> ERROR_TO_HTTP_STATUS = new HashMap<>();
private static final HttpStatus DEFAULT_RESPONSE_STATUS = HttpStatus.INTERNAL_SERVER_ERROR;
static {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_NOT_EXISTS, HttpStatus.NOT_FOUND);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_ALRREADY_EXISTS, HttpStatus.CONFLICT);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_ENTITY_READ_ONLY, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_SORT_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_PARAM_INVALID_FIELD, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_INSUFFICIENT_PERMISSION, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_DELETE_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ARTIFACT_LOAD_FAILED, HttpStatus.INTERNAL_SERVER_ERROR);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_STATUS_TO_MANY_ENTRIES, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ATTRIBUTES_TO_MANY_ENTRIES, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ACTION_NOT_CANCELABLE, HttpStatus.METHOD_NOT_ALLOWED);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_CREATION_FAILED_MISSING_MODULE, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_MODULE_UNSUPPORTED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED);
}
private static HttpStatus getStatusOrDefault(final SpServerError error) {
return ERROR_TO_HTTP_STATUS.getOrDefault(error, DEFAULT_RESPONSE_STATUS);
}
/**
* method for handling exception of type SpServerRtException. Called by the
* Spring-Framework for exception handling.
*
* @param request
* the Http request
* @param ex
* the exception which occurred
*
* @return the entity to be responded containing the exception information
* as entity.
*/
@ExceptionHandler(SpServerRtException.class)
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request,
final Exception ex) {
LOG.debug("Handling exception of request {}", request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
final HttpStatus responseStatus;
response.setMessage(ex.getMessage());
response.setExceptionClass(ex.getClass().getName());
if (ex instanceof SpServerRtException) {
responseStatus = getStatusOrDefault(((SpServerRtException) ex).getError());
response.setErrorCode(((SpServerRtException) ex).getError().getKey());
} else {
responseStatus = DEFAULT_RESPONSE_STATUS;
}
return new ResponseEntity<ExceptionInfo>(response, responseStatus);
}
/**
* Method for handling exception of type HttpMessageNotReadableException
* which is thrown in case the request body is not well formed and cannot be
* deserialized. Called by the Spring-Framework for exception handling.
*
* @param request
* the Http request
* @param ex
* the exception which occurred
* @return the entity to be responded containing the exception information
* as entity.
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ExceptionInfo> handleHttpMessageNotReadableException(final HttpServletRequest request,
final Exception ex) {
LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
response.setErrorCode(SpServerError.SP_REST_BODY_NOT_READABLE.getKey());
response.setMessage(SpServerError.SP_REST_BODY_NOT_READABLE.getMessage());
response.setExceptionClass(MessageNotReadableException.class.getName());
return new ResponseEntity<ExceptionInfo>(response, HttpStatus.BAD_REQUEST);
}
/**
* Method for handling exception of type {@link MultipartException} which is
* thrown in case the request body is not well formed and cannot be
* deserialized. Called by the Spring-Framework for exception handling.
*
* @param request
* the Http request
* @param ex
* the exception which occurred
* @return the entity to be responded containing the exception information
* as entity.
*/
@ExceptionHandler(MultipartException.class)
public ResponseEntity<ExceptionInfo> handleFileLimitExceededException(final HttpServletRequest request,
final Exception ex) {
LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
if (searchForCause(ex, FileSizeLimitExceededException.class)) {
response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getKey());
response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FILE_LIMIT_EXCEEDED.getMessage());
response.setExceptionClass(FileSizeLimitExceededException.class.getName());
} else {
response.setErrorCode(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getKey());
response.setMessage(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getMessage());
response.setExceptionClass(MultipartException.class.getName());
}
return new ResponseEntity<ExceptionInfo>(response, HttpStatus.BAD_REQUEST);
}
private static boolean searchForCause(final Throwable t, final Class<?> lookFor) {
if (t != null && t.getCause() != null) {
if (t.getCause().getClass().isAssignableFrom(lookFor)) {
return true;
} else {
return searchForCause(t.getCause(), lookFor);
}
}
return false;
}
}

View File

@@ -0,0 +1,162 @@
/**
* 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.rest.resource;
/**
* Constants for RESTful API.
*
*
*
*/
public final class RestConstants {
/**
* API version definition. We are using only major versions.
*/
public static final String API_VERSION = "v1";
/**
* The base URL mapping for the spring acuator management context path.
*/
public static final String BASE_SYSTEM_MAPPING = "/system";
/**
* URL mapping for system admin operations.
*/
public static final String SYSTEM_ADMIN_MAPPING = BASE_SYSTEM_MAPPING + "/admin";
/**
* The base URL mapping of the SP rest resources.
*/
public static final String BASE_V1_REQUEST_MAPPING = "/rest/v1";
/**
* The default limit parameter in case the limit parameter is not present in
* the request.
*
* @see #REQUEST_PARAMETER_PAGING_LIMIT
*/
public static final int REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE = 50;
/**
* String representation of
* {@link #REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE}.
*/
public static final String REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT = "50";
/**
* The default offset parameter in case the offset parameter is not present
* in the request.
*
* @see #REQUEST_PARAMETER_PAGING_OFFSET
*/
public static final String REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET = "0";
/**
* Limit http parameter for the limitation of returned values for a paged
* request.
*/
public static final String REQUEST_PARAMETER_PAGING_LIMIT = "limit";
/**
* The maximum limit of entities returned by rest resources.
*/
public static final int REQUEST_PARAMETER_PAGING_MAX_LIMIT = 500;
/**
* Paging http parameter for the offset for a paged request.
*/
public static final String REQUEST_PARAMETER_PAGING_OFFSET = "offset";
/**
* The request parameter for sorting. The value of the sort parameter must
* be in the following pattern. Example:
* http://www.bosch.com/iap/sp/rest/targets?sort=field_1:ASC,field_2:DESC,
* field_3:ASC
*/
public static final String REQUEST_PARAMETER_SORTING = "sort";
/**
* The request parameter for searching. The value of the search parameter
* must be in the FIQL syntax.
*/
public static final String REQUEST_PARAMETER_SEARCH = "q";
/**
* The target URL mapping, href link for assigned distribution set.
*/
public static final String TARGET_V1_ASSIGNED_DISTRIBUTION_SET = "assignedDS";
/**
* The target URL mapping, href link for installed distribution set.
*/
public static final String TARGET_V1_INSTALLED_DISTRIBUTION_SET = "installedDS";
/**
* The target URL mapping, href link for target attributes.
*/
public static final String TARGET_V1_ATTRIBUTES = "attributes";
/**
* The target URL mapping, href link for target actions.
*/
public static final String TARGET_V1_ACTIONS = "actions";
/**
* The target URL mapping, href link for canceled actions.
*/
public static final String TARGET_V1_CANCELED_ACTION = "canceledaction";
/**
* The target URL mapping, href link for canceled actions.
*/
public static final String TARGET_V1_ACTION_STATUS = "status";
/**
* The target URL mapping rest resource.
*/
public static final String TARGET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targets";
/**
* The software module URL mapping rest resource.
*/
public static final String SOFTWAREMODULE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/softwaremodules";
/**
* The software module type URL mapping rest resource.
*/
public static final String SOFTWAREMODULETYPE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/softwaremoduletypes";
public static final String DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING
+ "/distributionsettypes";
/**
* The software module URL mapping rest resource.
*/
public static final String DISTRIBUTIONSET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsets";
/**
* The target URL mapping, href link for artifact download.
*/
public static final String SOFTWAREMODULE_V1_ARTIFACT = "artifacts";
/**
* The target URL mapping, href link for type information.
*/
public static final String SOFTWAREMODULE_V1_TYPE = "type";
public static final String DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULES = "optionalmodules";
public static final String DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULES = "mandatorymodules";
public static final String DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES = "optionalmoduletypes";
public static final String DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES = "mandatorymoduletypes";
public static final String DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE = "/api/" + API_VERSION + "/downloadserver/";
public static final String DOWNLOAD_ID_V1_REQUEST_MAPPING = "downloadId/{downloadId}";
// constant class, private constructor.
private RestConstants() {
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.rest.resource.model.BaseEntityRest;
import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class RestModelMapper {
// private constructor, utility class
private RestModelMapper() {
}
static void mapBaseToBase(final BaseEntityRest response, final BaseEntity base) {
response.setCreatedBy(base.getCreatedBy());
response.setLastModifiedBy(base.getLastModifiedBy());
if (base.getCreatedAt() != null) {
response.setCreatedAt(base.getCreatedAt());
}
if (base.getLastModifiedAt() != null) {
response.setLastModifiedAt(base.getLastModifiedAt());
}
}
static void mapNamedToNamed(final NamedEntityRest response, final NamedEntity base) {
mapBaseToBase(response, base);
response.setName(base.getName());
response.setDescription(base.getDescription());
}
}

View File

@@ -0,0 +1,186 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.resource.model.MetadataRest;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactsRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulesRest;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class SoftwareModuleMapper {
private SoftwareModuleMapper() {
// Utility class
}
private static SoftwareModuleType getSoftwareModuleTypeFromKeyString(final String type,
final SoftwareManagement softwareManagement) {
final SoftwareModuleType smType = softwareManagement.findSoftwareModuleTypeByKey(type.trim());
if (smType == null) {
throw new EntityNotFoundException(type.trim());
}
return smType;
}
static SoftwareModule fromRequest(final SoftwareModuleRequestBodyPost smsRest,
final SoftwareManagement softwareManagement) {
return new SoftwareModule(getSoftwareModuleTypeFromKeyString(smsRest.getType(), softwareManagement),
smsRest.getName(), smsRest.getVersion(), smsRest.getDescription(), smsRest.getVendor());
}
static List<SoftwareModuleMetadata> fromRequestSwMetadata(final SoftwareModule sw,
final List<MetadataRest> metadata) {
final List<SoftwareModuleMetadata> mappedList = new ArrayList<>(metadata.size());
for (final MetadataRest metadataRest : metadata) {
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(new SoftwareModuleMetadata(metadataRest.getKey(), sw, metadataRest.getValue()));
}
return mappedList;
}
static List<SoftwareModule> smFromRequest(final Iterable<SoftwareModuleRequestBodyPost> smsRest,
final SoftwareManagement softwareManagement) {
final List<SoftwareModule> mappedList = new ArrayList<>();
for (final SoftwareModuleRequestBodyPost smRest : smsRest) {
mappedList.add(fromRequest(smRest, softwareManagement));
}
return mappedList;
}
static List<SoftwareModuleRest> toResponse(final List<SoftwareModule> baseSoftareModules) {
final List<SoftwareModuleRest> mappedList = new ArrayList<>();
if (baseSoftareModules != null) {
for (final SoftwareModule target : baseSoftareModules) {
final SoftwareModuleRest response = toResponse(target);
mappedList.add(response);
}
}
return mappedList;
}
static SoftwareModulesRest toResponseSoftwareModules(final Iterable<SoftwareModule> softwareModules) {
final SoftwareModulesRest response = new SoftwareModulesRest();
for (final SoftwareModule softwareModule : softwareModules) {
response.add(toResponse(softwareModule));
}
return response;
}
static List<MetadataRest> toResponseSwMetadata(final List<SoftwareModuleMetadata> metadata) {
final List<MetadataRest> mappedList = new ArrayList<>(metadata.size());
for (final SoftwareModuleMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseSwMetadata(distributionSetMetadata));
}
return mappedList;
}
static MetadataRest toResponseSwMetadata(final SoftwareModuleMetadata metadata) {
final MetadataRest metadataRest = new MetadataRest();
metadataRest.setKey(metadata.getId().getKey());
metadataRest.setValue(metadata.getValue());
return metadataRest;
}
static SoftwareModuleRest toResponse(final SoftwareModule baseSofwareModule) {
if (baseSofwareModule == null) {
return null;
}
final SoftwareModuleRest response = new SoftwareModuleRest();
RestModelMapper.mapNamedToNamed(response, baseSofwareModule);
response.setModuleId(baseSofwareModule.getId());
response.setVersion(baseSofwareModule.getVersion());
response.setType(baseSofwareModule.getType().getKey());
response.setVendor(baseSofwareModule.getVendor());
response.add(linkTo(methodOn(SoftwareModuleResource.class).getArtifacts(response.getModuleId()))
.withRel(RestConstants.SOFTWAREMODULE_V1_ARTIFACT));
response.add(linkTo(methodOn(SoftwareModuleResource.class).getSoftwareModule(response.getModuleId()))
.withRel("self"));
response.add(linkTo(
methodOn(SoftwareModuleTypeResource.class).getSoftwareModuleType(baseSofwareModule.getType().getId()))
.withRel(RestConstants.SOFTWAREMODULE_V1_TYPE));
return response;
}
/**
* @param artifact
* @return
*/
static ArtifactRest toResponse(final Artifact artifact) {
final ArtifactRest.ArtifactType type = artifact instanceof LocalArtifact ? ArtifactRest.ArtifactType.LOCAL
: ArtifactRest.ArtifactType.EXTERNAL;
final ArtifactRest artifactRest = new ArtifactRest();
artifactRest.setType(type);
artifactRest.setArtifactId(artifact.getId());
artifactRest.setSize(artifact.getSize());
artifactRest.setHashes(new ArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
if (artifact instanceof LocalArtifact) {
artifactRest.setProvidedFilename(((LocalArtifact) artifact).getFilename());
}
RestModelMapper.mapBaseToBase(artifactRest, artifact);
artifactRest.add(linkTo(methodOn(SoftwareModuleResource.class).getArtifact(artifact.getSoftwareModule().getId(),
artifact.getId())).withRel("self"));
if (artifact instanceof LocalArtifact) {
artifactRest.add(
linkTo(methodOn(SoftwareModuleResource.class).downloadArtifact(artifact.getSoftwareModule().getId(),
artifact.getId(), null, null)).withRel("download"));
}
return artifactRest;
}
static ArtifactsRest artifactsToResponse(final List<Artifact> artifacts) {
final ArtifactsRest mappedList = new ArtifactsRest();
if (artifacts != null) {
for (final Artifact artifact : artifacts) {
final ArtifactRest response = toResponse(artifact);
mappedList.add(response);
}
}
return mappedList;
}
}

View File

@@ -0,0 +1,603 @@
/**
* 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.rest.resource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import java.util.List;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.MetadataRest;
import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactsRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulePagedList;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPut;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulesRest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
*
*
*
*
*/
@RestController
@Transactional(readOnly = true)
@RequestMapping(RestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING)
@Api(value = "softwaremodules", description = "Software Modules Management API")
public class SoftwareModuleResource {
private static final Logger LOG = LoggerFactory.getLogger(SoftwareModuleResource.class);
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles POST request for artifact upload.
*
* @param softwareModuleId
* of the parent {@link SoftwareModule}
* @param file
* that has to be uploaded
* @param optionalFileName
* to override {@link MultipartFile#getOriginalFilename()}
* @param md5Sum
* checksum for uploaded content check
* @param sha1Sum
* checksum for uploaded content check
*
* @return {@link ResponseEntity} if status {@link HttpStatus#CREATED} if
* successful
*/
@RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/artifacts", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ArtifactRest.class, value = "Upload Artifact", notes = "Handles POST request for artifact upload. Required Permission: "
+ SpPermission.CREATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<ArtifactRest> uploadArtifact(@PathVariable final Long softwareModuleId,
@RequestParam("file") final MultipartFile file,
@RequestParam(value = "filename", required = false) final String optionalFileName,
@RequestParam(value = "md5sum", required = false) final String md5Sum,
@RequestParam(value = "sha1sum", required = false) final String sha1Sum) {
Artifact result = null;
if (!file.isEmpty()) {
String fileName = optionalFileName;
if (null == fileName) {
fileName = file.getOriginalFilename();
}
try {
result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId, fileName,
md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(),
false, file.getContentType());
} catch (final IOException e) {
LOG.error("Failed to store artifact", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<ArtifactRest>(SoftwareModuleMapper.toResponse(result), HttpStatus.CREATED);
}
/**
* Handles the GET request of retrieving all meta data of artifacts assigned
* to a software module.
*
* @param softwareModuleId
* of the parent {@link SoftwareModule}
*
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ArtifactsRest.class, value = "List Artifacts Metadata", notes = "Handles the GET request of retrieving all meta data of artifacts assigned to a software module. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@ResponseBody
public ResponseEntity<ArtifactsRest> getArtifacts(@PathVariable final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return new ResponseEntity<ArtifactsRest>(SoftwareModuleMapper.artifactsToResponse(module.getArtifacts()),
HttpStatus.OK);
}
/**
* Handles the GET request for downloading an artifact.
*
* @param softwareModuleId
* of the parent {@link SoftwareModule}
* @param artifactId
* of the related {@link LocalArtifact}
* @param servletResponse
* of the servlet
* @param request
* of the client
*
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}/download")
@ApiOperation(response = Void.class, value = "Download Artifact", notes = "Handles the GET request for downloading an artifact. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or Artifact", response = ExceptionInfo.class))
@ResponseBody
public ResponseEntity<Void> downloadArtifact(@PathVariable final Long softwareModuleId,
@PathVariable final Long artifactId, final HttpServletResponse servletResponse,
final HttpServletRequest request) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
if (null == module || !module.getLocalArtifact(artifactId).isPresent()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
final LocalArtifact artifact = module.getLocalArtifact(artifactId).get();
final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact);
final String ifMatch = request.getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
return RestResourceConversionHelper.writeFileResponse(artifact, servletResponse, request, file);
}
/**
* Handles the GET request of retrieving a single Artifact meta data
* request.
*
* @param softwareModuleId
* of the parent {@link SoftwareModule}
* @param artifactId
* of the related {@link LocalArtifact}
*
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ArtifactRest.class, value = "Get Artifact Metadata", notes = "Handles the GET request of retrieving a single Artifact meta data request. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or Artifact", response = ExceptionInfo.class))
@ResponseBody
public ResponseEntity<ArtifactRest> getArtifact(@PathVariable final Long softwareModuleId,
@PathVariable final Long artifactId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
return new ResponseEntity<ArtifactRest>(SoftwareModuleMapper.toResponse(module.getLocalArtifact(artifactId)
.get()), HttpStatus.OK);
}
/**
* Handles the DELETE request for a single SoftwareModule within SP.
*
* @param softwareModuleId
* the ID of the module that has the artifact
* @param artifactId
* of the artifact to be deleted
*
* @return status OK if delete as successful.
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/artifacts/{artifactId}")
@ApiOperation(value = "Delete Artifact", notes = "Handles the DELETE request for a single SoftwareModule within SP. Required Permission: "
+ SpPermission.DELETE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or Artifact", response = ExceptionInfo.class))
@ResponseBody
@Transactional
public ResponseEntity<Void> deleteArtifact(@PathVariable final Long softwareModuleId,
@PathVariable final Long artifactId) {
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
artifactManagement.deleteLocalArtifact(artifactId);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the GET request of retrieving all softwaremodules within SP.
*
* @param pagingOffsetParam
* the offset of list of modules for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
*
* @return a list of all modules for a defined or default page request with
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModulePagedList.class, value = "Get Software Modules", notes = "Handles the GET request of retrieving all softwaremodules within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<SoftwareModulePagedList> getSoftwareModules(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>version==1.0.0</td><td>softwaremodules with version 1.0.0</td></tr>"
+ "<tr><td>version=ge=2.0.0</td><td>softwaremodules with version greater-equal 2.0.0</td></tr>"
+ "<tr><td>version==1.0.0;type=application</td><td>softwaremodules with version 1.0.0 and type application</td></tr>"
+ "<tr><td>id=1,type=os</td><td>softwaremodules with id 1 or type os</td></tr>" + "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<SoftwareModule> findModulesAll;
Long countModulesAll;
if (rsqlParam != null) {
findModulesAll = softwareManagement.findSoftwareModulesByPredicate(
RSQLUtility.parse(rsqlParam, SoftwareModuleFields.class, entityManager), pageable);
countModulesAll = ((Page<SoftwareModule>) findModulesAll).getTotalElements();
} else {
findModulesAll = softwareManagement.findSoftwareModulesAll(pageable);
countModulesAll = softwareManagement.countSoftwareModulesAll();
}
final List<SoftwareModuleRest> rest = SoftwareModuleMapper.toResponse(findModulesAll.getContent());
return new ResponseEntity<>(new SoftwareModulePagedList(rest, countModulesAll), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a single software module within SP.
*
* @param softwareModuleId
* the ID of the module to retrieve
*
* @return a single softwareModule with status OK.
* @throws EntityNotFoundException
* in case no with the given {@code softwareModuleId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleRest.class, value = "Get Software Module", notes = "Handles the GET request of retrieving a single softwaremodule within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleRest> getSoftwareModule(@PathVariable final Long softwareModuleId) {
final SoftwareModule findBaseSoftareModule = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return new ResponseEntity<>(SoftwareModuleMapper.toResponse(findBaseSoftareModule), HttpStatus.OK);
}
/**
* Handles the POST request of creating new softwaremodules within SP. The
* request body must always be a list of modules. The requests is delgating
* to the {@link SoftwareManagement#createSoftwareModule(Iterable)}.
*
* @param softwareModules
* the modules to be created.
* @return In case all modules could successful created the ResponseEntity
* with status code 201 - Created but without ResponseBody. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModulesRest.class, value = "Create Software Modules", notes = "Handles the POST request of creating new software modules within SP. The request body must always be a list of modules. Required Permission: "
+ SpPermission.CREATE_REPOSITORY)
@ApiResponses({
@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class),
@ApiResponse(code = 409, message = "Conflict Software Module already exists", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<SoftwareModulesRest> createSoftwareModules(
@RequestBody final List<SoftwareModuleRequestBodyPost> softwareModules) {
LOG.debug("creating {} softwareModules", softwareModules.size());
final Iterable<SoftwareModule> createdSoftwareModules = softwareManagement
.createSoftwareModule(SoftwareModuleMapper.smFromRequest(softwareModules, softwareManagement));
LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(SoftwareModuleMapper.toResponseSoftwareModules(createdSoftwareModules),
HttpStatus.CREATED);
}
/**
* Handles the PUT request of updating a software module within SP.
* {@link SoftwareManagement#createSoftwareModule(Iterable)}.
*
* @param softwareModuleId
* the ID of the software module in the URL
* @param restSoftwareModule
* the modules to be updated.
* @return status OK if update is successful
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(value = "Update Software Module", notes = "Handles the PUT request for a single softwaremodule within SP. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<SoftwareModuleRest> updateSoftwareModule(@PathVariable final Long softwareModuleId,
@RequestBody final SoftwareModuleRequestBodyPut restSoftwareModule) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
// only description and vendor can be modified
if (restSoftwareModule.getDescription() != null) {
module.setDescription(restSoftwareModule.getDescription());
}
if (restSoftwareModule.getVendor() != null) {
module.setVendor(restSoftwareModule.getVendor());
}
final SoftwareModule updateSoftwareModule = softwareManagement.updateSoftwareModule(module);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(SoftwareModuleMapper.toResponse(updateSoftwareModule), HttpStatus.OK);
}
/**
* Handles the DELETE request for a single softwaremodule within SP.
*
* @param softwareModuleId
* the ID of the module to retrieve
* @return status OK if delete as sucessfull.
*
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}")
@ApiOperation(value = "Delete Software Module", notes = "Handles the DELETE request for a single softwaremodule within SP. Required Permission: "
+ SpPermission.DELETE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteSoftwareModule(@PathVariable final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModule(module);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Gets a paged list of meta data for a software module.
*
* @param softwareModuleId
* the ID of the software module for the meta data
* @param pagingOffsetParam
* the offset of list of meta data for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=key==abc}
* @return status OK if get request is successful with the paged list of
* meta data
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = MetadataRestPageList.class, value = "Get a paged list of meta data", notes = " Get a paged list of meta data for a software module."
+ " Required Permission: " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRestPageList> getMetadata(
@PathVariable final Long softwareModuleId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>key==aKey,key==bKey</td><td>metadata with the key 'aKey' or 'bKey'</td></tr>"
+ "<tr><td>value=li=%25someValue%25</td><td>metadata which contains 'someValue' in the value ignore case</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
// check if software module exists otherwise throw exception immediately
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<SoftwareModuleMetadata> metaDataPage;
if (rsqlParam != null) {
metaDataPage = softwareManagement.findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId,
RSQLUtility.parse(rsqlParam, SoftwareModuleMetadataFields.class, entityManager), pageable);
} else {
metaDataPage = softwareManagement.findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId, pageable);
}
return new ResponseEntity<>(new MetadataRestPageList(SoftwareModuleMapper.toResponseSwMetadata(metaDataPage
.getContent()), metaDataPage.getTotalElements()), HttpStatus.OK);
}
/**
* Gets a single meta data value for a specific key of a software module.
*
* @param softwareModuleId
* the ID of the software module to get the meta data from
* @param metadataKey
* the key of the meta data entry to retrieve the value from
* @return status OK if get request is successful with the value of the meta
* data
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = { MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = MetadataRest.class, value = "Get a single meta data value", notes = " Get a single meta data value for a meta data key."
+ " Required Permission: " + SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRest> getMetadataValue(@PathVariable final Long softwareModuleId,
@PathVariable final String metadataKey) {
// check if distribution set exists otherwise throw exception
// immediately
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata findOne = softwareManagement.findOne(new SwMetadataCompositeKey(sw, metadataKey));
return ResponseEntity.<MetadataRest> ok(SoftwareModuleMapper.toResponseSwMetadata(findOne));
}
/**
* Updates a single meta data value of a software module.
*
* @param softwareModuleId
* the ID of the software module to update the meta data entry
* @param metadataKey
* the key of the meta data to update the value
* @return status OK if the update request is successful and the updated
* meta data result
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
@ApiOperation(response = MetadataRest.class, value = "Update a single meta data value", notes = " Update a single meta data value for speficic key."
+ " Required Permission: " + SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<MetadataRest> updateMetadata(@PathVariable final Long softwareModuleId,
@PathVariable final String metadataKey, @RequestBody final MetadataRest metadata) {
// check if software module exists otherwise throw exception immediately
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata updated = softwareManagement
.updateSoftwareModuleMetadata(new SoftwareModuleMetadata(metadataKey, sw, metadata.getValue()));
return ResponseEntity.ok(SoftwareModuleMapper.toResponseSwMetadata(updated));
}
/**
* Deletes a single meta data entry from the software module.
*
* @param softwareModuleId
* the ID of the software module to delete the meta data entry
* @param metadataKey
* the key of the meta data to delete
* @return status OK if the delete request is successful
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/metadata/{metadataKey}")
@ApiOperation(value = "Delete a single meta data", notes = " Delete a single meta data." + " Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module or meta data key", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteMetadata(@PathVariable final Long softwareModuleId,
@PathVariable final String metadataKey) {
// check if software module exists otherwise throw exception immediately
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModuleMetadata(new SwMetadataCompositeKey(sw, metadataKey));
return ResponseEntity.ok().build();
}
/**
* Creates a list of meta data for a specific software module.
*
* @param softwareModuleId
* the ID of the distribution set to create meta data for
* @param metadataRest
* the list of meta data entries to create
* @return status created if post request is successful with the value of
* the created meta data
*/
@RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/metadata", consumes = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE,
"application/hal+json" })
@ApiOperation(response = MetadataRest.class, value = "Create a list of meta data entries", notes = "Create a list of meta data entries"
+ " Required Permission: " + SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_TARGET)
@ApiResponses({ @ApiResponse(code = 409, message = "Conflict", response = ExceptionInfo.class),
@ApiResponse(code = 404, message = "Not Found Distribution Set", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<List<MetadataRest>> createMetadata(@PathVariable final Long softwareModuleId,
@RequestBody final List<MetadataRest> metadataRest) {
// check if software module exists otherwise throw exception immediately
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final List<SoftwareModuleMetadata> created = softwareManagement
.createSoftwareModuleMetadata(SoftwareModuleMapper.fromRequestSwMetadata(sw, metadataRest));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<List<MetadataRest>>(SoftwareModuleMapper.toResponseSwMetadata(created),
HttpStatus.CREATED);
}
private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId, final Long artifactId) {
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (module == null) {
throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist");
} else if (artifactId != null && !module.getLocalArtifact(artifactId).isPresent()) {
throw new EntityNotFoundException("Artifact with Id {" + artifactId + "} does not exist");
}
return module;
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class SoftwareModuleTypeMapper {
// private constructor, utility class
private SoftwareModuleTypeMapper() {
}
static List<SoftwareModuleType> smFromRequest(final Iterable<SoftwareModuleTypeRequestBodyPost> smTypesRest) {
final List<SoftwareModuleType> mappedList = new ArrayList<>();
for (final SoftwareModuleTypeRequestBodyPost smRest : smTypesRest) {
mappedList.add(fromRequest(smRest));
}
return mappedList;
}
static SoftwareModuleType fromRequest(final SoftwareModuleTypeRequestBodyPost smsRest) {
return new SoftwareModuleType(smsRest.getKey(), smsRest.getName(), smsRest.getDescription(),
smsRest.getMaxAssignments());
}
static SoftwareModuleTypesRest toTypesResponse(final List<SoftwareModuleType> types) {
final SoftwareModuleTypesRest response = new SoftwareModuleTypesRest();
for (final SoftwareModuleType softwareModule : types) {
response.add(toResponse(softwareModule));
}
return response;
}
static List<SoftwareModuleTypeRest> toListResponse(final Collection<SoftwareModuleType> types) {
final List<SoftwareModuleTypeRest> response = new ArrayList<SoftwareModuleTypeRest>();
for (final SoftwareModuleType softwareModule : types) {
response.add(toResponse(softwareModule));
}
return response;
}
static SoftwareModuleTypeRest toResponse(final SoftwareModuleType type) {
final SoftwareModuleTypeRest result = new SoftwareModuleTypeRest();
RestModelMapper.mapNamedToNamed(result, type);
result.setKey(type.getKey());
result.setMaxAssignments(type.getMaxAssignments());
result.setModuleId(type.getId());
result.add(linkTo(methodOn(SoftwareModuleTypeResource.class).getSoftwareModuleType(result.getModuleId()))
.withRel("self"));
return result;
}
}

View File

@@ -0,0 +1,242 @@
/**
* 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.rest.resource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypePagedList;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPut;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest;
import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
*
*
*
*
*/
@RestController
@Transactional(readOnly = true)
@RequestMapping(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)
@Api(value = "softwaremoduletypes", description = "Software Module Types Management API")
public class SoftwareModuleTypeResource {
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles the GET request of retrieving all {@link SoftwareModuleType}s
* within SP.
*
* @param pagingOffsetParam
* the offset of list of modules for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
*
* @return a list of all module type for a defined or default page request
* with status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleTypePagedList.class, value = "Get Software Module Types", notes = "Handles the GET request of retrieving all software module types within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
public ResponseEntity<SoftwareModuleTypePagedList> getTypes(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query" + "<table border=0>"
+ "<tr><td>id=1,key=os</td><td>software module types with id 1 or key os</td></tr>"
+ "<tr><td>name=Application</td><td>software module types with name Application</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<SoftwareModuleType> findModuleTypessAll;
Long countModulesAll;
if (rsqlParam != null) {
findModuleTypessAll = softwareManagement.findSoftwareModuleTypesByPredicate(
RSQLUtility.parse(rsqlParam, SoftwareModuleTypeFields.class, entityManager), pageable);
countModulesAll = ((Page<SoftwareModuleType>) findModuleTypessAll).getTotalElements();
} else {
findModuleTypessAll = softwareManagement.findSoftwareModuleTypesAll(pageable);
countModulesAll = softwareManagement.countSoftwareModuleTypesAll();
}
final List<SoftwareModuleTypeRest> rest = SoftwareModuleTypeMapper.toListResponse(findModuleTypessAll
.getContent());
return new ResponseEntity<>(new SoftwareModuleTypePagedList(rest, countModulesAll), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a single software module type
* within SP.
*
* @param softwareModuleTypeId
* the ID of the module type to retrieve
*
* @return a single softwareModule with status OK.
* @throws EntityNotFoundException
* in case no with the given {@code softwareModuleId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleTypeId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleTypeRest.class, value = "Get Software Module Type", notes = "Handles the GET request of retrieving a single software module type within SP. Required Permission: "
+ SpPermission.READ_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module Type", response = ExceptionInfo.class))
public ResponseEntity<SoftwareModuleTypeRest> getSoftwareModuleType(@PathVariable final Long softwareModuleTypeId) {
final SoftwareModuleType foundType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(foundType), HttpStatus.OK);
}
/**
* Handles the DELETE request for a single software module type within SP.
*
* @param softwareModuleTypeId
* the ID of the module to retrieve
* @return status OK if delete as sucessfull.
*
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleTypeId}")
@ApiOperation(value = "Delete Software Module Type", notes = "Handles the DELETE request for a single software module Type within SP. Required Permission: "
+ SpPermission.DELETE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module Type", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteSoftwareModuleType(@PathVariable final Long softwareModuleTypeId) {
final SoftwareModuleType module = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
softwareManagement.deleteSoftwareModuleType(module);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the PUT request of updating a software module type within SP.
*
* @param softwareModuleTypeId
* the ID of the software module in the URL
* @param restSoftwareModuleType
* the module type to be updated.
* @return status OK if update is successful
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleTypeId}", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(value = "Updates Software Module Type", notes = "Handles the PUT request for a single software module type within SP. Required Permission: "
+ SpPermission.UPDATE_REPOSITORY)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Software Module", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<SoftwareModuleTypeRest> updateSoftwareModuleType(
@PathVariable final Long softwareModuleTypeId,
@RequestBody final SoftwareModuleTypeRequestBodyPut restSoftwareModuleType) {
final SoftwareModuleType type = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
// only description can be modified
if (restSoftwareModuleType.getDescription() != null) {
type.setDescription(restSoftwareModuleType.getDescription());
}
final SoftwareModuleType updatedSoftwareModuleType = softwareManagement.updateSoftwareModuleType(type);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(updatedSoftwareModuleType), HttpStatus.OK);
}
/**
* Handles the POST request of creating new {@link SoftwareModuleType}s
* within SP. The request body must always be a list of types.
*
* @param softwareModuleTypes
* the modules to be created.
* @return In case all modules could successful created the ResponseEntity
* with status code 201 - Created but without ResponseBody. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = SoftwareModuleTypesRest.class, value = "Create Software Module Types", notes = "Handles the POST request of creating new software module types within SP. The request body must always be a list of module types. Required Permission: "
+ SpPermission.CREATE_REPOSITORY)
@ApiResponses({
@ApiResponse(code = 404, message = "Not Found Software Module Type", response = ExceptionInfo.class),
@ApiResponse(code = 409, message = "Conflict Software Module Type already exists", response = ExceptionInfo.class) })
@Transactional
public ResponseEntity<SoftwareModuleTypesRest> createSoftwareModuleTypes(
@RequestBody final List<SoftwareModuleTypeRequestBodyPost> softwareModuleTypes) {
final List<SoftwareModuleType> createdSoftwareModules = softwareManagement
.createSoftwareModuleTypes(SoftwareModuleTypeMapper.smFromRequest(softwareModuleTypes));
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(SoftwareModuleTypeMapper.toTypesResponse(createdSoftwareModules),
HttpStatus.CREATED);
}
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId);
if (module == null) {
throw new EntityNotFoundException("SoftwareModuleType with Id {" + softwareModuleTypeId
+ "} does not exist");
}
return module;
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.rest.resource;
/**
* A definition of possible sorting direction.
*
*
*
*/
public enum SortDirection {
/**
* Ascending.
*/
ASC,
/**
* Descending.
*/
DESC;
/**
* Returns the sort direction for the given name.
*
* @param name
* the name of the enum
* @return the corresponding enum
* @throws SortParameterUnsupportedDirectionException
* if there is no matching enum for the specified name
*/
public static SortDirection getByName(final String name) {
try {
return valueOf(name.toUpperCase());
} catch (final IllegalArgumentException ex) {// NOSONAR
throw new SortParameterUnsupportedDirectionException();
}
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception used by the REST API in case of invalid sort parameter syntax.
*
*
*
*
*/
public class SortParameterSyntaxErrorException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException with
* {@link SpServerError#SP_REST_SORT_PARAM_SYNTAX} error.
*/
public SortParameterSyntaxErrorException() {
super(SpServerError.SP_REST_SORT_PARAM_SYNTAX);
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception used by the REST API in case of invalid sort parameter direction
* name.
*
*
*
*
*/
public class SortParameterUnsupportedDirectionException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException.
*/
public SortParameterUnsupportedDirectionException() {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION);
}
/**
* Creates a new SortParameterSyntaxErrorException with
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_DIRECTION} error.
*
* @param cause
* the cause (which is saved for later retrieval by the
* getCause() method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
*/
public SortParameterUnsupportedDirectionException(final Throwable cause) {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, cause);
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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.rest.resource;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* Exception used by the REST API in case of invalid field name in the sort
* parameter.
*
*
*
*
*/
public class SortParameterUnsupportedFieldException extends SpServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException with
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_FIELD} error.
*/
public SortParameterUnsupportedFieldException() {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD);
}
/**
* Creates a new SortParameterSyntaxErrorException with
* {@link SpServerError#SP_REST_SORT_PARAM_INVALID_FIELD} error.
*
* @param cause
* the cause (which is saved for later retrieval by the
* getCause() method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
*/
public SortParameterUnsupportedFieldException(final Throwable cause) {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, cause);
}
}

View File

@@ -0,0 +1,124 @@
/**
* 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.rest.resource;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.hawkbit.repository.FieldNameProvider;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import com.google.common.collect.Lists;
/**
* A utility class for parsing query parameters which define the sorting of
* elements.
*
*
*
*/
public final class SortUtility {
/**
* the delimiter between the field and direction in the sort request.
*/
public static final String DELIMITER_FIELD_DIRECTION = ":";
private static final String DELIMITER_SORT_TUPLE = ",";
/*
* utility constructor private.
*/
private SortUtility() {
}
/**
* Parses the sort string e.g. given in a REST call based on the definition
* of sorting: http://localhost/entity?s=field1:ASC, field2:DESC The fields
* will be split into the keys of the returned map. The direction of the
* sorting will be mapped into the {@link Direction} enum.
*
* @param enumType
* the class of the enum which the fields in the sort string
* should be related to.
* @param <T>
* the type of the enumeration which must be derived from
* {@link FieldNameProvider}
* @param sortString
* the string representation of the query parameters. Might be
* {@code null} or an empty string.
* @return a list which holds the {@link FieldNameProvider} and the specific
* {@link Direction} for them as a tuple. Never {@code null}. In
* case of no sorting parameters an empty map will be returned.
* @throws SortParameterSyntaxErrorException
* if the sorting query parameter is not well-formed
* @throws SortParameterUnsupportedFieldException
* if a field name cannot be mapped to the enum type
* @throws SortParameterUnsupportedDirectionException
* if the given direction is not "ASC" or "DESC"
*/
public static <T extends Enum<T> & FieldNameProvider> List<Order> parse(final Class<T> enumType,
final String sortString) throws SortParameterSyntaxErrorException {
final List<Order> parsedSortings = Lists.newArrayList();
// scan the sort tuples e.g. field:direction
if (sortString != null) {
final StringTokenizer tupleTokenizer = new StringTokenizer(sortString, DELIMITER_SORT_TUPLE);
while (tupleTokenizer.hasMoreTokens()) {
final String sortTuple = tupleTokenizer.nextToken().trim();
final StringTokenizer fieldDirectionTokenizer = new StringTokenizer(sortTuple,
DELIMITER_FIELD_DIRECTION);
if (fieldDirectionTokenizer.countTokens() == 2) {
final String fieldName = fieldDirectionTokenizer.nextToken().trim().toUpperCase();
final String sortDirectionStr = fieldDirectionTokenizer.nextToken().trim();
final T identifier = getAttributeIdentifierByName(enumType, fieldName);
final Direction sortDirection = getDirection(sortDirectionStr);
parsedSortings.add(new Order(sortDirection, identifier.getFieldName()));
} else {
throw new SortParameterSyntaxErrorException();
}
}
}
return parsedSortings;
}
/**
* Returns the attribute identifier for the given name.
*
* @param enumType
* the class of the enum which the fields in the sort string
* should be related to.
* @param name
* the name of the enum
* @param <T>
* the type of the enumeration which must be derived from
* {@link FieldNameProvider}
* @return the corresponding enum
* @throws SortParameterUnsupportedFieldException
* if there is no matching enum for the specified name
*/
private static <T extends Enum<T> & FieldNameProvider> T getAttributeIdentifierByName(final Class<T> enumType,
final String name) {
try {
return Enum.valueOf(enumType, name.toUpperCase());
} catch (final IllegalArgumentException e) {
throw new SortParameterUnsupportedFieldException(e);
}
}
private static Direction getDirection(final String sortDirectionStr) {
try {
return Direction.fromString(sortDirectionStr);
} catch (final IllegalArgumentException e) {
throw new SortParameterUnsupportedDirectionException(e);
}
}
}

View File

@@ -0,0 +1,132 @@
/**
* 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.rest.resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.rest.resource.model.system.CacheRest;
import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationRest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* {@link SystemManagement} capabilities by REST.
*
*
*
*
*/
@RestController
@RequestMapping(RestConstants.SYSTEM_ADMIN_MAPPING)
@Transactional(readOnly = true)
public class SystemManagementResource {
private static final Logger LOGGER = LoggerFactory.getLogger(SystemManagementResource.class);
@Autowired
private SystemManagement systemManagement;
@Autowired
private CacheManager cacheManager;
/**
* Deletes the tenant data of a given tenant. USE WITH CARE!
*
* @param tenant
* to delete
* @return HttpStatus.OK
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/tenants/{tenant}")
@Transactional
public ResponseEntity<Void> deleteTenant(@PathVariable final String tenant) {
systemManagement.deleteTenant(tenant);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Returns a list of all caches containing currently.
*
* @return a list of caches for all tenants
*/
@RequestMapping(method = RequestMethod.GET, value = "/caches")
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
public ResponseEntity<Collection<CacheRest>> getCaches() {
final Collection<String> cacheNames = cacheManager.getCacheNames();
return ResponseEntity.ok(cacheNames.stream().map(cacheName -> cacheManager.getCache(cacheName))
.map(cache -> cacheRest(cache)).collect(Collectors.toList()));
}
/**
* Invalidates all caches for all tenants.
*
* @return a list of cache names which has been invalidated
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/caches")
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
public ResponseEntity<Collection<String>> invalidateCaches() {
final Collection<String> cacheNames = cacheManager.getCacheNames();
LOGGER.info("Invalidating caches {}", cacheNames);
cacheNames.forEach(cacheName -> cacheManager.getCache(cacheName).clear());
return ResponseEntity.ok(cacheNames);
}
/**
* Adds or updates a configuration for a specific tenant to the tenant
* configuration.
*
* @param configuration
* the configuration value to add or update
* @param key
* the key of the configuration to add or update
* @return the response entity with status OK.
*/
@RequestMapping(method = RequestMethod.PUT, value = "/conf/{key}")
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
public ResponseEntity<Void> addUpdateConfig(@RequestBody final TenantConfigurationRest configuration,
@PathVariable final String key) {
systemManagement.addOrUpdateConfiguration(new TenantConfiguration(key, configuration.getValue()));
return ResponseEntity.ok().build();
}
private CacheRest cacheRest(final Cache cache) {
final Object nativeCache = cache.getNativeCache();
if (nativeCache instanceof com.google.common.cache.Cache) {
return guavaCache(cache, nativeCache);
} else {
return new CacheRest(cache.getName(), Collections.emptyList());
}
}
@SuppressWarnings("unchecked")
private CacheRest guavaCache(final Cache cache, final Object nativeCache) {
final com.google.common.cache.Cache<Object, Object> guavaCache = (com.google.common.cache.Cache<Object, Object>) nativeCache;
final List<String> keys = guavaCache.asMap().keySet().stream().map(key -> key.toString())
.collect(Collectors.toList());
return new CacheRest(cache.getName(), keys);
}
}

View File

@@ -0,0 +1,245 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.net.URI;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo.PollStatus;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.rest.resource.model.PollStatusRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionStatusRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionStatussRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionsRest;
import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody;
import org.eclipse.hawkbit.rest.resource.model.target.TargetRest;
import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class TargetMapper {
private TargetMapper() {
// Utility class
}
private static String getNameOfActionStatusType(final Action.Status type) {
String result = null;
switch (type) {
case CANCELED:
result = ActionStatusRest.AS_CANCELED;
break;
case ERROR:
result = ActionStatusRest.AS_ERROR;
break;
case FINISHED:
result = ActionStatusRest.AS_FINISHED;
break;
case RETRIEVED:
result = ActionStatusRest.AS_RETRIEVED;
break;
case RUNNING:
result = ActionStatusRest.AS_RUNNING;
break;
case WARNING:
result = ActionStatusRest.AS_WARNING;
break;
default:
return type.name().toLowerCase();
}
return result;
}
private static String getType(final Action action) {
if (!action.isCancelingOrCanceled()) {
return ActionRest.ACTION_UPDATE;
} else if (action.isCancelingOrCanceled()) {
return ActionRest.ACTION_CANCEL;
}
return null;
}
private static String getUpdateStatusName(final TargetUpdateStatus updatestatus) {
String result = null;
switch (updatestatus) {
case ERROR:
result = "error";
break;
case IN_SYNC:
result = "in_sync";
break;
case PENDING:
result = "pending";
break;
case REGISTERED:
result = "registered";
break;
case UNKNOWN:
result = "unknown";
break;
default:
return updatestatus.name().toLowerCase();
}
return result;
}
static ActionRest toResponse(final String targetId, final Action action, final boolean isActive) {
final ActionRest result = new ActionRest();
result.setActionId(action.getId());
result.setType(getType(action));
if (isActive) {
result.setStatus(ActionRest.ACTION_PENDING);
} else {
result.setStatus(ActionRest.ACTION_FINISHED);
}
RestModelMapper.mapBaseToBase(result, action);
result.add(linkTo(methodOn(TargetResource.class).getAction(targetId, action.getId())).withRel("self"));
return result;
}
static ActionsRest toResponse(final String targetId, final List<Action> actions) {
final ActionsRest mappedList = new ActionsRest();
for (final Action action : actions) {
final ActionRest response = toResponse(targetId, action, action.isActive());
mappedList.add(response);
}
return mappedList;
}
static TargetRest toResponse(final Target target, final boolean includePollStatus) {
if (target == null) {
return null;
}
final TargetRest targetRest = new TargetRest();
targetRest.setControllerId(target.getControllerId());
targetRest.setDescription(target.getDescription());
targetRest.setName(target.getName());
targetRest.setUpdateStatus(getUpdateStatusName(target.getTargetInfo().getUpdateStatus()));
final URI address = target.getTargetInfo().getAddress();
if (address != null) {
targetRest.setIpAddress(address.getHost());
targetRest.setAddress(address.toString());
}
targetRest.setCreatedBy(target.getCreatedBy());
targetRest.setLastModifiedBy(target.getLastModifiedBy());
targetRest.setCreatedAt(target.getCreatedAt());
targetRest.setLastModifiedAt(target.getLastModifiedAt());
targetRest.setSecurityToken(target.getSecurityToken());
// last target query is the last controller request date
final Long lastTargetQuery = target.getTargetInfo().getLastTargetQuery();
final Long installationDate = target.getTargetInfo().getInstallationDate();
if (lastTargetQuery != null) {
targetRest.setLastControllerRequestAt(lastTargetQuery);
}
if (installationDate != null) {
targetRest.setInstalledAt(installationDate);
}
if (includePollStatus) {
final PollStatus pollStatus = target.getTargetInfo().getPollStatus();
if (pollStatus != null) {
final PollStatusRest pollStatusRest = new PollStatusRest();
pollStatusRest.setLastRequestAt(
Date.from(pollStatus.getLastPollDate().atZone(ZoneId.systemDefault()).toInstant()).getTime());
pollStatusRest.setNextExpectedRequestAt(
Date.from(pollStatus.getNextPollDate().atZone(ZoneId.systemDefault()).toInstant()).getTime());
pollStatusRest.setOverdue(pollStatus.isOverdue());
targetRest.setPollStatus(pollStatusRest);
}
}
targetRest.add(linkTo(methodOn(TargetResource.class).getTarget(target.getControllerId())).withRel("self"));
return targetRest;
}
private static ActionStatusRest toResponse(final Action action, final ActionStatus actionStatus) {
final ActionStatusRest result = new ActionStatusRest();
result.setMessages(actionStatus.getMessages());
result.setReportedAt(action.getCreatedAt());
result.setStatusId(action.getId());
result.setType(getNameOfActionStatusType(actionStatus.getStatus()));
return result;
}
static List<Target> fromRequest(final Iterable<TargetRequestBody> targetsRest) {
final List<Target> mappedList = new ArrayList<>();
for (final TargetRequestBody targetRest : targetsRest) {
mappedList.add(fromRequest(targetRest));
}
return mappedList;
}
static Target fromRequest(final TargetRequestBody targetRest) {
final Target target = new Target(targetRest.getControllerId());
target.setDescription(targetRest.getDescription());
target.setName(targetRest.getName());
return target;
}
static ActionStatussRest toActionStatusRestResponse(final Action action, final List<ActionStatus> actionStatus) {
final ActionStatussRest mappedList = new ActionStatussRest();
if (actionStatus != null) {
for (final ActionStatus status : actionStatus) {
final ActionStatusRest response = toResponse(action, status);
mappedList.add(response);
}
}
return mappedList;
}
static TargetsRest toResponse(final Iterable<Target> targets) {
final TargetsRest mappedList = new TargetsRest();
if (targets != null) {
for (final Target target : targets) {
// to single response without pollstatus in the list
final TargetRest response = toResponse(target, false);
mappedList.add(response);
}
}
return mappedList;
}
}

View File

@@ -0,0 +1,602 @@
/**
* 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.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.action.ActionPagedList;
import org.eclipse.hawkbit.rest.resource.model.action.ActionRest;
import org.eclipse.hawkbit.rest.resource.model.action.ActionStatusPagedList;
import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest;
import org.eclipse.hawkbit.rest.resource.model.target.DistributionSetAssigmentRest;
import org.eclipse.hawkbit.rest.resource.model.target.TargetAttributes;
import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList;
import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody;
import org.eclipse.hawkbit.rest.resource.model.target.TargetRest;
import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling target CRUD operations.
*
*
*
*
*/
@RestController
@Transactional(readOnly = true)
@RequestMapping(RestConstants.TARGET_V1_REQUEST_MAPPING)
@Api(value = "targets", description = "Provisioning Target Management API")
public class TargetResource {
private static final Logger LOG = LoggerFactory.getLogger(TargetResource.class);
@Autowired
private TargetManagement targetManagement;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private EntityManager entityManager;
/**
* Handles the GET request of retrieving a single target within SP.
*
* @param targetId
* the ID of the target to retrieve
* @return a single target with status OK.
* @throws EntityNotFoundException
* in case no target with the given {@code targetId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = TargetRest.class, value = "Get single Target", notes = "Handles the GET request of retrieving a single target within SP. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
public ResponseEntity<TargetRest> getTarget(@PathVariable final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
// to single response include poll status
final TargetRest response = TargetMapper.toResponse(findTarget, true);
// add links
response.add(linkTo(methodOn(TargetResource.class).getAssignedDistributionSet(response.getControllerId()))
.withRel(RestConstants.TARGET_V1_ASSIGNED_DISTRIBUTION_SET));
response.add(linkTo(methodOn(TargetResource.class).getInstalledDistributionSet(response.getControllerId()))
.withRel(RestConstants.TARGET_V1_INSTALLED_DISTRIBUTION_SET));
response.add(linkTo(methodOn(TargetResource.class).getAttributes(response.getControllerId())).withRel(
RestConstants.TARGET_V1_ATTRIBUTES));
response.add(linkTo(
methodOn(TargetResource.class).getActionHistory(response.getControllerId(), 0,
RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionFields.ID.getFieldName() + ":" + SortDirection.DESC, null)).withRel(
RestConstants.TARGET_V1_ACTIONS));
return new ResponseEntity<>(response, HttpStatus.OK);
}
/**
* Handles the GET request of retrieving all targets within SP.
*
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return a list of all targets for a defined or default page request with
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = TargetPagedList.class, value = "Get paged list of Targets", notes = "Handles the GET request of retrieving all targets within SP. Required Permission: "
+ SpPermission.READ_TARGET)
public ResponseEntity<TargetPagedList> getTargets(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query"
+ "<table border=0>"
+ "<tr><td>updatestatus==unknown,updatestatus==error</td><td>targets with status 'unknown' or 'error'</td></tr>"
+ "<tr><td>installedAt=ge=1424699220</td><td>targets which installation date later than 1424699220</td></tr>"
+ "<tr><td>name=li=%25ccu%25</td><td>targets which contain 'ccu' in their name ingore case</td></tr>"
+ "<tr><td>controllerId==target0815</td><td>target which has the ID 'target0815'</td></tr>"
+ "<tr><td>ipaddress=li=171.%25</td><td>target which starts with IP address '171.'</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<Target> findTargetsAll;
final Long countTargetsAll;
if (rsqlParam != null) {
final Page<Target> findTargetPage = targetManagement.findTargetsAll(
RSQLUtility.parse(rsqlParam, TargetFields.class, entityManager), pageable);
countTargetsAll = findTargetPage.getTotalElements();
findTargetsAll = findTargetPage;
} else {
findTargetsAll = targetManagement.findTargetsAll(pageable);
countTargetsAll = targetManagement.countTargetsAll();
}
final List<TargetRest> rest = TargetMapper.toResponse(findTargetsAll.getContent());
return new ResponseEntity<>(new TargetPagedList(rest, countTargetsAll), HttpStatus.OK);
}
/**
* Handles the POST request of creating new targets within SP. The request
* body must always be a list of targets. The requests is delegating to the
* {@link TargetManagement#createTarget(Iterable)}.
*
* @param targets
* the targets to be created.
* @return In case all targets could successful created the ResponseEntity
* with status code 201 with a list of successfully created
* entities. In any failure the JsonResponseExceptionHandler is
* handling the response.
*/
@RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }, produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = TargetsRest.class, value = "Create list of Targets", notes = "Handles the POST request of creating new targets within SP. The request body must always be a list of targets. Required Permission: "
+ SpPermission.CREATE_TARGET)
@ApiResponses(@ApiResponse(code = 409, message = "Conflict", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<TargetsRest> createTargets(
@RequestBody @ApiParam(value = "List of targets", required = true) final List<TargetRequestBody> targets) {
LOG.debug("creating {} targets", targets.size());
final Iterable<Target> createdTargets = targetManagement.createTargets(TargetMapper.fromRequest(targets));
LOG.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(TargetMapper.toResponse(createdTargets), HttpStatus.CREATED);
}
/**
* Handles the PUT request of updating a target within SP. The ID is within
* the URL path of the request. A given ID in the request body is ignored.
* It's not possible to set fields to {@code null} values.
*
* @param targetId
* the path parameter which contains the ID of the target
* @param targetRest
* the request body which contains the fields which should be
* updated, fields which are not given are ignored for the
* udpate.
* @return the updated target response which contains all fields also fields
* which have not updated
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{targetId}", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = TargetRest.class, value = "Updates a target", notes = "Handles the PUT request of updating a target within SP. Required Permission: "
+ SpPermission.UPDATE_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<TargetRest> updateTarget(@PathVariable final String targetId,
@RequestBody @ApiParam(value = "Target to be updated", required = true) final TargetRequestBody targetRest) {
final Target existingTarget = findTargetWithExceptionIfNotFound(targetId);
LOG.debug("updating target {}", existingTarget.getId());
if (targetRest.getDescription() != null) {
existingTarget.setDescription(targetRest.getDescription());
}
if (targetRest.getName() != null) {
existingTarget.setName(targetRest.getName());
}
final Target updateTarget = targetManagement.updateTarget(existingTarget);
LOG.debug("target {} updated, return status {}", updateTarget.getId(), HttpStatus.OK);
// we flush to ensure that entity is generated and we can return ID etc.
entityManager.flush();
return new ResponseEntity<>(TargetMapper.toResponse(updateTarget, false), HttpStatus.OK);
}
/**
* Handles the DELETE request of deleting a target within SP.
*
* @param targetId
* the ID of the target to be deleted
* @return If the given targetId could exists and could be deleted Http OK.
* In any failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(value = "Deletes a target", notes = "Handles the DELETE request of deleting a single target within SP. Required Permission: "
+ SpPermission.DELETE_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> deleteTarget(@PathVariable final String targetId) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
targetManagement.deleteTargets(target.getId());
LOG.debug("{} target deleted, return status {}", targetId, HttpStatus.OK);
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the attributes of a specific
* target.
*
* @param targetId
* the ID of the target to retrieve the attributes.
* @return the target attributes as map response with status OK
* @throws EntityNotFoundException
* in case no target with the given {@code targetId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/attributes", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = TargetAttributes.class, value = "Get attributes of Target", notes = "Handles the GET request of retrieving the attributes of a specific target. Reponse is a key/value list. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
public ResponseEntity<TargetAttributes> getAttributes(@PathVariable final String targetId) {
final Target foundTarget = findTargetWithExceptionIfNotFound(targetId);
final Map<String, String> controllerAttributes = foundTarget.getTargetInfo().getControllerAttributes();
if (controllerAttributes.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
final TargetAttributes result = new TargetAttributes();
result.putAll(controllerAttributes);
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the {@link Action}s of a specific
* target.
*
* @param targetId
* to load actions for
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=status==pending}
* @return a list of all {@link Action}s for a defined or default page
* request with status OK. The response is always paged. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionPagedList.class, value = "List all actions of Target", notes = "Handles the GET request of retrieving the full action history of a specific target. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
public ResponseEntity<ActionPagedList> getActionHistory(
@PathVariable final String targetId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@ApiParam(required = false, value = "FIQL syntax search query" + "<table border=0>"
+ "<tr><td>status==finished</td><td>actions which are already 'finished'</td></tr>"
+ "<tr><td>status==pending</td><td>actions which are currently in 'pending' state</td></tr>"
+ "</table>") @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final Target foundTarget = findTargetWithExceptionIfNotFound(targetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeActionSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<Action> activeActions;
final Long totalActionCount;
if (rsqlParam != null) {
final Specification<Action> parse = RSQLUtility.parse(rsqlParam, ActionFields.class, entityManager);
activeActions = deploymentManagement.findActionsByTarget(parse, foundTarget, pageable);
totalActionCount = deploymentManagement.countActionsByTarget(parse, foundTarget);
} else {
activeActions = deploymentManagement.findActionsByTarget(foundTarget, pageable);
totalActionCount = deploymentManagement.countActionsByTarget(foundTarget);
}
return new ResponseEntity<ActionPagedList>(new ActionPagedList(TargetMapper.toResponse(targetId,
activeActions.getContent()), totalActionCount), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a specific {@link Action}s of a
* specific {@link Target}.
*
* @param targetId
* to load the action for
* @param actionId
* to load
* @return the action
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionRest.class, value = "Get assigned action of Target", notes = "Handles the GET request of retrieving a specific action on a specific target. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target or Action", response = ExceptionInfo.class))
public ResponseEntity<ActionRest> getAction(@PathVariable final String targetId, @PathVariable final Long actionId) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn("given action ({}) is not assigned to given target ({}).", action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
final ActionRest result = TargetMapper.toResponse(targetId, action, action.isActive());
if (!action.isCancelingOrCanceled()) {
result.add(linkTo(
methodOn(DistributionSetResource.class).getDistributionSet(action.getDistributionSet().getId()))
.withRel("distributionset"));
} else if (action.isCancelingOrCanceled()) {
result.add(linkTo(methodOn(TargetResource.class).getAction(targetId, action.getId())).withRel(
RestConstants.TARGET_V1_CANCELED_ACTION));
}
result.add(linkTo(
methodOn(TargetResource.class).getActionStatusList(targetId, action.getId(), 0,
RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionStatusFields.ID.getFieldName() + ":" + SortDirection.DESC)).withRel(
RestConstants.TARGET_V1_ACTION_STATUS));
return new ResponseEntity<ActionRest>(result, HttpStatus.OK);
}
/**
* Handles the DELETE request of canceling an specific {@link Action}s of a
* specific {@link Target}.
*
* @param targetId
* the ID of the target in the URL path parameter
* @param actionId
* the ID of the action in the URL path parameter
* @return status no content in case cancellation was successful
* @throws CancelActionNotAllowedException
* if the given action is not active and cannot be canceled.
* @throws EntityNotFoundException
* if the target or the action is not found
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}/actions/{actionId}")
@ApiOperation(value = "Cancels an active action", notes = "Cancels an active action, only active actions can be deleted. Required Permission: "
+ SpPermission.UPDATE_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target or Action", response = ExceptionInfo.class))
public ResponseEntity<Void> cancelAction(@PathVariable final String targetId, @PathVariable final Long actionId) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Action action = findActionWithExceptionIfNotFound(actionId);
// cancel action will throw an exception if action cannot be canceled
// which is mapped by
// response exception handler.
deploymentManagement.cancelAction(action, target);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
/**
* Handles the GET request of retrieving the {@link ActionStatus}s of a
* specific target and action.
*
* @param targetId
* of the the action
* @param actionId
* of the status we are intend to load
* @param pagingOffsetParam
* the offset of list of targets for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @return a list of all {@link ActionStatus}s for a defined or default page
* request with status OK. The response is always paged. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}/status", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = ActionStatusPagedList.class, value = "Get assigned action of Target", notes = "Handles the GET request of retrieving a specific action on a specific target. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target or Action", response = ExceptionInfo.class))
public ResponseEntity<ActionStatusPagedList> getActionStatusList(
@PathVariable final String targetId,
@PathVariable final Long actionId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Action action = findActionWithExceptionIfNotFound(actionId);
if (!action.getTarget().getId().equals(target.getId())) {
LOG.warn("given action ({}) is not assigned to given target ({}).", action.getId(), target.getId());
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeActionStatusSortParam(sortParam);
final Page<ActionStatus> statusList = deploymentManagement.findActionStatusMessagesByActionInDescOrder(
new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting), action, true);
return new ResponseEntity<>(new ActionStatusPagedList(TargetMapper.toActionStatusRestResponse(action,
statusList.getContent()), statusList.getTotalElements()), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving the assigned distribution set of an
* specific target.
*
* @param targetId
* the ID of the target to retrieve the assigned distribution
* @return the assigned distribution set with status OK, if none is assigned
* than {@code null} content (e.g. "{}")
* @throws EntityNotFoundException
* in case no target with the given {@code targetId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/assignedDS", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetRest.class, value = "Get assigned Distribution Set of Target", notes = "Handles the GET request of retrieving the assigned distribution set of an specific target. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
public ResponseEntity<DistributionSetRest> getAssignedDistributionSet(@PathVariable final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
final DistributionSetRest distributionSetRest = DistributionSetMapper.toResponse(findTarget
.getAssignedDistributionSet());
final HttpStatus retStatus;
if (distributionSetRest == null) {
retStatus = HttpStatus.NO_CONTENT;
} else {
retStatus = HttpStatus.OK;
}
return new ResponseEntity<>(distributionSetRest, retStatus);
}
/**
* Changes the assigned distribution set of a target.
*
* @param targetId
* of the target to change
* @param dsId
* of the distributionset that is to be assigned
* @return {@link HttpStatus#OK}
*
* @throws EntityNotFoundException
* in case no target with the given {@code targetId} exists.
*
*/
@RequestMapping(method = RequestMethod.POST, value = "/{targetId}/assignedDS", consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = Void.class, value = "Assigned Distribution Set to Target", notes = "Handles the POST request for assigning a distribution set to a specific target. Required Permission: "
+ SpPermission.READ_REPOSITORY + " and " + SpPermission.UPDATE_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
@Transactional
public ResponseEntity<Void> postAssignedDistributionSet(
@PathVariable final String targetId,
@RequestBody @ApiParam(value = "Distribution Set ID", required = true) final DistributionSetAssigmentRest dsId) {
findTargetWithExceptionIfNotFound(targetId);
final ActionType type = (dsId.getType() != null) ? RestResourceConversionHelper.convertActionType(dsId
.getType()) : ActionType.FORCED;
final Iterator<Target> changed = deploymentManagement
.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), targetId).getAssignedTargets()
.iterator();
if (changed.hasNext()) {
return new ResponseEntity<>(HttpStatus.OK);
}
LOG.error("Target update (ds {} assigment to target {}) failed! Returnd target list is empty.", dsId.getId(),
targetId);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* Handles the GET request of retrieving the installed distribution set of
* an specific target.
*
* @param targetId
* the ID of the target to retrieve
* @return the assigned installed set with status OK, if none is installed
* than {@code null} content (e.g. "{}")
* @throws EntityNotFoundException
* in case no target with the given {@code targetId} exists.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{targetId}/installedDS", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
@ApiOperation(response = DistributionSetRest.class, value = "Get installed Distribution Set of Target", notes = "Handles the GET request of retrieving the installed distribution set of an specific target. Required Permission: "
+ SpPermission.READ_TARGET)
@ApiResponses(@ApiResponse(code = 404, message = "Not Found Target", response = ExceptionInfo.class))
public ResponseEntity<DistributionSetRest> getInstalledDistributionSet(@PathVariable final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
final DistributionSetRest distributionSetRest = DistributionSetMapper.toResponse(findTarget.getTargetInfo()
.getInstalledDistributionSet());
final HttpStatus retStatus;
if (distributionSetRest == null) {
retStatus = HttpStatus.NO_CONTENT;
} else {
retStatus = HttpStatus.OK;
}
return new ResponseEntity<>(distributionSetRest, retStatus);
}
private Target findTargetWithExceptionIfNotFound(final String targetId) {
final Target findTarget = targetManagement.findTargetByControllerID(targetId);
if (findTarget == null) {
throw new EntityNotFoundException("Target with Id {" + targetId + "} does not exist");
}
return findTarget;
}
private Action findActionWithExceptionIfNotFound(final Long actionId) {
final Action findAction = deploymentManagement.findAction(actionId);
if (findAction == null) {
throw new EntityNotFoundException("Action with Id {" + actionId + "} does not exist");
}
return findAction;
}
}

View File

@@ -0,0 +1,120 @@
/**
* 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.rest.resource.helper;
/**
* Byte range for resume download operations.
*
*
*
*
*
*/
public class ByteRange {
public static final String MULTIPART_BOUNDARY = "THIS_STRING_SEPARATES_MULTIPART";
private final long start;
private final long end;
private final long length;
private final long total;
/**
* Construct a byte range.
*
* @param start
* Start of the byte range.
* @param end
* End of the byte range.
* @param total
* Total length of the byte source.
*/
public ByteRange(final long start, final long end, final long total) {
this.start = start;
this.end = end;
length = end - start + 1;
this.total = total;
}
/**
* @return the start
*/
public long getStart() {
return start;
}
/**
* @return the end
*/
public long getEnd() {
return end;
}
/**
* @return the length
*/
public long getLength() {
return length;
}
/**
* @return the total
*/
public long getTotal() {
return total;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() { // NOSONAR - as this is generated
final int prime = 31;
int result = 1;
result = prime * result + (int) (end ^ end >>> 32);
result = prime * result + (int) (length ^ length >>> 32);
result = prime * result + (int) (start ^ start >>> 32);
result = prime * result + (int) (total ^ total >>> 32);
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ByteRange other = (ByteRange) obj;
if (end != other.end) {
return false;
}
if (length != other.length) {
return false;
}
if (start != other.start) {
return false;
}
if (total != other.total) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,374 @@
/**
* 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.rest.resource.helper;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.controller.FileSteamingFailedException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.rest.resource.model.distributionset.ActionTypeRest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import com.google.common.math.DoubleMath;
import com.google.common.net.HttpHeaders;
/**
* Utility class for the Rest Source API.
*
*
*
*
*/
public final class RestResourceConversionHelper {
private static final Logger LOG = LoggerFactory.getLogger(RestResourceConversionHelper.class);
private static final int BUFFER_SIZE = 4096;
// utility class, private constructor.
private RestResourceConversionHelper() {
}
/**
* Write response without target relation.
*
* @param artifact
* the artifact
* @param servletResponse
* to be sent back to the requesting client
* @param request
* from the client
* @param file
* to be write to the client response
*
* @return http code
*/
public static ResponseEntity<Void> writeFileResponse(final LocalArtifact artifact,
final HttpServletResponse servletResponse, final HttpServletRequest request, final DbArtifact file) {
return writeFileResponse(artifact, servletResponse, request, file, null, null);
}
/**
* <p>
* Write response with target relation and publishes events concerning the
* download progress based on given {@link UpdateActionStatus}.
* </p>
*
* <p>
* The request supports RFC7233 range requests.
* </p>
*
* @param artifact
* the artifact
* @param response
* to be sent back to the requesting client
* @param request
* from the client
* @param file
* to be write to the client response
* @param cacheWriteNotify
* to write progress updates to
* @param statusId
* of the UpdateActionStatus
*
* @throws IOException
* in case of exceptions
*
* @return http code
*
* @see https://tools.ietf.org/html/rfc7233
*/
public static ResponseEntity<Void> writeFileResponse(final LocalArtifact artifact,
final HttpServletResponse response, final HttpServletRequest request, final DbArtifact file,
final CacheWriteNotify cacheWriteNotify, final Long statusId) {
ResponseEntity<Void> result = null;
final String etag = artifact.getSha1Hash();
final Long lastModified = artifact.getLastModifiedAt() != null ? artifact.getLastModifiedAt()
: artifact.getCreatedAt();
final long length = file.getSize();
response.reset();
response.setBufferSize(BUFFER_SIZE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + artifact.getFilename());
response.setHeader(HttpHeaders.ETAG, etag);
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
final ByteRange full = new ByteRange(0, length - 1, length);
final List<ByteRange> ranges = new ArrayList<>();
// Validate and process Range and If-Range headers.
final String range = request.getHeader("Range");
if (range != null) {
LOG.debug("range header for filename ({}) is: {}", artifact.getFilename(), range);
// Range header matches"bytes=n-n,n-n,n-n..."
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length);
LOG.debug("range header for filename ({}) is not satisfiable: ", artifact.getFilename());
return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
}
// RFC: if the representation is unchanged, send me the part(s) that
// I am requesting in
// Range; otherwise, send me the entire representation.
checkForShortcut(request, etag, lastModified, full, ranges);
// it seems there are valid ranges
result = extractRange(response, length, ranges, range);
// return if range extraction turned out to be invalid
if (result != null) {
return result;
}
}
// full request - no range
if (ranges.isEmpty() || ranges.get(0).equals(full)) {
LOG.debug("filename ({}) results into a full request: ", artifact.getFilename());
fullfileRequest(artifact, response, file, cacheWriteNotify, statusId, full);
result = new ResponseEntity<>(HttpStatus.OK);
}
// standard range request
else if (ranges.size() == 1) {
LOG.debug("filename ({}) results into a standard range request: ", artifact.getFilename());
standardRangeRequest(artifact, response, file, cacheWriteNotify, statusId, ranges);
result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT);
}
// multipart range request
else {
LOG.debug("filename ({}) results into a multipart range request: ", artifact.getFilename());
multipartRangeRequest(artifact, response, file, cacheWriteNotify, statusId, ranges);
result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT);
}
return result;
}
private static void fullfileRequest(final LocalArtifact artifact, final HttpServletResponse response,
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId, final ByteRange full) {
final ByteRange r = full;
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength()));
try {
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId, r.getStart(),
r.getLength());
} catch (final IOException e) {
LOG.error("fullfileRequest of file ({}) failed!", artifact.getFilename(), e);
throw new FileSteamingFailedException(artifact.getFilename());
}
}
private static ResponseEntity<Void> extractRange(final HttpServletResponse response, final long length,
final List<ByteRange> ranges, final String range) {
ResponseEntity<Void> result = null;
if (ranges.isEmpty()) {
for (final String part : range.substring(6).split(",")) {
long start = sublong(part, 0, part.indexOf('-'));
long end = sublong(part, part.indexOf('-') + 1, part.length());
if (start == -1) {
start = length - end;
end = length - 1;
} else if (end == -1 || end > length - 1) {
end = length - 1;
}
// Check if Range is syntactically valid. If not, then return
// 416.
if (start > end) {
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length);
result = new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
return result;
}
// Add range.
ranges.add(new ByteRange(start, end, length));
}
}
return null;
}
private static long sublong(final String value, final int beginIndex, final int endIndex) {
final String substring = value.substring(beginIndex, endIndex);
return substring.length() > 0 ? Long.parseLong(substring) : -1;
}
private static void checkForShortcut(final HttpServletRequest request, final String etag, final long lastModified,
final ByteRange full, final List<ByteRange> ranges) {
final String ifRange = request.getHeader(HttpHeaders.IF_RANGE);
if (ifRange != null && !ifRange.equals(etag)) {
try {
final long ifRangeTime = request.getDateHeader(HttpHeaders.IF_RANGE);
if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
ranges.add(full);
}
} catch (final IllegalArgumentException ignore) {
LOG.info("Invalid if-range header field", ignore);
ranges.add(full);
}
}
}
private static void multipartRangeRequest(final LocalArtifact artifact, final HttpServletResponse response,
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId,
final List<ByteRange> ranges) {
response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
try {
for (final ByteRange r : ranges) {
// Add multipart boundary and header fields for every range.
response.getOutputStream().println();
response.getOutputStream().println("--" + ByteRange.MULTIPART_BOUNDARY);
response.getOutputStream()
.println("Content-Range: bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
// Copy single part range of multi part range.
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId,
r.getStart(), r.getLength());
}
// End with final multipart boundary.
response.getOutputStream().println();
response.getOutputStream().print("--" + ByteRange.MULTIPART_BOUNDARY + "--");
} catch (final IOException e) {
LOG.error("multipartRangeRequest of file ({}) failed!", artifact.getFilename(), e);
throw new FileSteamingFailedException(artifact.getFilename());
}
}
private static void standardRangeRequest(final LocalArtifact artifact, final HttpServletResponse response,
final DbArtifact file, final CacheWriteNotify cacheWriteNotify, final Long statusId,
final List<ByteRange> ranges) {
final ByteRange r = ranges.get(0);
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal());
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength()));
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
try {
copyStreams(file.getFileInputStream(), response.getOutputStream(), cacheWriteNotify, statusId, r.getStart(),
r.getLength());
} catch (final IOException e) {
LOG.error("standardRangeRequest of file ({}) failed!", artifact.getFilename(), e);
throw new FileSteamingFailedException(artifact.getFilename());
}
}
private static long copyStreams(final InputStream from, final OutputStream to,
final CacheWriteNotify cacheWriteNotify, final Long statusId, final long start, final long length)
throws IOException {
checkNotNull(from);
checkNotNull(to);
final byte[] buf = new byte[BUFFER_SIZE];
long total = 0;
int progressPercent = 1;
// skipp until start is reached
long skipped = 0;
do {
skipped += from.skip(start);
} while (skipped < start);
long toRead = length;
boolean toContinue = true;
while (toContinue) {
final int r = from.read(buf);
if (r == -1) {
break;
}
toRead -= r;
if (toRead > 0) {
to.write(buf, 0, r);
total += r;
} else {
to.write(buf, 0, (int) toRead + r);
total += toRead + r;
toContinue = false;
}
if (cacheWriteNotify != null) {
final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, RoundingMode.DOWN);
// every 10 percent an event
if (newPercent == 100 || newPercent > progressPercent + 10) {
progressPercent = newPercent;
cacheWriteNotify.downloadProgressPercent(statusId, progressPercent);
}
}
}
return total;
}
/**
* Checks given CSV string for defined match value or * wildcard.
*
* @param matchHeader
* to search through
* @param toMatch
* to search for
*
* @return <code>true</code> if string matches.
*/
public static boolean matchesHttpHeader(final String matchHeader, final String toMatch) {
final String[] matchValues = matchHeader.split("\\s*,\\s*");
Arrays.sort(matchValues);
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
}
/**
* Convert a action rest type to a action repository type.
*
* @param actionTypeRest
* the rest type
* @return <null> or the action repository type
*/
public static ActionType convertActionType(final ActionTypeRest actionTypeRest) {
if (actionTypeRest == null) {
return null;
}
switch (actionTypeRest) {
case SOFT:
return ActionType.SOFT;
case FORCED:
return ActionType.FORCED;
case TIMEFORCED:
return ActionType.TIMEFORCED;
default:
throw new IllegalStateException("Action Type is not supported");
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.result.PrintingResultHandler;
import org.springframework.util.CollectionUtils;
public abstract class MockMvcResultPrinter {
private static final Logger LOG = LoggerFactory.getLogger(MockMvcResultPrinter.class);
private MockMvcResultPrinter() {
}
/**
* Print {@link MvcResult} details to the "standard" output stream.
*/
public static ResultHandler print() {
return new ConsolePrintingResultHandler();
}
/**
* An {@link PrintingResultHandler} that writes to the "standard" output
* stream
*/
private static class ConsolePrintingResultHandler extends PrintingResultHandler {
public ConsolePrintingResultHandler() {
super(new ResultValuePrinter() {
@Override
public void printHeading(final String heading) {
LOG.debug(String.format("%20s:", heading));
}
@Override
public void printValue(final String label, Object value) {
if (value != null && value.getClass().isArray()) {
value = CollectionUtils.arrayToList(value);
}
LOG.debug(String.format("%20s = %s", label, value));
}
});
}
}
}

View File

@@ -0,0 +1,576 @@
/**
* 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.controller;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.eventbus.event.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.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MvcResult;
import com.google.common.base.Charsets;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.net.HttpHeaders;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Test artifact downloads from the controller.
*
*
*
*
*/
@ActiveProfiles({ "im", "test" })
@Features("Component Tests - Controller RESTful API")
@Stories("Artifact Download Resource")
public class ArtifactDownloadTest extends AbstractIntegrationTestWithMongoDB {
private static final String AUTH_ANOYM = "ROLE_CONTROLLER_ANONYMOUS";
public ArtifactDownloadTest() {
LOG = LoggerFactory.getLogger(ArtifactDownloadTest.class);
}
private int downLoadProgress = 0;
@Autowired
private EventBus eventBus;
@Test
@Description("Tests non allowed requests on the artifact ressource, e.g. invalid URI, wrong if-match, wrong command.")
public void invalidRequestsOnArtifactResource() throws Exception {
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList<>();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(ds, targets);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "file1", false);
// no artifact available
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/123455",
target.getControllerId(), ds.findFirstModuleByType(osType).getId())).andExpect(status().isNotFound());
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/123455.MD5SUM",
target.getControllerId(), ds.findFirstModuleByType(osType).getId())).andExpect(status().isNotFound());
// SM does not exist
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/1234567890/artifacts/{filename}",
target.getControllerId(), artifact.getFilename())).andExpect(status().isNotFound());
mvc.perform(get("/controller/v1/{targetid}/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}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename()).header(HttpHeaders.IF_MATCH, artifact.getSha1Hash()))
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isOk());
// test failed If-match
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename()).header("If-Match", "fsjkhgjfdhg")).andExpect(status().isPreconditionFailed());
// test invalid range
mvc.perform(get("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
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}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
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}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
mvc.perform(post("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
mvc.perform(put("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
mvc.perform(post("/{tenant}/controller/v1/{targetid}/softwaremodules/{smId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(), ds.findFirstModuleByType(osType).getId(),
artifact.getFilename())).andExpect(status().isMethodNotAllowed());
}
@Test
@WithUser(principal = "4712", 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
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList<>();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.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.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "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.")
public void downloadArtifactThroughFileName() throws Exception {
downLoadProgress = 1;
eventBus.register(this);
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList<Target>();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "file1", false);
// download fails as artifact is not yet assigned
mvc.perform(get("/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
target.getControllerId(), ds.findFirstModuleByType(osType).getId(), artifact.getFilename()))
.andExpect(status().isNotFound());
// now assign and download successful
deploymentManagement.assignDistributionSet(ds, targets);
final MvcResult result = mvc
.perform(
get("/{tenant}/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
tenantAware.getCurrentTenant(), target.getControllerId(),
ds.findFirstModuleByType(osType).getId(), artifact.getFilename()))
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
.andExpect(header().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().string("Content-Disposition", "attachment;filename=" + artifact.getFilename()))
.andReturn();
assertTrue(Arrays.equals(result.getResponse().getContentAsByteArray(), random));
// download complete
assertThat(downLoadProgress).isEqualTo(10);
}
@Test
@Description("Tests valid MD5SUm file downloads through the artifact resource by identifying the artifact by ID.")
public void downloadMd5sumThroughControllerApi() throws Exception {
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "file1", false);
// download
final MvcResult result = mvc
.perform(
get("/{tenant}/controller/v1/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM",
tenantAware.getCurrentTenant(), target.getControllerId(),
ds.findFirstModuleByType(osType).getId(), artifact.getFilename()))
.andExpect(status().isOk()).andExpect(header().string("Content-Disposition",
"attachment;filename=" + artifact.getFilename() + ".MD5SUM"))
.andReturn();
assertThat(result.getResponse().getContentAsByteArray()).isEqualTo(
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;
eventBus.register(this);
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "file1.tar.bz2", false);
// download fails as artifact is not yet assigned to target
deploymentManagement.assignDistributionSet(ds, targets);
mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1.tar.bz2"))
.andExpect(status().isNotFound());
}
@Test
@WithUser(principal = "4712", authorities = "ROLE_CONTROLLER", allSpPermissions = true)
@Description("Ensures that an authenticated and named controller is permitted to download.")
public void downloadArtifactByNameByNamedController() throws Exception {
downLoadProgress = 1;
eventBus.register(this);
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024 * 1024);
final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "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
deploymentManagement.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().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
assertTrue(Arrays.equals(result.getResponse().getContentAsByteArray(), random));
// one (update) action
assertThat(actionRepository.findByTargetAndDistributionSet(pageReq, target, ds).getContent()).hasSize(1);
final Action action = actionRepository.findByTargetAndDistributionSet(pageReq, target, ds).getContent().get(0);
// one status - download
assertThat(actionStatusRepository.findAll()).hasSize(2);
assertThat(actionStatusRepository.findByAction(pageReq, action).getContent()).hasSize(2);
assertThat(actionStatusRepository.findByAction(new PageRequest(0, 400, Direction.DESC, "id"), action)
.getContent().get(0).getStatus()).isEqualTo(Status.DOWNLOAD);
// download complete
assertThat(downLoadProgress).isEqualTo(10);
}
@Test
@WithUser(principal = "4712", 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 {
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList<>();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final int resultLength = 5 * 1000 * 1024;
// create artifact
final byte random[] = RandomUtils.nextBytes(resultLength);
final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "file1", false);
assertThat(random.length).isEqualTo(resultLength);
// now assign and download successful
deploymentManagement.assignDistributionSet(ds, targets);
final int range = 100 * 1024;
// full file download with standard range request
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (int i = 0; i < resultLength / range; i++) {
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))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
.andExpect(header().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().longValue("Content-Length", range))
.andExpect(header().string("Content-Range", "bytes " + rangeString + "/" + resultLength))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
outputStream.write(result.getResponse().getContentAsByteArray());
}
assertThat(outputStream.toByteArray()).isEqualTo(random);
// return last 1000 Bytes
MvcResult result = mvc
.perform(get("/${tenant}/controller/artifacts/v1/filename/{filename}", tenantAware.getCurrentTenant(),
"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"))
.andExpect(header().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().longValue("Content-Length", 1000))
.andExpect(header().string("Content-Range",
"bytes " + (resultLength - 1000) + "-" + (resultLength - 1) + "/" + resultLength))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
assertThat(result.getResponse().getContentAsByteArray())
.isEqualTo(Arrays.copyOfRange(random, resultLength - 1000, resultLength));
// 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-"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes"))
.andExpect(header().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().longValue("Content-Length", resultLength - 1000))
.andExpect(header().string("Content-Range",
"bytes " + 1000 + "-" + (resultLength - 1) + "/" + resultLength))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
assertThat(result.getResponse().getContentAsByteArray())
.isEqualTo(Arrays.copyOfRange(random, 1000, resultLength));
// 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"))
.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"))
.andExpect(header().longValue("Last-Modified", artifact.getCreatedAt()))
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn();
outputStream.reset();
outputStream.write("\r\n--THIS_STRING_SEPARATES_MULTIPART\r\n".getBytes(StandardCharsets.ISO_8859_1));
outputStream.write(("Content-Range: bytes 0-9/" + resultLength + "\r\n").getBytes(StandardCharsets.ISO_8859_1));
outputStream.write(Arrays.copyOfRange(random, 0, 10));
outputStream.write("\r\n--THIS_STRING_SEPARATES_MULTIPART\r\n".getBytes(StandardCharsets.ISO_8859_1));
outputStream
.write(("Content-Range: bytes 10-19/" + resultLength + "\r\n").getBytes(StandardCharsets.ISO_8859_1));
outputStream.write(Arrays.copyOfRange(random, 10, 20));
outputStream.write("\r\n--THIS_STRING_SEPARATES_MULTIPART--".getBytes(StandardCharsets.ISO_8859_1));
assertThat(result.getResponse().getContentAsByteArray()).isEqualTo(outputStream.toByteArray());
}
@Test
@Description("Ensures that the download fails if te controller is not authenticated.")
public void faildDownloadArtifactByNameIfAuthenticationMissing() throws Exception {
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
// create target
Target target = new Target("4712");
target = targetManagement.createTarget(target);
final List<Target> targets = new ArrayList<>();
targets.add(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "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
Target target = new Target("4712");
target = targetManagement.createTarget(target);
// create ds
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "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));
}
@Subscribe
public void listen(final DownloadProgressEvent event) {
downLoadProgress++;
}
}

View File

@@ -0,0 +1,549 @@
/**
* 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.controller;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.JsonBuilder;
import org.junit.Test;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@ActiveProfiles({ "im", "test" })
@Features("Component Tests - Controller RESTful API")
@Stories("Cancel Action Resource")
public class CancelActionTest extends AbstractIntegrationTest {
@Test
@Description("Test of the controller can continue a started update even after a cancel command if it so desires.")
public void rootRsCancelActionButContinueAnyway() throws Exception {
// prepare test data
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0);
final Action cancelAction = deploymentManagement.cancelAction(updateAction,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
// controller rejects cancelation
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "rejected"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
final long current = System.currentTimeMillis();
// get update action anyway
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + updateAction.getId(),
tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(updateAction.getId()))))
.andExpect(jsonPath("$deployment.download", equalTo("forced")))
.andExpect(jsonPath("$deployment.update", equalTo("forced")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].version",
equalTo(ds.findFirstModuleByType(runtimeType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].version",
equalTo(ds.findFirstModuleByType(osType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].version",
equalTo(ds.findFirstModuleByType(appType).getVersion())));
// and finish it
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + updateAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(
JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed", "success"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// check database after test
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet().getId())
.isEqualTo(ds.getId());
assertThat(targetManagement.findTargetByControllerIDWithDetails("4712").getTargetInfo()
.getInstalledDistributionSet().getId()).isEqualTo(ds.getId());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getInstallationDate())
.isGreaterThanOrEqualTo(current);
}
@Test
@Description("Test for cancel operation of a update action.")
public void rootRsCancelAction() throws Exception {
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0);
long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href",
startsWith("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/deploymentBase/" + updateAction.getId())));
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
// Retrieved is reported
List<Action> activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(savedTarget);
assertThat(activeActionsByTarget).hasSize(1);
assertThat(activeActionsByTarget.get(0).getStatus()).isEqualTo(Status.RUNNING);
final Action cancelAction = deploymentManagement.cancelAction(updateAction,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(savedTarget);
// the canceled action should still be active!
assertThat(cancelAction.isActive()).isTrue();
assertThat(activeActionsByTarget).hasSize(1);
assertThat(activeActionsByTarget.get(0).getStatus()).isEqualTo(Status.CANCELING);
current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.cancelAction.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/cancelAction/" + cancelAction.getId())));
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
current = System.currentTimeMillis();
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
mvc.perform(
get("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId(), tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(cancelAction.getId()))))
.andExpect(jsonPath("$cancelAction.stopId", equalTo(String.valueOf(updateAction.getId()))));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
// controller confirmed cancelled action, should not be active anymore
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)
.content(JsonBuilder.cancelActionFeedback(updateAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(savedTarget);
assertThat(activeActionsByTarget).hasSize(0);
final Action canceledAction = deploymentManagement.findAction(cancelAction.getId());
assertThat(canceledAction.isActive()).isFalse();
assertThat(canceledAction.getStatus()).isEqualTo(Status.CANCELED);
}
@Test
@Description("Tests various bad requests and if the server handles them as expected.")
public void badCancelAction() throws Exception {
// not allowed methods
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(put("/{tenant}/controller/v1/4712/cancelAction/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/4712/cancelAction/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
// non existing target
mvc.perform(get("/{tenant}/controller/v1/34534543/cancelAction/1", tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
createCancelAction("34534543");
// wrong media type
mvc.perform(get("/{tenant}/controller/v1/34534543/cancelAction/1", tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_ATOM_XML)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotAcceptable());
}
private Action createCancelAction(final String targetid) {
final Target target = new Target(targetid);
final DistributionSet ds = TestDataUtil.generateDistributionSet(targetid, softwareManagement,
distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0);
return deploymentManagement.cancelAction(updateAction,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
}
@Test
@Description("Tests the feedback channel of the cancel operation.")
public void rootRsCancelActionFeedback() throws Exception {
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
final Action updateAction = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" })
.getActions().get(0);
// cancel action manually
final Action cancelAction = deploymentManagement.cancelAction(updateAction,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
assertThat(actionStatusRepository.findAll()).hasSize(2);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
long current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "proceeding"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(3);
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "resumed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(4);
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "scheduled"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(actionStatusRepository.findAll()).hasSize(5);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
// cancelation canceled -> should remove the action from active
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "canceled"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(actionStatusRepository.findAll()).hasSize(6);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
// cancelation rejected -> action still active until controller close it
// with finished or
// error
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "rejected"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(actionStatusRepository.findAll()).hasSize(7);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
// cancelaction closed -> should remove the action from active
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(actionStatusRepository.findAll()).hasSize(8);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(0);
}
@Test
@Description("Tests the feeback chanel of for multiple open cancel operations on the same target.")
public void multipleCancelActionFeedback() throws Exception {
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds3 = TestDataUtil.generateDistributionSet("3", softwareManagement,
distributionSetManagement, true);
final Target savedTarget = targetManagement.createTarget(target);
final Action updateAction = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" })
.getActions().get(0);
final Action updateAction2 = deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" })
.getActions().get(0);
final Action updateAction3 = deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" })
.getActions().get(0);
assertThat(actionStatusRepository.findAll()).hasSize(3);
// 3 update actions, 0 cancel actions
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(3);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(3);
final Action cancelAction = deploymentManagement.cancelAction(updateAction,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
final Action cancelAction2 = deploymentManagement.cancelAction(updateAction2,
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(3);
assertThat(deploymentManagement.findActionsByTarget(savedTarget)).hasSize(3);
mvc.perform(
get("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId(), tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(cancelAction.getId()))))
.andExpect(jsonPath("$cancelAction.stopId", equalTo(String.valueOf(updateAction.getId()))));
assertThat(actionStatusRepository.findAll()).hasSize(6);
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.cancelAction.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/cancelAction/" + cancelAction.getId())));
// now lets return feedback for the first cancelation
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(actionStatusRepository.findAll()).hasSize(7);
// 1 update actions, 1 cancel actions
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
assertThat(deploymentManagement.findActionsByTarget(savedTarget)).hasSize(3);
mvc.perform(get("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction2.getId(),
tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(cancelAction2.getId()))))
.andExpect(jsonPath("$cancelAction.stopId", equalTo(String.valueOf(updateAction2.getId()))));
assertThat(actionStatusRepository.findAll()).hasSize(8);
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.cancelAction.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/cancelAction/" + cancelAction2.getId())));
// now lets return feedback for the second cancelation
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction2.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction2.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(actionStatusRepository.findAll()).hasSize(9);
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).isEqualTo(ds3);
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + updateAction3.getId(),
tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(actionStatusRepository.findAll()).hasSize(10);
// 1 update actions, 0 cancel actions
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action cancelAction3 = deploymentManagement.cancelAction(actionRepository.findOne(updateAction3.getId()),
targetManagement.findTargetByControllerID(savedTarget.getControllerId()));
// action is in cancelling state
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
assertThat(deploymentManagement.findActionsByTarget(savedTarget)).hasSize(3);
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).isEqualTo(ds3);
mvc.perform(get("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction3.getId(),
tenantAware.getCurrentTenant()).accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(cancelAction3.getId()))))
.andExpect(jsonPath("$cancelAction.stopId", equalTo(String.valueOf(updateAction3.getId()))));
assertThat(actionStatusRepository.findAll()).hasSize(12);
// now lets return feedback for the third cancelation
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction3.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction3.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(actionStatusRepository.findAll()).hasSize(13);
// final status
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(0);
assertThat(deploymentManagement.findActionsByTarget(savedTarget)).hasSize(3);
}
@Test
@Description("Tests the feeback channel closing for too many feedbacks, i.e. denial of service prevention.")
public void tooMuchCancelActionFeedback() throws Exception {
final Target target = targetManagement.createTarget(new Target("4712"));
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(target);
final Action action = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" })
.getActions().get(0);
final Action cancelAction = deploymentManagement.cancelAction(action,
targetManagement.findTargetByControllerID(target.getControllerId()));
final String feedback = JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "proceeding");
// assignDistributionSet creates an ActionStatus and cancel action
// stores an action status, so
// only 97 action status left
for (int i = 0; i < 98; i++) {
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(feedback).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(feedback).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}
@Test
@Description("test the correct rejection of various invalid feedback requests")
public void badCancelActionFeedback() throws Exception {
final Action cancelAction = createCancelAction("4712");
final Action cancelAction2 = createCancelAction("4715");
// not allowed methods
mvc.perform(put("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(get("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
// bad content type
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_ATOM_XML).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isUnsupportedMediaType());
// bad body
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "546456456"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
// non existing target
mvc.perform(post("/{tenant}/controller/v1/12345/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// invalid action
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(JsonBuilder.cancelActionFeedback("sdfsdfsdfs", "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(JsonBuilder.cancelActionFeedback("1234", "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// right action but for wrong target
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction2.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// finally get it right :)
mvc.perform(post("/{tenant}/controller/v1/4712/cancelAction/" + cancelAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.cancelActionFeedback(cancelAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,181 @@
/**
* 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.controller;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.JsonBuilder;
import org.junit.Test;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@ActiveProfiles({ "im", "test" })
@Features("Component Tests - Controller RESTful API")
@Stories("Config Data Resource")
public class ConfigDataTest extends AbstractIntegrationTest {
@Test
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
+ "are requested only once from the device.")
public void requestConfigDataIfEmpty() throws Exception {
final Target target = new Target("4712");
final Target savedTarget = targetManagement.createTarget(target);
final long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.configData.href", equalTo(
"http://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/4712/configData")));
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
savedTarget.getTargetInfo().getControllerAttributes().put("dsafsdf", "sdsds");
final Target updateControllerAttributes = controllerManagament.updateControllerAttributes(
savedTarget.getControllerId(), savedTarget.getTargetInfo().getControllerAttributes());
// request controller attributes need to be false because we don't want
// to request the
// controller attributes again
assertThat(updateControllerAttributes.getTargetInfo().isRequestControllerAttributes()).isFalse();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.configData.href").doesNotExist());
}
@Test
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
+ "can be uploaded correctly by the controller.")
public void putConfigData() throws Exception {
targetManagement.createTarget(new Target("4717"));
// initial
final Map<String, String> attributes = new HashMap<>();
attributes.put("dsafsdf", "sdsds");
long current = System.currentTimeMillis();
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4717").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4717").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(
targetManagement.findTargetByControllerIDWithDetails("4717").getTargetInfo().getControllerAttributes())
.isEqualTo(attributes);
// update
attributes.put("sdsds", "123412");
current = System.currentTimeMillis();
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4717").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4717").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(
targetManagement.findTargetByControllerIDWithDetails("4717").getTargetInfo().getControllerAttributes())
.isEqualTo(attributes);
}
@Test
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
+ "upload limitation is inplace which is meant to protect the server from malicious attempts.")
public void putToMuchConfigData() throws Exception {
targetManagement.createTarget(new Target("4717"));
// initial
Map<String, String> attributes = new HashMap<>();
for (int i = 0; i < 10; i++) {
attributes.put("dsafsdf" + i, "sdsds" + i);
}
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
attributes = new HashMap<>();
attributes.put("on too many", "sdsds");
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}
@Test
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
+ "resource behaves as exptected in cae of invalid request attempts.")
public void badConfigData() throws Exception {
final Target target = new Target("4712");
final Target savedTarget = targetManagement.createTarget(target);
// not allowed methods
mvc.perform(post("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).//
andExpect(status().isMethodNotAllowed());
mvc.perform(get("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
// bad content type
final Map<String, String> attributes = new HashMap<>();
attributes.put("dsafsdf", "sdsds");
mvc.perform(put("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaTypes.HAL_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isUnsupportedMediaType());
// non existing target
mvc.perform(put("/{tenant}/controller/v1/456456/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// bad body
mvc.perform(put("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant())
.content("{\"id\": \"51659181\"}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
}
}

View File

@@ -0,0 +1,984 @@
/**
* 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.controller;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.rest.resource.JsonBuilder;
import org.fest.assertions.core.Condition;
import org.junit.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@ActiveProfiles({ "im", "test" })
@Features("Component Tests - Controller RESTful API")
@Stories("Deployment Action Resource")
public class DeploymentBaseTest extends AbstractIntegrationTestWithMongoDB {
@Test()
@Description("Ensures that artifact not found, when softare module not exists ")
public void testArtifactsNotFound() throws Exception {
final Target target = TestDataUtil.createTarget(targetManagement);
final Long softwareModuleIdNotExist = 1l;
mvc.perform(get("/{tenant}/controller/v1/{targetNotExist}/softwaremodules/{softwareModuleId}/artifacts",
tenantAware.getCurrentTenant(), target.getName(), softwareModuleIdNotExist))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test()
@Description("Ensures that artifact are found, when softare module exists ")
public void testArtifactsExists() throws Exception {
final Target target = TestDataUtil.createTarget(targetManagement);
final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(distributionSet.getId(), new String[] { target.getName() });
final Long softwareModuleId = distributionSet.getModules().stream().findFirst().get().getId();
mvc.perform(get("/{tenant}/controller/v1/{targetNotExist}/softwaremodules/{softwareModuleId}/artifacts",
tenantAware.getCurrentTenant(), target.getName(), softwareModuleId)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(0)));
TestDataUtil.generateArtifacts(artifactManagement, softwareModuleId);
mvc.perform(get("/{tenant}/controller/v1/{targetNotExist}/softwaremodules/{softwareModuleId}/artifacts",
tenantAware.getCurrentTenant(), target.getName(), softwareModuleId)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(3)))
.andExpect(jsonPath("$[?(@.filename==filename0)]", hasSize(1)))
.andExpect(jsonPath("$[?(@.filename==filename1)]", hasSize(1)))
.andExpect(jsonPath("$[?(@.filename==filename2)]", hasSize(1)));
}
@Test()
@Description("Ensures that software modules are found, when assigned distribution set exists with modules and atrifacts")
public void testAvailableSoftwareModulesWithArtifacts() throws Exception {
final Target target = TestDataUtil.createTarget(targetManagement);
final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(distributionSet.getId(), new String[] { target.getName() });
final Long softwareModuleId = distributionSet.getModules().stream().findFirst().get().getId();
TestDataUtil.generateArtifacts(artifactManagement, softwareModuleId);
final int modulesSize = distributionSet.getModules().size();
mvc.perform(get("/{tenant}/controller/v1/{targetExist}/softwaremodules/", tenantAware.getCurrentTenant(),
target.getName())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(modulesSize)));
}
@Test
@Description("Forced deployment to a controller. Checks if the resource reponse payload for a given deployment is as expected.")
public void deplomentForceAction() throws Exception {
// Prepare test data
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement, true);
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1", false);
final LocalArtifact artifactSignature = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1.signature", false);
final Target savedTarget = targetManagement.createTarget(target);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).isEmpty();
assertThat(actionRepository.findAll()).isEmpty();
assertThat(actionStatusRepository.findAll()).isEmpty();
List<Target> saved = deploymentManagement.assignDistributionSet(ds.getId(), ActionType.FORCED,
Action.NO_FORCE_TIME, savedTarget.getControllerId()).getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
assertThat(actionRepository.findAll()).hasSize(2);
final Action uaction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(uaction.getDistributionSet()).isEqualTo(ds);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
// Run test
long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href", startsWith("http://localhost/"
+ tenantAware.getCurrentTenant() + "/controller/v1/4712/deploymentBase/" + uaction.getId())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(actionStatusRepository.findAll()).hasSize(2);
current = System.currentTimeMillis();
Thread.sleep(1);
final DistributionSet findDistributionSetByAction = distributionSetManagement
.findDistributionSetByAction(action);
mvc.perform(
get("/{tenant}/controller/v1/4712/deploymentBase/" + uaction.getId(), tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(action.getId()))))
.andExpect(jsonPath("$deployment.download", equalTo("forced")))
.andExpect(jsonPath("$deployment.update", equalTo("forced")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].name",
equalTo(ds.findFirstModuleByType(runtimeType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].version",
equalTo(ds.findFirstModuleByType(runtimeType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].name",
equalTo(ds.findFirstModuleByType(osType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].version",
equalTo(ds.findFirstModuleByType(osType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].filename", equalTo("test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.md5",
equalTo(artifact.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.sha1",
equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].filename",
equalTo("test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.md5",
equalTo(artifactSignature.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.sha1",
equalTo(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].version",
equalTo(ds.findFirstModuleByType(appType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].name",
equalTo(ds.findFirstModuleByType(appType).getName())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
// Retrieved is reported
final Iterable<ActionStatus> actionStatusMessages = actionStatusRepository
.findAll(new PageRequest(0, 100, Direction.DESC, "id"));
assertThat(actionStatusMessages).hasSize(3);
final ActionStatus actionStatusMessage = actionStatusMessages.iterator().next();
assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED);
}
@Test
@Description("Attempt/soft deployment to a controller. Checks if the resource reponse payload for a given deployment is as expected.")
public void deplomentAttemptAction() throws Exception {
// Prepare test data
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement, true);
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1", false);
final LocalArtifact artifactSignature = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1.signature", false);
final Target savedTarget = targetManagement.createTarget(target);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).isEmpty();
assertThat(actionRepository.findAll()).isEmpty();
assertThat(actionStatusRepository.findAll()).isEmpty();
List<Target> saved = deploymentManagement
.assignDistributionSet(ds.getId(), ActionType.SOFT, Action.NO_FORCE_TIME, savedTarget.getControllerId())
.getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
assertThat(actionRepository.findAll()).hasSize(2);
final Action uaction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(uaction.getDistributionSet()).isEqualTo(ds);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
// Run test
long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href", startsWith("http://localhost/"
+ tenantAware.getCurrentTenant() + "/controller/v1/4712/deploymentBase/" + uaction.getId())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(actionStatusRepository.findAll()).hasSize(2);
current = System.currentTimeMillis();
Thread.sleep(1);
final DistributionSet findDistributionSetByAction = distributionSetManagement
.findDistributionSetByAction(action);
mvc.perform(
get("/{tenant}/controller/v1/4712/deploymentBase/" + uaction.getId(), tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(action.getId()))))
.andExpect(jsonPath("$deployment.download", equalTo("attempt")))
.andExpect(jsonPath("$deployment.update", equalTo("attempt")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].name",
equalTo(ds.findFirstModuleByType(runtimeType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].version",
equalTo(ds.findFirstModuleByType(runtimeType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].name",
equalTo(ds.findFirstModuleByType(osType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].version",
equalTo(ds.findFirstModuleByType(osType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].filename", equalTo("test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.md5",
equalTo(artifact.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.sha1",
equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].filename",
equalTo("test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.md5",
equalTo(artifactSignature.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.sha1",
equalTo(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].version",
equalTo(ds.findFirstModuleByType(appType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].name",
equalTo(ds.findFirstModuleByType(appType).getName())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
// Retrieved is reported
final Iterable<ActionStatus> actionStatusMessages = actionStatusRepository
.findAll(new PageRequest(0, 100, Direction.DESC, "id"));
assertThat(actionStatusMessages).hasSize(3);
final ActionStatus actionStatusMessage = actionStatusMessages.iterator().next();
assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED);
}
@Test
@Description("Attempt/soft deployment to a controller including automated switch to hard. Checks if the resource reponse payload for a given deployment is as expected.")
public void deplomentAutoForceAction() throws Exception {
// Prepare test data
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement, true);
final byte random[] = RandomUtils.nextBytes(5 * 1024);
final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1", false);
final LocalArtifact artifactSignature = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).getId(), "test1.signature", false);
final Target savedTarget = targetManagement.createTarget(target);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).isEmpty();
assertThat(actionRepository.findAll()).isEmpty();
assertThat(actionStatusRepository.findAll()).isEmpty();
List<Target> saved = deploymentManagement.assignDistributionSet(ds.getId(), ActionType.TIMEFORCED,
System.currentTimeMillis(), savedTarget.getControllerId()).getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedTargets();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
assertThat(actionRepository.findAll()).hasSize(2);
final Action uaction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(uaction.getDistributionSet()).isEqualTo(ds);
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(2);
// Run test
long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href", startsWith("http://localhost/"
+ tenantAware.getCurrentTenant() + "/controller/v1/4712/deploymentBase/" + uaction.getId())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(actionStatusRepository.findAll()).hasSize(2);
current = System.currentTimeMillis();
Thread.sleep(1);
final DistributionSet findDistributionSetByAction = distributionSetManagement
.findDistributionSetByAction(action);
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/{actionId}", tenantAware.getCurrentTenant(),
uaction.getId()).accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$id", equalTo(String.valueOf(action.getId()))))
.andExpect(jsonPath("$deployment.download", equalTo("forced")))
.andExpect(jsonPath("$deployment.update", equalTo("forced")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].name",
equalTo(ds.findFirstModuleByType(runtimeType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==jvm)][0].version",
equalTo(ds.findFirstModuleByType(runtimeType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].name",
equalTo(ds.findFirstModuleByType(osType).getName())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].version",
equalTo(ds.findFirstModuleByType(osType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].filename", equalTo("test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.md5",
equalTo(artifact.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0].hashes.sha1",
equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].size", equalTo(5 * 1024)))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].filename",
equalTo("test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.md5",
equalTo(artifactSignature.getMd5Hash())))
.andExpect(
jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1].hashes.sha1",
equalTo(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.md5sum.href",
equalTo("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].version",
equalTo(ds.findFirstModuleByType(appType).getVersion())))
.andExpect(jsonPath("$deployment.chunks[?(@.part==bApp)][0].name",
equalTo(ds.findFirstModuleByType(appType).getName())));
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
// Retrieved is reported
final Iterable<ActionStatus> actionStatusMessages = actionStatusRepository
.findAll(new PageRequest(0, 100, Direction.DESC, "id"));
assertThat(actionStatusMessages).hasSize(3);
final ActionStatus actionStatusMessage = actionStatusMessages.iterator().next();
assertThat(actionStatusMessage.getStatus()).isEqualTo(Status.RETRIEVED);
}
@Test
@Description("Test various invalid access attempts to the deployment resource und the expected behaviour of the server.")
public void badDeploymentAction() throws Exception {
final Target target = targetManagement.createTarget(new Target("4712"));
// not allowed methods
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(put("/{tenant}/controller/v1/4712/deploymentBase/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/4712/deploymentBase/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
// non existing target
mvc.perform(get("/{tenant}/controller/v1/4715/deploymentBase/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// no deployment
mvc.perform(get("/controller/v1/4712/deploymentBase/1", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// wrong media type
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(target);
final DistributionSet savedSet = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final Action action1 = deploymentManagement.assignDistributionSet(savedSet, toAssign).getActions().get(0);
mvc.perform(
get("/{tenant}/controller/v1/4712/deploymentBase/" + action1.getId(), tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(
get("/{tenant}/controller/v1/4712/deploymentBase/" + action1.getId(), tenantAware.getCurrentTenant())
.accept(MediaType.APPLICATION_ATOM_XML))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotAcceptable());
}
@Test
@Description("The server protects itself against to many feedback upload attempts. The test verfies that "
+ "it is not possible to exceed the configured maximum number of feedback uplods.")
public void toMuchDeplomentActionFeedback() throws Exception {
final Target target = targetManagement.createTarget(new Target("4712"));
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(target);
deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" });
final Pageable pageReq = new PageRequest(0, 100);
final Action action = actionRepository.findByDistributionSet(pageReq, ds).getContent().get(0);
final String feedback = JsonBuilder.deploymentActionFeedback(action.getId().toString(), "proceeding");
// assign distribution set creates an action status, so only 99 left
for (int i = 0; i < 99; i++) {
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(feedback).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(feedback).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}
@Test
@Description("Multiple uploads of deployment status feedback to the server.")
public void multipleDeplomentActionFeedback() throws Exception {
final Target target1 = new Target("4712");
final Target target2 = new Target("4713");
final Target target3 = new Target("4714");
final Target savedTarget1 = targetManagement.createTarget(target1);
targetManagement.createTarget(target2);
targetManagement.createTarget(target3);
final DistributionSet ds1 = TestDataUtil.generateDistributionSet("1", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement, true);
final DistributionSet ds3 = TestDataUtil.generateDistributionSet("3", softwareManagement,
distributionSetManagement, true);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget1);
final Action action1 = deploymentManagement.assignDistributionSet(ds1.getId(), new String[] { "4712" })
.getActions().get(0);
final Action action2 = deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" })
.getActions().get(0);
final Action action3 = deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" })
.getActions().get(0);
Target myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(3);
assertThat(myT.getAssignedDistributionSet()).isEqualTo(ds3);
assertThat(myT.getTargetInfo().getInstalledDistributionSet()).isNull();
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.UNKNOWN))
.hasSize(2);
// action1 done
long current = System.currentTimeMillis();
long lastModified = targetManagement.findTargetByControllerID("4712").getLastModifiedAt();
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action1.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action1.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
myT = targetManagement.findTargetByControllerIDWithDetails("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
// assertThat( myT.getLastModifiedAt() ).isEqualTo( lastModified );
final long timeDiff = Math
.abs(myT.getTargetInfo().getLastTargetQuery() - myT.getTargetInfo().getInstallationDate());
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(2);
assertThat(myT.getTargetInfo().getInstalledDistributionSet().getId()).isEqualTo(ds1.getId());
assertThat(myT.getAssignedDistributionSet()).isEqualTo(ds3);
Iterable<ActionStatus> actionStatusMessages = actionStatusRepository.findAll(new Sort(Direction.DESC, "id"));
assertThat(actionStatusMessages).hasSize(4);
assertThat(actionStatusMessages.iterator().next().getStatus()).isEqualTo(Status.FINISHED);
// action2 done
current = System.currentTimeMillis();
lastModified = targetManagement.findTargetByControllerID("4712").getLastModifiedAt();
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action2.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action2.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
myT = targetManagement.findTargetByControllerIDWithDetails("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(myT.getTargetInfo().getInstalledDistributionSet().getId()).isEqualTo(ds2.getId());
assertThat(myT.getAssignedDistributionSet()).isEqualTo(ds3);
actionStatusMessages = actionStatusRepository.findAll(new PageRequest(0, 100, Direction.DESC, "id"));
assertThat(actionStatusMessages).hasSize(5);
assertThat(actionStatusMessages).haveAtLeast(1, new ActionStatusCondition(Status.FINISHED));
// action3 done
current = System.currentTimeMillis();
lastModified = targetManagement.findTargetByControllerID("4712").getLastModifiedAt();
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action3.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action3.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(0);
assertThat(myT.getTargetInfo().getInstalledDistributionSet()).isEqualTo(ds3);
assertThat(myT.getAssignedDistributionSet()).isEqualTo(ds3);
actionStatusMessages = actionStatusRepository.findAll();
assertThat(actionStatusMessages).hasSize(6);
assertThat(actionStatusMessages).haveAtLeast(1, new ActionStatusCondition(Status.FINISHED));
}
@Test
@Description("Verfies that an update action is correctly set to error if the controller provides error feedback.")
public void rootRsSingleDeplomentActionWithErrorFeedback() throws Exception {
final Target target = new Target("4712");
DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.UNKNOWN);
deploymentManagement.assignDistributionSet(ds, toAssign);
final Action action = actionRepository.findByDistributionSet(pageReq, ds).getContent().get(0);
long current = System.currentTimeMillis();
long lastModified = targetManagement.findTargetByControllerID("4712").getLastModifiedAt();
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed", "failure"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
Target myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.ERROR);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(0);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(0);
assertThat(deploymentManagement.findActionsByTarget(myT)).hasSize(1);
final Iterable<ActionStatus> actionStatusMessages = actionStatusRepository.findAll();
assertThat(actionStatusMessages).hasSize(2);
assertThat(actionStatusMessages).haveAtLeast(1, new ActionStatusCondition(Status.ERROR));
// redo
toAssign = new ArrayList<Target>();
toAssign.add(targetManagement.findTargetByControllerID("4712"));
ds = distributionSetManagement.findDistributionSetByIdWithDetails(ds.getId());
deploymentManagement.assignDistributionSet(ds, toAssign);
final Action action2 = deploymentManagement.findActiveActionsByTarget(myT).get(0);
current = System.currentTimeMillis();
lastModified = targetManagement.findTargetByControllerID("4712").getLastModifiedAt();
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action2.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action2.getId().toString(), "closed", "success"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(1);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(0);
assertThat(deploymentManagement.findInActiveActionsByTarget(myT)).hasSize(2);
assertThat(actionStatusRepository.findAll()).hasSize(4);
assertThat(actionStatusRepository.findByAction(pageReq, action).getContent()).haveAtLeast(1,
new ActionStatusCondition(Status.ERROR));
assertThat(actionStatusRepository.findByAction(pageReq, action2).getContent()).haveAtLeast(1,
new ActionStatusCondition(Status.FINISHED));
}
@Test
@Description("Verfies that the controller can provided as much feedback entries as necessry as long as it is in the configured limites.")
public void rootRsSingleDeplomentActionFeedback() throws Exception {
final Target target = new Target("4712");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
Target myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.UNKNOWN);
deploymentManagement.assignDistributionSet(ds, toAssign);
final Action action = actionRepository.findByDistributionSet(pageReq, ds).getContent().get(0);
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(targetRepository.findByTargetInfoInstalledDistributionSet(new PageRequest(0, 10), ds)).hasSize(0);
assertThat(targetRepository.findByAssignedDistributionSet(new PageRequest(0, 10), ds)).hasSize(1);
assertThat(targetRepository
.findByAssignedDistributionSetOrTargetInfoInstalledDistributionSet(new PageRequest(0, 10), ds, ds))
.hasSize(1);
// Now valid Feedback
long current = System.currentTimeMillis();
for (int i = 0; i < 4; i++) {
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "proceeding"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(0);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(5);
assertThat(actionStatusRepository.findAll()).haveAtLeast(5, new ActionStatusCondition(Status.RUNNING));
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "scheduled"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(0);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(6);
assertThat(actionStatusRepository.findAll()).haveAtLeast(5, new ActionStatusCondition(Status.RUNNING));
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "resumed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(0);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(7);
assertThat(actionStatusRepository.findAll()).haveAtLeast(6, new ActionStatusCondition(Status.RUNNING));
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "canceled"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.PENDING))
.hasSize(1);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(0);
assertThat(actionStatusRepository.findAll()).hasSize(8);
assertThat(actionStatusRepository.findAll()).haveAtLeast(7, new ActionStatusCondition(Status.RUNNING));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.CANCELED));
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "rejected"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(9);
assertThat(actionStatusRepository.findAll()).haveAtLeast(6, new ActionStatusCondition(Status.RUNNING));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.WARNING));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.CANCELED));
current = System.currentTimeMillis();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
myT = targetManagement.findTargetByControllerID("4712");
assertThat(myT.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current);
assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(deploymentManagement.findActiveActionsByTarget(myT)).hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.ERROR))
.hasSize(0);
assertThat(targetManagement.findTargetByUpdateStatus(new PageRequest(0, 10), TargetUpdateStatus.IN_SYNC))
.hasSize(1);
assertThat(actionStatusRepository.findAll()).hasSize(10);
assertThat(actionStatusRepository.findAll()).haveAtLeast(7, new ActionStatusCondition(Status.RUNNING));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.WARNING));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.CANCELED));
assertThat(actionStatusRepository.findAll()).haveAtLeast(1, new ActionStatusCondition(Status.FINISHED));
assertThat(targetRepository.findByTargetInfoInstalledDistributionSet(new PageRequest(0, 10), ds)).hasSize(1);
assertThat(targetRepository.findByAssignedDistributionSet(new PageRequest(0, 10), ds)).hasSize(1);
assertThat(targetRepository
.findByAssignedDistributionSetOrTargetInfoInstalledDistributionSet(new PageRequest(0, 10), ds, ds))
.hasSize(1);
}
@Test
@Description("Various forbidden request appempts on the feedback resource. Ensures correct answering behaviour as expected to these kind of errors.")
public void badDeplomentActionFeedback() throws Exception {
final Target target = new Target("4712");
final Target target2 = new Target("4713");
final DistributionSet savedSet = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final DistributionSet savedSet2 = TestDataUtil.generateDistributionSet("1", softwareManagement,
distributionSetManagement);
// target does not exist
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/1234/feedback", tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionInProgressFeedback("1234")).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
Target savedTarget = targetManagement.createTarget(target);
final Target savedTarget2 = targetManagement.createTarget(target2);
// Action does not exists
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/1234/feedback", tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionInProgressFeedback("1234")).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
final List<Target> toAssign2 = new ArrayList<Target>();
toAssign2.add(savedTarget2);
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.UNKNOWN);
savedTarget = deploymentManagement.assignDistributionSet(savedSet, toAssign).getAssignedTargets().iterator()
.next();
deploymentManagement.assignDistributionSet(savedSet2, toAssign2);
// wrong format
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/AAAA/feedback", tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionInProgressFeedback("AAAA")).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
final Action updateAction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
// action exists but is not assigned to this target
mvc.perform(post("/{tenant}/controller/v1/4713/deploymentBase/" + updateAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionInProgressFeedback(updateAction.getId().toString()))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// not allowed methods
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/2/feedback", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(put("/{tenant}/controller/v1/4712/deploymentBase/2/feedback", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/{tenant}/controller/v1/4712/deploymentBase/2/feedback", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
}
private class ActionStatusCondition extends Condition<ActionStatus> {
private final Status status;
/**
* @param status
*/
public ActionStatusCondition(final Status status) {
this.status = status;
}
@Override
public boolean matches(final ActionStatus actionStatus) {
return actionStatus.getStatus() == status;
}
}
}

View File

@@ -0,0 +1,309 @@
/**
* 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.controller;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithSpringAuthorityRule;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.rest.resource.JsonBuilder;
import org.eclipse.hawkbit.util.IpUtil;
import org.junit.Test;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@ActiveProfiles({ "im", "test" })
@Features("Component Tests - Controller RESTful API")
@Stories("Root Poll Resource")
// TODO: fully document tests -> @Description for long text and reasonable
// method name as short text
public class RootControllerTest extends AbstractIntegrationTest {
@Test()
@Description("Ensures that software modules are not found, when target does not exists ")
public void testSoftwareModulesIfTargetNotExists() throws Exception {
final String targetNotExist = "targetNotExist";
mvc.perform(get("/{tenant}/controller/v1/{targetNotExist}/softwaremodules/", tenantAware.getCurrentTenant(),
targetNotExist)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test()
@Description("Ensures that software modules are not found, when assigned distribution set exists with no modules")
public void testSoftwareModulesEmptyIfDistributionSetNotExists() throws Exception {
mvc.perform(get("/{tenant}/controller/v1/{targetExist}/softwaremodules/", tenantAware.getCurrentTenant(),
TestDataUtil.createTarget(targetManagement).getName())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(0)));
}
@Test()
@Description("Ensures that software modules are found, when assigned distribution set exists with modules")
public void testAvailableSoftwareModulesWithNoArtifacts() throws Exception {
final Target target = TestDataUtil.createTarget(targetManagement);
final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(distributionSet.getId(), new String[] { target.getName() });
final int modulesSize = distributionSet.getModules().size();
mvc.perform(get("/{tenant}/controller/v1/{targetExist}/softwaremodules/", tenantAware.getCurrentTenant(),
target.getName())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(modulesSize)));
}
@Test()
@Description("Ensures that targets cannot be created e.g. in plug'n play scenarios when tenant does not exists but can be created if the tenant exists.")
@WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = "ROLE_CONTROLLER", autoCreateTenant = false)
public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception {
mvc.perform(get("/default-tenant/", tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// create tenant
systemManagement.getTenantMetadata("tenantDoesNotExists");
mvc.perform(get("/{}/controller/v1/aControllerId", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// delete tenant again
systemManagement.deleteTenant("tenantDoesNotExists");
mvc.perform(get("/{}/controller/v1/aControllerId", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
}
@Test
@WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET })
public void targetPollDoesNotModifyAuditData() throws Exception {
// create target first with "knownPrincipal" user and audit data
final String knownTargetControllerId = "target1";
final String knownCreatedBy = "knownPrincipal";
targetManagement.createTarget(new Target(knownTargetControllerId));
final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownTargetControllerId);
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
assertThat(findTargetByControllerID.getCreatedAt()).isNotNull();
// make a poll, audit information should not be changed, run as
// controller principal!
securityRule.runAs(
WithSpringAuthorityRule.withUser("controller", SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS),
new Callable<Void>() {
@Override
public Void call() throws Exception {
mvc.perform(get("/{tenant}/controller/v1/" + knownTargetControllerId,
tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
return null;
}
});
// verify that audit information has not changed
final Target targetVerify = targetManagement.findTargetByControllerID(knownTargetControllerId);
assertThat(targetVerify.getCreatedBy()).isEqualTo(findTargetByControllerID.getCreatedBy());
assertThat(targetVerify.getCreatedAt()).isEqualTo(findTargetByControllerID.getCreatedAt());
assertThat(targetVerify.getLastModifiedBy()).isEqualTo(findTargetByControllerID.getLastModifiedBy());
assertThat(targetVerify.getLastModifiedAt()).isEqualTo(findTargetByControllerID.getLastModifiedAt());
}
@Test
public void rootRsWithoutId() throws Exception {
mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test
public void rootRsPlugAndPlay() throws Exception {
final long current = System.currentTimeMillis();
mvc.perform(get("/default-tenant/controller/v1/4711")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")));
assertThat(targetManagement.findTargetByControllerID("4711").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(targetRepository.findByControllerId("4711").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.REGISTERED);
// not allowed methods
mvc.perform(post("/default-tenant/controller/v1/4711")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(put("/default-tenant/controller/v1/4711")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/default-tenant/controller/v1/4711")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
}
@Test
public void rootRsNotModified() throws Exception {
final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00"))).andReturn().getResponse()
.getHeader("ETag");
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified());
final Target target = targetRepository.findByControllerId("4711");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4711" });
final Action updateAction = deploymentManagement.findActiveActionsByTarget(target).get(0);
final String etagWithFirstUpdate = mvc
.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())
.header("If-None-Match", etag).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href",
startsWith("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4711/deploymentBase/" + updateAction.getId())))
.andReturn().getResponse().getHeader("ETag");
assertThat(etagWithFirstUpdate).isNotNull();
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match",
etagWithFirstUpdate)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified());
// now lets finish the update
mvc.perform(post("/{tenant}/controller/v1/4711/deploymentBase/" + updateAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// we are again at the original state
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified());
// Now another deployment
final DistributionSet ds2 = TestDataUtil.generateDistributionSet("2", softwareManagement,
distributionSetManagement);
deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4711" });
final Action updateAction2 = deploymentManagement.findActiveActionsByTarget(target).get(0);
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())
.header("If-None-Match", etagWithFirstUpdate).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")))
.andExpect(jsonPath("$_links.deploymentBase.href",
startsWith("http://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4711/deploymentBase/" + updateAction2.getId())))
.andReturn().getResponse().getHeader("ETag");
}
@Test
public void rootRsPrecommissioned() throws Exception {
final Target target = new Target("4711");
targetManagement.createTarget(target);
assertThat(targetRepository.findByControllerId("4711").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.UNKNOWN);
final long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andExpect(jsonPath("$config.polling.sleep", equalTo("00:01:00")));
Thread.sleep(1); // is required: otherwise processing the next line is
// often too fast and
// the following assert will fail
assertThat(targetManagement.findTargetByControllerID("4711").getTargetInfo().getLastTargetQuery())
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(targetManagement.findTargetByControllerID("4711").getTargetInfo().getLastTargetQuery())
.isGreaterThanOrEqualTo(current);
assertThat(targetRepository.findByControllerId("4711").getTargetInfo().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.REGISTERED);
}
@Test
public void rootRsPlugAndPlayIpAddress() throws Exception {
// test
final String knownControllerId1 = "0815";
mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), knownControllerId1))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// verify
final Target target = targetManagement.findTargetByControllerID(knownControllerId1);
assertThat(target.getTargetInfo().getAddress()).isEqualTo(IpUtil.createHttpUri("127.0.0.1"));
}
@Test
@Description("Controller trys to finish an update process after it has been finished by an error action status.")
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
// mock
final Target target = new Target("911");
final DistributionSet ds = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
Target savedTarget = targetManagement.createTarget(target);
final List<Target> toAssign = new ArrayList<Target>();
toAssign.add(savedTarget);
savedTarget = deploymentManagement.assignDistributionSet(ds, toAssign).getAssignedTargets().iterator().next();
final Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "failure"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback",
tenantAware.getCurrentTenant()).content(
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isGone());
}
}

View File

@@ -0,0 +1,916 @@
/**
* 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.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.fail;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.annotation.Description;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
import com.google.common.collect.Lists;
import com.jayway.jsonpath.JsonPath;
/**
*
*
*
*/
@Features("Component Tests - Management RESTful API")
@Stories("Distribution Set Resource")
// TODO: fully document tests -> @Description for long text and reasonable
// method name as short text
public class DistributionSetResourceTest extends AbstractIntegrationTest {
@Test
@Description("This test verifies the call of all Software Modules that are assiged to a Distribution Set through the RESTful API.")
public void getSoftwaremodules() throws Exception {
// Create DistributionSet with three software modules
final DistributionSet set = TestDataUtil.generateDistributionSet("SMTest", softwareManagement,
distributionSetManagement);
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + set.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(set.getModules().size())));
}
@Test
@Description("This test verifies the deletion of a assigned Software Module of a Distribution Set can not be achieved when that Distribution Set has been assigned or installed to a target.")
public void testDeleteFailureWhenDistributionSetInUse() throws Exception {
// create DisSet
final DistributionSet disSet = TestDataUtil.generateDistributionSetWithNoSoftwareModules("Eris", "560a",
distributionSetManagement);
final List<Long> smIDs = new ArrayList<Long>();
SoftwareModule sm = new SoftwareModule(osType, "Dysnomia ", "15,772", null, null);
sm = softwareManagement.createSoftwareModule(sm);
smIDs.add(sm.getId());
final JSONArray smList = new JSONArray();
for (final Long smID : smIDs) {
smList.put(new JSONObject().put("id", Long.valueOf(smID)));
}
// post assignment
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(smList.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// create targets and assign DisSet to target
final String[] knownTargetIds = new String[] { "1", "2" };
final JSONArray list = new JSONArray();
for (final String targetId : knownTargetIds) {
targetManagement.createTarget(new Target(targetId));
list.put(new JSONObject().put("id", Long.valueOf(targetId)));
}
deploymentManagement.assignDistributionSet(disSet.getId(), knownTargetIds[0]);
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
// try to delete the Software Module from DisSet that has been assigned
// to the target.
mvc.perform(
delete(
RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM/"
+ smIDs.get(0)).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isLocked())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.entitiylocked")));
}
@Test
@Description("This test verifies that the assignment of a Software Module to a Distribution Set can not be achieved when that Distribution Set has been assigned or installed to a target.")
public void testAssignmentFailureWhenAssigningToUsedDistributionSet() throws Exception {
// create DisSet
final DistributionSet disSet = TestDataUtil.generateDistributionSetWithNoSoftwareModules("Mars", "686,980",
distributionSetManagement);
final List<Long> smIDs = new ArrayList<Long>();
SoftwareModule sm = new SoftwareModule(osType, "Phobos", "0,3189", null, null);
sm = softwareManagement.createSoftwareModule(sm);
smIDs.add(sm.getId());
final JSONArray smList = new JSONArray();
for (final Long smID : smIDs) {
smList.put(new JSONObject().put("id", Long.valueOf(smID)));
}
// post assignment
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(smList.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// create Targets
final String[] knownTargetIds = new String[] { "1", "2" };
final JSONArray list = new JSONArray();
for (final String targetId : knownTargetIds) {
targetManagement.createTarget(new Target(targetId));
list.put(new JSONObject().put("id", Long.valueOf(targetId)));
}
// assign DisSet to target and test assignment
deploymentManagement.assignDistributionSet(disSet.getId(), knownTargetIds[0]);
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
// Create another SM and post assignment
final List<Long> smID2s = new ArrayList<Long>();
SoftwareModule sm2 = new SoftwareModule(appType, "Deimos", "1,262", null, null);
sm2 = softwareManagement.createSoftwareModule(sm2);
smID2s.add(sm2.getId());
final JSONArray smList2 = new JSONArray();
for (final Long smID : smID2s) {
smList2.put(new JSONObject().put("id", Long.valueOf(smID)));
}
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(smList2.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isLocked())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.entitiylocked")));
}
@Test
@Description("This test verifies the assignment of Software Modules to a Distribution Set through the RESTful API.")
public void assignSoftwaremoduleToDistributionSet() throws Exception {
// create DisSet
final DistributionSet disSet = TestDataUtil.generateDistributionSetWithNoSoftwareModules("Jupiter", "398,88",
distributionSetManagement);
// Test if size is 0
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(disSet.getModules().size())));
// create Software Modules
final List<Long> smIDs = new ArrayList<Long>();
SoftwareModule sm = new SoftwareModule(osType, "Europa", "3,551", null, null);
sm = softwareManagement.createSoftwareModule(sm);
smIDs.add(sm.getId());
SoftwareModule sm2 = new SoftwareModule(appType, "Ganymed", "7,155", null, null);
sm2 = softwareManagement.createSoftwareModule(sm2);
smIDs.add(sm2.getId());
SoftwareModule sm3 = new SoftwareModule(runtimeType, "Kallisto", "16,689", null, null);
sm3 = softwareManagement.createSoftwareModule(sm3);
smIDs.add(sm3.getId());
final JSONArray list = new JSONArray();
for (final Long smID : smIDs) {
list.put(new JSONObject().put("id", Long.valueOf(smID)));
}
// post assignment
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// Test if size is 3
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(smIDs.size())));
}
@Test
@Description("This test verifies the rejection of Software Modules of a Distribution Set through the RESTful API.")
public void unassignSoftwaremoduleFromDistributionSet() throws Exception {
// Create DistributionSet with three software modules
final DistributionSet set = TestDataUtil.generateDistributionSet("Venus", softwareManagement,
distributionSetManagement);
int amountOfSM = set.getModules().size();
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + set.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(amountOfSM)));
// test the rejection of all assigned software modules one by one
for (final Iterator<SoftwareModule> iter = set.getModules().iterator(); iter.hasNext();) {
final Long smId = iter.next().getId();
mvc.perform(
delete(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + set.getId() + "/assignedSM/" + smId))
.andExpect(status().isOk());
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + set.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(--amountOfSM)));
}
}
@Test
public void assignMultipleTargetsToDistributionSet() throws Exception {
// prepare distribution set
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
// prepare targets
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
final JSONArray list = new JSONArray();
for (final String targetId : knownTargetIds) {
targetManagement.createTarget(new Target(targetId));
list.put(new JSONObject().put("id", Long.valueOf(targetId)));
}
// assign already one target to DS
deploymentManagement.assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
mvc.perform(
post(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(list.toString())).andExpect(status().isOk())
.andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
}
@Test
public void getAssignedTargetsOfDistributionSet() throws Exception {
// prepare distribution set
final String knownTargetId = "knownTargetId1";
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
targetManagement.createTarget(new Target(knownTargetId));
deploymentManagement.assignDistributionSet(createdDs.getId(), knownTargetId);
mvc.perform(
get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets"))
.andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1)))
.andExpect(jsonPath("$.content[0].controllerId", equalTo(knownTargetId)));
}
@Test
public void getAssignedTargetsOfDistributionSetIsEmpty() throws Exception {
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
mvc.perform(
get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets"))
.andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(0)))
.andExpect(jsonPath("$.total", equalTo(0)));
}
@Test
public void getInstalledTargetsOfDistributionSet() throws Exception {
// prepare distribution set
final String knownTargetId = "knownTargetId1";
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
final Target createTarget = targetManagement.createTarget(new Target(knownTargetId));
// create some dummy targets which are not assigned or installed
targetManagement.createTarget(new Target("dummy1"));
targetManagement.createTarget(new Target("dummy2"));
// assign knownTargetId to distribution set
deploymentManagement.assignDistributionSet(createdDs.getId(), knownTargetId);
// make it in install state
sendUpdateActionStatusToTargets(createdDs, Lists.newArrayList(createTarget), Status.FINISHED, "some message");
mvc.perform(
get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/installedTargets"))
.andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1)))
.andExpect(jsonPath("$.content[0].controllerId", equalTo(knownTargetId)));
}
@Test
public void testGetDistributionSetsWithoutAddtionalRequestParameters() throws Exception {
final int modules = 5;
createDistributionSetsAlphabetical(modules);
mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(modules)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(modules)));
}
@Test
public void testGetDistributionSetsWithPagingLimitRequestParameter() throws Exception {
final int modules = 5;
final int limitSize = 1;
createDistributionSetsAlphabetical(modules);
mvc.perform(
get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING).param(
RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize)));
}
@Test
public void testGetDistributionSetsWithPagingLimitAndOffsetRequestParameter() throws Exception {
final int modules = 5;
final int offsetParam = 2;
final int expectedSize = modules - offsetParam;
createDistributionSetsAlphabetical(modules);
mvc.perform(
get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING).param(
RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam)).param(
RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(modules)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize)));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
public void testGetDistributionSets() throws Exception {
// prepare test data
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
DistributionSet set = TestDataUtil
.generateDistributionSet("one", softwareManagement, distributionSetManagement);
set.setRequiredMigrationStep(set.isRequiredMigrationStep());
set = distributionSetManagement.updateDistributionSet(set);
set.setVersion("anotherVersion");
set = distributionSetManagement.updateDistributionSet(set);
// load also lazy stuff
set = distributionSetManagement.findDistributionSetByIdWithDetails(set.getId());
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(1);
// perform request
mvc.perform(get("/rest/v1/distributionsets").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(
jsonPath("$content.[0]._links.self.href", equalTo("http://localhost/rest/v1/distributionsets/"
+ set.getId())))
.andExpect(jsonPath("$content.[0].id", equalTo(set.getId().intValue())))
.andExpect(jsonPath("$content.[0].name", equalTo(set.getName())))
.andExpect(jsonPath("$content.[0].requiredMigrationStep", equalTo(set.isRequiredMigrationStep())))
.andExpect(jsonPath("$content.[0].description", equalTo(set.getDescription())))
.andExpect(jsonPath("$content.[0].type", equalTo(set.getType().getKey())))
.andExpect(jsonPath("$content.[0].createdBy", equalTo(set.getCreatedBy())))
.andExpect(jsonPath("$content.[0].createdAt", equalTo(set.getCreatedAt())))
.andExpect(jsonPath("$content.[0].complete", equalTo(Boolean.TRUE)))
.andExpect(jsonPath("$content.[0].lastModifiedBy", equalTo(set.getLastModifiedBy())))
.andExpect(jsonPath("$content.[0].lastModifiedAt", equalTo(set.getLastModifiedAt())))
.andExpect(jsonPath("$content.[0].version", equalTo(set.getVersion())))
.andExpect(
jsonPath("$content.[0].modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(runtimeType).getId().intValue())))
.andExpect(
jsonPath("$content.[0].modules.[?(@.type==" + appType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(appType).getId().intValue())))
.andExpect(
jsonPath("$content.[0].modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(osType).getId().intValue())));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
public void testGetDistributionSet() throws Exception {
// prepare test data
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
DistributionSet set = TestDataUtil
.generateDistributionSet("one", softwareManagement, distributionSetManagement);
set.setVersion("anotherVersion");
set = distributionSetManagement.updateDistributionSet(set);
// load also lazy stuff
set = distributionSetManagement.findDistributionSetByIdWithDetails(set.getId());
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(1);
// perform request
mvc.perform(get("/rest/v1/distributionsets/{dsId}", set.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(
jsonPath("$_links.self.href",
equalTo("http://localhost/rest/v1/distributionsets/" + set.getId())))
.andExpect(jsonPath("$id", equalTo(set.getId().intValue())))
.andExpect(jsonPath("$name", equalTo(set.getName())))
.andExpect(jsonPath("$type", equalTo(set.getType().getKey())))
.andExpect(jsonPath("$description", equalTo(set.getDescription())))
.andExpect(jsonPath("$requiredMigrationStep", equalTo(set.isRequiredMigrationStep())))
.andExpect(jsonPath("$createdBy", equalTo(set.getCreatedBy())))
.andExpect(jsonPath("$complete", equalTo(Boolean.TRUE)))
.andExpect(jsonPath("$createdAt", equalTo(set.getCreatedAt())))
.andExpect(jsonPath("$lastModifiedBy", equalTo(set.getLastModifiedBy())))
.andExpect(jsonPath("$lastModifiedAt", equalTo(set.getLastModifiedAt())))
.andExpect(jsonPath("$version", equalTo(set.getVersion())))
.andExpect(
jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(runtimeType).getId().intValue())))
.andExpect(
jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(appType).getId().intValue())))
.andExpect(
jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(set
.findFirstModuleByType(osType).getId().intValue())));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
public void testCreateDistributionSets() throws JSONException, Exception {
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
final SoftwareModule ah = softwareManagement.createSoftwareModule(new SoftwareModule(appType, "agent-hub",
"1.0.1", null, ""));
final SoftwareModule jvm = softwareManagement.createSoftwareModule(new SoftwareModule(runtimeType,
"oracle-jre", "1.7.2", null, ""));
final SoftwareModule os = softwareManagement.createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2",
null, ""));
DistributionSet one = TestDataUtil.buildDistributionSet("one", "one", standardDsType, os, jvm, ah);
DistributionSet two = TestDataUtil.buildDistributionSet("two", "two", standardDsType, os, jvm, ah);
DistributionSet three = TestDataUtil.buildDistributionSet("three", "three", standardDsType, os, jvm, ah);
three.setRequiredMigrationStep(true);
final List<DistributionSet> sets = new ArrayList<>();
sets.add(one);
sets.add(two);
sets.add(three);
final long current = System.currentTimeMillis();
final MvcResult mvcResult = mvc
.perform(
post("/rest/v1/distributionsets/").content(JsonBuilder.distributionSets(sets))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0]name", equalTo(one.getName())))
.andExpect(jsonPath("[0]description", equalTo(one.getDescription())))
.andExpect(jsonPath("[0]type", equalTo(standardDsType.getKey())))
.andExpect(jsonPath("[0]createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[0]version", equalTo(one.getVersion())))
.andExpect(jsonPath("[0]complete", equalTo(Boolean.TRUE)))
.andExpect(jsonPath("[0]requiredMigrationStep", equalTo(one.isRequiredMigrationStep())))
.andExpect(
jsonPath("[0].modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", equalTo(one
.findFirstModuleByType(runtimeType).getId().intValue())))
.andExpect(
jsonPath("[0].modules.[?(@.type==" + appType.getKey() + ")][0].id", equalTo(one
.findFirstModuleByType(appType).getId().intValue())))
.andExpect(
jsonPath("[0].modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(one
.findFirstModuleByType(osType).getId().intValue())))
.andExpect(jsonPath("[1]name", equalTo(two.getName())))
.andExpect(jsonPath("[1]description", equalTo(two.getDescription())))
.andExpect(jsonPath("[1]complete", equalTo(Boolean.TRUE)))
.andExpect(jsonPath("[1]type", equalTo(standardDsType.getKey())))
.andExpect(jsonPath("[1]createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[1]version", equalTo(two.getVersion())))
.andExpect(
jsonPath("[1].modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", equalTo(two
.findFirstModuleByType(runtimeType).getId().intValue())))
.andExpect(
jsonPath("[1].modules.[?(@.type==" + appType.getKey() + ")][0].id", equalTo(two
.findFirstModuleByType(appType).getId().intValue())))
.andExpect(
jsonPath("[1].modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(two
.findFirstModuleByType(osType).getId().intValue())))
.andExpect(jsonPath("[1]requiredMigrationStep", equalTo(two.isRequiredMigrationStep())))
.andExpect(jsonPath("[2]name", equalTo(three.getName())))
.andExpect(jsonPath("[2]description", equalTo(three.getDescription())))
.andExpect(jsonPath("[2]complete", equalTo(Boolean.TRUE)))
.andExpect(jsonPath("[2]type", equalTo(standardDsType.getKey())))
.andExpect(jsonPath("[2]createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[2]version", equalTo(three.getVersion())))
.andExpect(
jsonPath("[2].modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", equalTo(three
.findFirstModuleByType(runtimeType).getId().intValue())))
.andExpect(
jsonPath("[2].modules.[?(@.type==" + appType.getKey() + ")][0].id", equalTo(three
.findFirstModuleByType(appType).getId().intValue())))
.andExpect(
jsonPath("[2].modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(three
.findFirstModuleByType(osType).getId().intValue())))
.andExpect(jsonPath("[2]requiredMigrationStep", equalTo(three.isRequiredMigrationStep()))).andReturn();
one = distributionSetManagement.findDistributionSetByIdWithDetails(distributionSetManagement
.findDistributionSetByNameAndVersion("one", "one").getId());
two = distributionSetManagement.findDistributionSetByIdWithDetails(distributionSetManagement
.findDistributionSetByNameAndVersion("two", "two").getId());
three = distributionSetManagement.findDistributionSetByIdWithDetails(distributionSetManagement
.findDistributionSetByNameAndVersion("three", "three").getId());
assertThat(
JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsets/" + one.getId());
assertThat(
JsonPath.compile("[0]_links.type.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + one.getType().getId());
assertThat(JsonPath.compile("[0]id").read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
String.valueOf(one.getId()));
assertThat(
JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsets/" + two.getId());
assertThat(
JsonPath.compile("[1]_links.type.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + two.getType().getId());
assertThat(JsonPath.compile("[1]id").read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
String.valueOf(two.getId()));
assertThat(
JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsets/" + three.getId());
assertThat(
JsonPath.compile("[2]_links.type.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + three.getType().getId());
assertThat(JsonPath.compile("[2]id").read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
String.valueOf(three.getId()));
// check in database
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(3);
assertThat(one.isRequiredMigrationStep()).isEqualTo(false);
assertThat(two.isRequiredMigrationStep()).isEqualTo(false);
assertThat(three.isRequiredMigrationStep()).isEqualTo(true);
assertThat(one.getCreatedAt()).isGreaterThanOrEqualTo(current);
assertThat(two.getCreatedAt()).isGreaterThanOrEqualTo(current);
assertThat(three.getCreatedAt()).isGreaterThanOrEqualTo(current);
}
@Test
public void testDeleteUnassignedistributionSet() throws Exception {
// prepare test data
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
final DistributionSet set = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(1);
// perform request
mvc.perform(delete("/rest/v1/distributionsets/{smId}", set.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check repository content
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).isEmpty();
assertThat(distributionSetRepository.findAll()).isEmpty();
}
@Test
public void testDeleteAssignedDistributionSet() throws Exception {
// prepare test data
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
final DistributionSet set = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
targetManagement.createTarget(new Target("test"));
deploymentManagement.assignDistributionSet(set.getId(), "test");
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(1);
// perform request
mvc.perform(delete("/rest/v1/distributionsets/{smId}", set.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check repository content
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, true, true)).hasSize(1);
}
@Test
public void testUpdateDistributionSet() throws Exception {
// prepare test data
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0);
final DistributionSet set = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(1);
final DistributionSet update = new DistributionSet();
update.setVersion("anotherVersion");
update.setName(null);
update.setType(standardDsType);
mvc.perform(
put("/rest/v1/distributionsets/{dsId}", set.getId()).content(JsonBuilder.distributionSet(update))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(
distributionSetManagement.findDistributionSetsAll(pageReq, false, true).getContent().get(0)
.getVersion()).isEqualTo("anotherVersion");
assertThat(
distributionSetManagement.findDistributionSetsAll(pageReq, false, true).getContent().get(0).getName())
.isEqualTo(set.getName());
}
@Test
public void testInvalidRequestsOnDistributionSetsResource() throws Exception {
final DistributionSet set = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
final List<DistributionSet> sets = new ArrayList<>();
sets.add(set);
// SM does not exist
mvc.perform(get("/rest/v1/distributionsets/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
mvc.perform(delete("/rest/v1/distributionsets/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// bad request - no content
mvc.perform(post("/rest/v1/distributionsets").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
// bad request - bad content
mvc.perform(
post("/rest/v1/distributionsets").content("sdfjsdlkjfskdjf".getBytes()).contentType(
MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
// unsupported media type
mvc.perform(
post("/rest/v1/distributionsets").content(JsonBuilder.distributionSets(sets)).contentType(
MediaType.APPLICATION_OCTET_STREAM)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isUnsupportedMediaType());
// not allowed methods
mvc.perform(post("/rest/v1/distributionsets/{smId}", set.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(put("/rest/v1/distributionsets")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/rest/v1/distributionsets")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
}
@Test
public void createMetadata() throws Exception {
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
final String knownKey1 = "knownKey1";
final String knownKey2 = "knownKey2";
final String knownValue1 = "knownValue1";
final String knownValue2 = "knownValue2";
final JSONArray jsonArray = new JSONArray();
jsonArray.put(new JSONObject().put("key", knownKey1).put("value", knownValue1));
jsonArray.put(new JSONObject().put("key", knownKey2).put("value", knownValue2));
mvc.perform(
post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).contentType(
MediaType.APPLICATION_JSON).content(jsonArray.toString())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0]key", equalTo(knownKey1)))
.andExpect(jsonPath("[0]value", equalTo(knownValue1)))
.andExpect(jsonPath("[1]key", equalTo(knownKey2)))
.andExpect(jsonPath("[1]value", equalTo(knownValue2)));
final DistributionSetMetadata metaKey1 = distributionSetManagement.findOne(new DsMetadataCompositeKey(testDS,
knownKey1));
final DistributionSetMetadata metaKey2 = distributionSetManagement.findOne(new DsMetadataCompositeKey(testDS,
knownKey2));
assertThat(metaKey1.getValue()).isEqualTo(knownValue1);
assertThat(metaKey2.getValue()).isEqualTo(knownValue2);
}
@Test
public void updateMetadata() throws Exception {
// prepare and create metadata for update
final String knownKey = "knownKey";
final String knownValue = "knownValue";
final String updateValue = "valueForUpdate";
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
distributionSetManagement.createDistributionSetMetadata(new DistributionSetMetadata(knownKey, testDS,
knownValue));
final JSONObject jsonObject = new JSONObject().put("key", knownKey).put("value", updateValue);
mvc.perform(
put("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey).contentType(
MediaType.APPLICATION_JSON).content(jsonObject.toString())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("key", equalTo(knownKey))).andExpect(jsonPath("value", equalTo(updateValue)));
final DistributionSetMetadata assertDS = distributionSetManagement.findOne(new DsMetadataCompositeKey(testDS,
knownKey));
assertThat(assertDS.getValue()).isEqualTo(updateValue);
}
@Test
public void deleteMetadata() throws Exception {
// prepare and create metadata for deletion
final String knownKey = "knownKey";
final String knownValue = "knownValue";
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
distributionSetManagement.createDistributionSetMetadata(new DistributionSetMetadata(knownKey, testDS,
knownValue));
mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
try {
distributionSetManagement.findOne(new DsMetadataCompositeKey(testDS, knownKey));
fail("expected EntityNotFoundException but didn't throw");
} catch (final EntityNotFoundException e) {
// ok as expected
}
}
@Test
public void getSingleMetadata() throws Exception {
// prepare and create metadata
final String knownKey = "knownKey";
final String knownValue = "knownValue";
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
distributionSetManagement.createDistributionSetMetadata(new DistributionSetMetadata(knownKey, testDS,
knownValue));
mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("key", equalTo(knownKey))).andExpect(jsonPath("value", equalTo(knownValue)));
}
@Test
public void getPagedListofMetadata() throws Exception {
final int totalMetadata = 10;
final int limitParam = 5;
final String offsetParam = "0";
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
for (int index = 0; index < totalMetadata; index++) {
distributionSetManagement.createDistributionSetMetadata(new DistributionSetMetadata(knownKeyPrefix + index,
distributionSetManagement.findDistributionSetById(testDS.getId()), knownValuePrefix + index));
}
mvc.perform(
get("/rest/v1/distributionsets/{dsId}/metadata?offset=" + offsetParam + "&limit=" + limitParam,
testDS.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("size", equalTo(limitParam))).andExpect(jsonPath("total", equalTo(totalMetadata)));
}
@Test
public void searchDistributionSetRsql() throws Exception {
final String dsSuffix = "test";
final int amount = 10;
TestDataUtil.generateDistributionSets(dsSuffix, amount, softwareManagement, distributionSetManagement);
TestDataUtil.generateDistributionSet("DS1test", softwareManagement, distributionSetManagement);
TestDataUtil.generateDistributionSet("DS2test", softwareManagement, distributionSetManagement);
final String rsqlFindLikeDs1OrDs2 = "name==DS1test,name==DS2test";
mvc.perform(get("/rest/v1/distributionsets?q=" + rsqlFindLikeDs1OrDs2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(2)))
.andExpect(jsonPath("total", equalTo(2))).andExpect(jsonPath("content[0].name", equalTo("DS1test")))
.andExpect(jsonPath("content[1].name", equalTo("DS2test")));
}
@Ignore
@Test
public void filterDistributionSetComplete() throws Exception {
final int amount = 10;
TestDataUtil.generateDistributionSets(amount, softwareManagement, distributionSetManagement);
final String rsqlFindLikeDs1OrDs2 = "complete==" + Boolean.TRUE;
mvc.perform(get("/rest/v1/distributionsets?q=" + rsqlFindLikeDs1OrDs2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(10)))
.andExpect(jsonPath("total", equalTo(10)));
}
@Test
public void searchDistributionSetAssignedTargetsRsql() throws Exception {
// prepare distribution set
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
// prepare targets
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
final JSONArray list = new JSONArray();
for (final String targetId : knownTargetIds) {
targetManagement.createTarget(new Target(targetId));
list.put(new JSONObject().put("id", Long.valueOf(targetId)));
}
// assign already one target to DS
deploymentManagement.assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
final String rsqlFindTargetId1 = "controllerId==1";
mvc.perform(
get(
RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId()
+ "/assignedTargets?q=" + rsqlFindTargetId1).contentType(MediaType.APPLICATION_JSON)
.content(list.toString())).andExpect(status().isOk()).andExpect(jsonPath("total", equalTo(1)))
.andExpect(jsonPath("size", equalTo(1))).andExpect(jsonPath("content[0].controllerId", equalTo("1")));
}
@Test
public void searchDistributionSetMetadataRsql() throws Exception {
final int totalMetadata = 10;
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement,
distributionSetManagement);
for (int index = 0; index < totalMetadata; index++) {
distributionSetManagement.createDistributionSetMetadata(new DistributionSetMetadata(knownKeyPrefix + index,
distributionSetManagement.findDistributionSetById(testDS.getId()), knownValuePrefix + index));
}
final String rsqlSearchValue1 = "value==knownValue1";
mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata?q=" + rsqlSearchValue1, testDS.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(1)))
.andExpect(jsonPath("total", equalTo(1))).andExpect(jsonPath("content[0].key", equalTo("knownKey1")))
.andExpect(jsonPath("content[0].value", equalTo("knownValue1")));
}
private Set<DistributionSet> createDistributionSetsAlphabetical(final int amount) {
char character = 'a';
final Set<DistributionSet> created = new HashSet<>();
for (int index = 0; index < amount; index++) {
final String str = String.valueOf(character);
created.add(TestDataUtil.generateDistributionSet(str, softwareManagement, distributionSetManagement));
character++;
}
return created;
}
private List<Target> sendUpdateActionStatusToTargets(final DistributionSet dsA, final Iterable<Target> targs,
final Status status, final String... msgs) {
final List<Target> result = new ArrayList<Target>();
for (final Target t : targs) {
final List<Action> findByTarget = actionRepository.findByTarget(t);
for (final Action action : findByTarget) {
result.add(sendUpdateActionStatusToTarget(status, action, t, msgs));
}
}
return result;
}
private Target sendUpdateActionStatusToTarget(final Status status, final Action updActA, final Target t,
final String... msgs) {
updActA.setStatus(status);
final ActionStatus statusMessages = new ActionStatus();
statusMessages.setAction(updActA);
statusMessages.setOccurredAt(System.currentTimeMillis());
statusMessages.setStatus(status);
for (final String msg : msgs) {
statusMessages.addMessage(msg);
}
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
return targetManagement.findTargetByControllerID(t.getControllerId());
}
}

View File

@@ -0,0 +1,551 @@
/**
* 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.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import com.google.common.collect.Lists;
import com.jayway.jsonpath.JsonPath;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Test for {@link DistributionSetTypeResource}.
*
*
*
*
*/
@Features("Component Tests - Management RESTful API")
@Stories("Distribution Set Type Resource")
public class DistributionSetTypeResourceTest extends AbstractIntegrationTest {
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes GET requests.")
public void getDistributionSetTypes() throws Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
testType.setDescription("Desc1234");
testType = distributionSetManagement.updateDistributionSetType(testType);
mvc.perform(get("/rest/v1/distributionsettypes").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content.[?(@.key==" + standardDsType.getKey() + ")][0].name",
equalTo(standardDsType.getName())))
.andExpect(jsonPath("$content.[?(@.key==" + standardDsType.getKey() + ")][0].description",
equalTo(standardDsType.getDescription())))
.andExpect(jsonPath("$content.[?(@.key==" + standardDsType.getKey() + ")][0].key",
equalTo(standardDsType.getKey())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].name", equalTo("TestName123")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].description", equalTo("Desc1234")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].lastModifiedBy", equalTo("uploadTester")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].lastModifiedAt",
equalTo(testType.getLastModifiedAt())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].key", equalTo("test123")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0]._links.self.href",
equalTo("http://localhost/rest/v1/distributionsettypes/" + testType.getId())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0]._links.mandatorymodules.href",
equalTo("http://localhost/rest/v1/distributionsettypes/" + testType.getId()
+ "/mandatorymoduletypes")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0]._links.optionalmodules.href", equalTo(
"http://localhost/rest/v1/distributionsettypes/" + testType.getId() + "/optionalmoduletypes")))
.andExpect(jsonPath("$total", equalTo(4)));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes POST requests.")
public void createDistributionSetTypes() throws JSONException, Exception {
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(3);
final List<DistributionSetType> types = new ArrayList<>();
types.add(new DistributionSetType("test1", "TestName1", "Desc1").addMandatoryModuleType(osType)
.addOptionalModuleType(runtimeType));
types.add(new DistributionSetType("test2", "TestName2", "Desc2").addOptionalModuleType(osType)
.addOptionalModuleType(runtimeType).addOptionalModuleType(appType));
types.add(new DistributionSetType("test3", "TestName3", "Desc3").addMandatoryModuleType(osType)
.addMandatoryModuleType(runtimeType));
final MvcResult mvcResult = mvc
.perform(post("/rest/v1/distributionsettypes/").content(JsonBuilder.distributionSetTypes(types))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0].name", equalTo("TestName1"))).andExpect(jsonPath("[0].key", equalTo("test1")))
.andExpect(jsonPath("[0].description", equalTo("Desc1")))
.andExpect(jsonPath("[0].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[1].name", equalTo("TestName2"))).andExpect(jsonPath("[1].key", equalTo("test2")))
.andExpect(jsonPath("[1].description", equalTo("Desc2")))
.andExpect(jsonPath("[1].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[2].name", equalTo("TestName3"))).andExpect(jsonPath("[2].key", equalTo("test3")))
.andExpect(jsonPath("[2].description", equalTo("Desc3")))
.andExpect(jsonPath("[2].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[2].createdAt", not(equalTo(0)))).andReturn();
final DistributionSetType created1 = distributionSetManagement.findDistributionSetTypeByKey("test1");
final DistributionSetType created2 = distributionSetManagement.findDistributionSetTypeByKey("test2");
final DistributionSetType created3 = distributionSetManagement.findDistributionSetTypeByKey("test3");
assertThat(created1.getMandatoryModuleTypes()).containsOnly(osType);
assertThat(created1.getOptionalModuleTypes()).containsOnly(runtimeType);
assertThat(created2.getOptionalModuleTypes()).containsOnly(osType, runtimeType, appType);
assertThat(created3.getMandatoryModuleTypes()).containsOnly(osType, runtimeType);
assertThat(
JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + created1.getId());
assertThat(
JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + created2.getId());
assertThat(
JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/distributionsettypes/" + created3.getId());
assertThat(JsonPath.compile("[0]_links.mandatorymodules.href")
.read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created1.getId() + "/mandatorymoduletypes");
assertThat(JsonPath.compile("[1]_links.mandatorymodules.href")
.read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created2.getId() + "/mandatorymoduletypes");
assertThat(JsonPath.compile("[2]_links.mandatorymodules.href")
.read(mvcResult.getResponse().getContentAsString()).toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created3.getId() + "/mandatorymoduletypes");
assertThat(JsonPath.compile("[0]_links.optionalmodules.href").read(mvcResult.getResponse().getContentAsString())
.toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created1.getId() + "/optionalmoduletypes");
assertThat(JsonPath.compile("[1]_links.optionalmodules.href").read(mvcResult.getResponse().getContentAsString())
.toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created2.getId() + "/optionalmoduletypes");
assertThat(JsonPath.compile("[2]_links.optionalmodules.href").read(mvcResult.getResponse().getContentAsString())
.toString()).isEqualTo(
"http://localhost/rest/v1/distributionsettypes/" + created3.getId() + "/optionalmoduletypes");
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(6);
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes POST requests.")
public void addMandatoryModuleToDistributionSetType() throws JSONException, Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes", testType.getId())
.content("{\"id\":" + osType.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
testType = distributionSetManagement.findDistributionSetTypeById(testType.getId());
assertThat(testType.getLastModifiedBy()).isEqualTo("uploadTester");
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
assertThat(testType.getOptionalModuleTypes()).isEmpty();
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/optionalmoduletypes POST requests.")
public void addOptionalModuleToDistributionSetType() throws JSONException, Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes", testType.getId())
.content("{\"id\":" + osType.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
testType = distributionSetManagement.findDistributionSetTypeById(testType.getId());
assertThat(testType.getLastModifiedBy()).isEqualTo("uploadTester");
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getOptionalModuleTypes()).containsExactly(osType);
assertThat(testType.getMandatoryModuleTypes()).isEmpty();
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes GET requests.")
public void getMandatoryModulesOfDistributionSetType() throws JSONException, Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(get("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes", testType.getId())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0].name", equalTo(osType.getName())))
.andExpect(jsonPath("[0].description", equalTo(osType.getDescription())))
.andExpect(jsonPath("[0].maxAssignments", equalTo(1))).andExpect(jsonPath("[0].key", equalTo("os")));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/optionalmoduletypes GET requests.")
public void getOptionalModulesOfDistributionSetType() throws JSONException, Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(get("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes", testType.getId())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0].name", equalTo(appType.getName())))
.andExpect(jsonPath("[0].description", equalTo(appType.getDescription())))
.andExpect(jsonPath("[0].maxAssignments", equalTo(1)))
.andExpect(jsonPath("[0].key", equalTo("application")));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes/{ID} GET requests.")
public void getMandatoryModuleOfDistributionSetType() throws JSONException, Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(get("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes/{smtId}", testType.getId(),
osType.getId()).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$id", equalTo(osType.getId().intValue())))
.andExpect(jsonPath("$name", equalTo(osType.getName())))
.andExpect(jsonPath("$description", equalTo(osType.getDescription())))
.andExpect(jsonPath("$createdBy", equalTo(osType.getCreatedBy())))
.andExpect(jsonPath("$createdAt", equalTo(osType.getCreatedAt())))
.andExpect(jsonPath("$lastModifiedBy", equalTo(osType.getLastModifiedBy())))
.andExpect(jsonPath("$lastModifiedAt", equalTo(osType.getLastModifiedAt())));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/optionalmoduletypes/{ID} GET requests.")
public void getOptionalModuleOfDistributionSetType() throws JSONException, Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(get("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes/{smtId}", testType.getId(),
appType.getId()).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$id", equalTo(appType.getId().intValue())))
.andExpect(jsonPath("$name", equalTo(appType.getName())))
.andExpect(jsonPath("$description", equalTo(appType.getDescription())))
.andExpect(jsonPath("$createdBy", equalTo(appType.getCreatedBy())))
.andExpect(jsonPath("$createdAt", equalTo(appType.getCreatedAt())))
.andExpect(jsonPath("$lastModifiedBy", equalTo(appType.getLastModifiedBy())))
.andExpect(jsonPath("$lastModifiedAt", equalTo(appType.getLastModifiedAt())));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes/{ID} DELETE requests.")
public void removeMandatoryModuleToDistributionSetType() throws JSONException, Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(delete("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes/{smtId}", testType.getId(),
osType.getId()).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
testType = distributionSetManagement.findDistributionSetTypeById(testType.getId());
assertThat(testType.getLastModifiedBy()).isEqualTo("uploadTester");
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).isEmpty();
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/optionalmoduletypes/{ID} DELETE requests.")
public void removeOptionalModuleToDistributionSetType() throws JSONException, Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123")
.addMandatoryModuleType(osType).addOptionalModuleType(appType));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
assertThat(testType.getOptionalModuleTypes()).containsExactly(appType);
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
mvc.perform(delete("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes/{smtId}", testType.getId(),
appType.getId()).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
testType = distributionSetManagement.findDistributionSetTypeById(testType.getId());
assertThat(testType.getLastModifiedBy()).isEqualTo("uploadTester");
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getOptionalModuleTypes()).isEmpty();
assertThat(testType.getMandatoryModuleTypes()).containsExactly(osType);
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID} GET requests.")
public void getDistributionSetType() throws Exception {
DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
testType.setDescription("Desc1234");
testType = distributionSetManagement.updateDistributionSetType(testType);
mvc.perform(get("/rest/v1/distributionsettypes/{dstId}", testType.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$name", equalTo("TestName123")))
.andExpect(jsonPath("$description", equalTo("Desc1234")))
.andExpect(jsonPath("$createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("$createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$lastModifiedBy", equalTo("uploadTester")));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/DistributionSetTypes/{ID} DELETE requests (hard delete scenario).")
public void deleteDistributionSetTypeUnused() throws Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(4);
mvc.perform(delete("/rest/v1/distributionsettypes/{dsId}", testType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(3);
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/DistributionSetTypes/{ID} DELETE requests (soft delete scenario).")
public void deleteDistributionSetTypeUsed() throws Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
distributionSetManagement
.createDistributionSet(new DistributionSet("sdfsd", "dsfsdf", "sdfsdf", testType, null));
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(4);
assertThat(distributionSetTypeRepository.count()).isEqualTo(4);
mvc.perform(delete("/rest/v1/distributionsettypes/{smId}", testType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(distributionSetTypeRepository.count()).isEqualTo(4);
assertThat(distributionSetManagement.countDistributionSetTypesAll()).isEqualTo(3);
}
@Test
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID} PUT requests.")
public void updateDistributionSetTypeOnlyDescriptionAndNameUntouched() throws Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
final String body = new JSONObject().put("id", testType.getId()).put("description", "foobardesc")
.put("name", "nameShouldNotBeChanged").toString();
mvc.perform(put("/rest/v1/distributionsettypes/{smId}", testType.getId()).content(body)
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$description", equalTo("foobardesc")))
.andExpect(jsonPath("$name", equalTo("TestName123"))).andReturn();
}
@Test
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes GET requests with paging.")
public void getDistributionSetTypesWithoutAddtionalRequestParameters() throws Exception {
final int types = 3;
mvc.perform(get(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(types)));
}
@Test
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes GET requests with paging.")
public void getDistributionSetTypesWithPagingLimitRequestParameter() throws Exception {
final int types = 3;
final int limitSize = 1;
mvc.perform(get(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)
.param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize)));
}
@Test
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes GET requests with paging.")
public void getDistributionSetTypesWithPagingLimitAndOffsetRequestParameter() throws Exception {
final int types = 3;
final int offsetParam = 2;
final int expectedSize = types - offsetParam;
mvc.perform(get(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING)
.param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam))
.param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(types)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize)));
}
@Test
@Description("Ensures that the server is behaving as expected on invalid requests (wrong media type, wrong ID etc.).")
public void invalidRequestsOnDistributionSetTypesResource() throws Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
final SoftwareModuleType testSmType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<DistributionSetType> types = new ArrayList<>();
types.add(testType);
// DST does not exist
mvc.perform(get("/rest/v1/distributionsettypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
mvc.perform(get("/rest/v1/distributionsettypes/12345678/mandatorymoduletypes"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(get("/rest/v1/distributionsettypes/12345678/optionalmoduletypes"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(delete("/rest/v1/distributionsettypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// Module types incorrect
mvc.perform(get("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/mandatorymoduletypes"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(get("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/mandatorymoduletypes/565765656"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(get("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/optionalmoduletypes/565765656"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(get(
"/rest/v1/distributionsettypes/" + standardDsType.getId() + "/mandatorymoduletypes/" + osType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(get("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/mandatorymoduletypes/"
+ testSmType.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(get(
"/rest/v1/distributionsettypes/" + standardDsType.getId() + "/optionalmoduletypes/" + osType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(get(
"/rest/v1/distributionsettypes/" + standardDsType.getId() + "/optionalmoduletypes/" + appType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(get("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/optionalmoduletypes/"
+ testSmType.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// Modules types at creation time invalid
final DistributionSetType testNewType = new DistributionSetType("test123", "TestName123", "Desc123");
testNewType.addMandatoryModuleType(new SoftwareModuleType("foo", "bar", "test", Integer.MAX_VALUE));
mvc.perform(post("/rest/v1/distributionsettypes")
.content(JsonBuilder.distributionSetTypes(Lists.newArrayList(testNewType)))
.contentType(MediaType.APPLICATION_OCTET_STREAM)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isUnsupportedMediaType());
// bad request - no content
mvc.perform(post("/rest/v1/distributionsettypes").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
// bad request - bad content
mvc.perform(post("/rest/v1/distributionsettypes").content("sdfjsdlkjfskdjf".getBytes())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
// not allowed methods
mvc.perform(put("/rest/v1/distributionsettypes")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/rest/v1/distributionsettypes")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(put("/rest/v1/distributionsettypes/" + standardDsType.getId() + "/mandatorymoduletypes"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(put(
"/rest/v1/distributionsettypes/" + standardDsType.getId() + "/optionalmoduletypes/" + osType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
}
@Test
@Description("Search erquest of software module types.")
public void searchDistributionSetTypeRsql() throws Exception {
final DistributionSetType testType = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test123", "TestName123", "Desc123"));
final DistributionSetType testType2 = distributionSetManagement
.createDistributionSetType(new DistributionSetType("test1234", "TestName1234", "Desc123"));
final String rsqlFindLikeDs1OrDs2 = "name==TestName123,name==TestName1234";
mvc.perform(get("/rest/v1/distributionsettypes?q=" + rsqlFindLikeDs1OrDs2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(2)))
.andExpect(jsonPath("total", equalTo(2))).andExpect(jsonPath("content[0].name", equalTo("TestName123")))
.andExpect(jsonPath("content[1].name", equalTo("TestName1234")));
}
private void createSoftwareModulesAlphabetical(final int amount) {
char character = 'a';
for (int index = 0; index < amount; index++) {
final String str = String.valueOf(character);
final SoftwareModule softwareModule = new SoftwareModule(osType, str, str, str, str);
softwareManagement.createSoftwareModule(softwareModule);
character++;
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.rest.resource;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
*
*
*/
@Features("Component Tests- Download Restful API")
@Stories("Download Resource")
public class DownloadResourceTest extends AbstractIntegrationTestWithMongoDB {
@Autowired
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE)
private Cache downloadIdCache;
private final String downloadIdSha1 = "downloadIdSha1";
private final String downloadIdNotAvailable = "downloadIdNotAvailable";
@Before
public void setupCache() {
final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("Test", softwareManagement,
distributionSetManagement);
final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get();
final Artifact artifact = TestDataUtil.generateArtifacts(artifactManagement, softwareModule.getId()).stream()
.findFirst().get();
downloadIdCache.put(downloadIdSha1, new DownloadArtifactCache(DownloadType.BY_SHA1, artifact.getSha1Hash()));
}
@Test
@Description("This test verifies the call of download artifact without a valid download id fails.")
public void testNoDownloadIdAvailable() throws Exception {
mvc.perform(
get(RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
downloadIdNotAvailable))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test
@Description("This test verifies the call of download artifact works and the download id will be removed.")
public void testDownload() throws Exception {
mvc.perform(
get(RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
downloadIdSha1))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// because cache is empty
mvc.perform(
get(RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING_BASE + RestConstants.DOWNLOAD_ID_V1_REQUEST_MAPPING,
downloadIdSha1))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
}

View File

@@ -0,0 +1,263 @@
/**
* 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.rest.resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Builder class for building certain json strings.
*
*
*
*/
public abstract class JsonBuilder {
public static String softwareModules(final List<SoftwareModule> modules) throws JSONException {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final SoftwareModule module : modules) {
try {
builder.append(new JSONObject().put("name", module.getName())
.put("description", module.getDescription()).put("type", module.getType().getKey())
.put("id", Long.MAX_VALUE).put("vendor", module.getVendor()).put("version", module.getVersion())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
if (++i < modules.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
public static String softwareModuleTypes(final List<SoftwareModuleType> types) throws JSONException {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final SoftwareModuleType module : types) {
try {
builder.append(new JSONObject().put("name", module.getName())
.put("description", module.getDescription()).put("id", Long.MAX_VALUE)
.put("key", module.getKey()).put("maxAssignments", module.getMaxAssignments())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
if (++i < types.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
/**
* builds a json string for the feedback for the execution "proceeding".
*
* @param id
* of the Action feedback refers to
* @return the built string
* @throws JSONException
*/
public static String deploymentActionInProgressFeedback(final String id) throws JSONException {
return deploymentActionFeedback(id, "proceeding");
}
/**
* builds a certain json string for a action feedback.
*
* @param id
* of the action the feedback refers to
* @param execution
* see ExecutionStatus
* @return the build json string
* @throws JSONException
*/
public static String deploymentActionFeedback(final String id, final String execution) throws JSONException {
return deploymentActionFeedback(id, execution, "none");
}
public static String deploymentActionFeedback(final String id, final String execution, final String finished)
throws JSONException {
final List<String> messages = new ArrayList<String>();
messages.add(RandomStringUtils.randomAscii(1000));
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result",
new JSONObject().put("finished", finished).put("progress",
new JSONObject().put("cnt", 2).put("of", 5)))
.put("details", messages))
.toString();
}
/**
* @param types
* @return
*/
public static String distributionSetTypes(final List<DistributionSetType> types) {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final DistributionSetType module : types) {
try {
final JSONArray osmTypes = new JSONArray();
module.getOptionalModuleTypes().forEach(smt -> osmTypes.put(new JSONObject().put("id", smt.getId())));
final JSONArray msmTypes = new JSONArray();
module.getMandatoryModuleTypes().forEach(smt -> msmTypes.put(new JSONObject().put("id", smt.getId())));
builder.append(new JSONObject().put("name", module.getName())
.put("description", module.getDescription()).put("id", Long.MAX_VALUE)
.put("key", module.getKey()).put("createdAt", "0").put("updatedAt", "0")
.put("createdBy", "fghdfkjghdfkjh").put("optionalmodules", osmTypes)
.put("mandatorymodules", msmTypes).put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
if (++i < types.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
public static String distributionSets(final List<DistributionSet> sets) throws JSONException {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final DistributionSet set : sets) {
try {
builder.append(distributionSet(set));
} catch (final Exception e) {
e.printStackTrace();
}
if (++i < sets.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
public static String distributionSet(final DistributionSet set) throws JSONException {
final List<JSONObject> modules = set.getModules().stream().map(module -> {
try {
return new JSONObject().put("id", module.getId());
} catch (final Exception e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList());
return new JSONObject().put("name", set.getName()).put("description", set.getDescription())
.put("type", set.getType().getKey()).put("id", Long.MAX_VALUE).put("version", set.getVersion())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").put("requiredMigrationStep", set.isRequiredMigrationStep())
.put("modules", modules).toString();
}
/**
* @param targets
* @return
*/
public static String targets(final List<Target> targets) {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final Target target : targets) {
try {
builder.append(new JSONObject().put("controllerId", target.getControllerId())
.put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0")
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.toString());
} catch (final Exception e) {
e.printStackTrace();
}
if (++i < targets.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
public static String cancelActionFeedback(final String id, final String execution) throws JSONException {
final List<String> messages = new ArrayList<String>();
messages.add(RandomStringUtils.randomAscii(1000));
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success")).put("details", messages))
.toString();
}
public static String configData(final String id, final Map<String, String> attributes, final String execution)
throws JSONException {
return new JSONObject().put("id", id).put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success"))
.put("details", new ArrayList<String>()))
.put("data", attributes).toString();
}
}

View File

@@ -0,0 +1,252 @@
/**
* 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.rest.resource;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import org.eclipse.hawkbit.repository.FieldNameProvider;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.rsql.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.rsql.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@RunWith(MockitoJUnitRunner.class)
@Features("Component Tests - Management RESTful API")
@Stories("RSQL search utility")
// TODO: fully document tests -> @Description for long text and reasonable
// method name as short text
public class RSQLUtilityTest {
@Mock
private Root<Object> baseSoftwareModuleRootMock;
@Mock
private CriteriaQuery<SoftwareModule> criteriaQueryMock;
@Mock
private CriteriaBuilder criteriaBuilderMock;
@Mock
private EntityManager entityManager;
@Mock
private Metamodel metamodel;
@Mock
private ManagedType managedType;
@Mock
private Attribute attribute;
@Test(expected = RSQLParameterSyntaxException.class)
public void wrongRsqlSyntaxThrowSyntaxException() {
final String wrongRSQL = "name==abc;d";
when(entityManager.getMetamodel()).thenReturn(metamodel);
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class, entityManager).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
}
@Test(expected = RSQLParameterUnsupportedFieldException.class)
public void wrongFieldThrowUnsupportedFieldException() {
final String wrongRSQL = "unknownField==abc";
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
doEntitySetup(SoftwareModule.class);
RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class, entityManager).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
}
@Test
public <T> void correctRsqlBuildsPredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name==abc;version==1.2";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.get("version")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(
mock(Predicate.class));
doEntitySetup(SoftwareModule.class);
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, entityManager).toPredicate(
baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
}
@Test
public void correctRsqlBuildsNotEqualPredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name!=abc";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(
mock(Predicate.class));
doEntitySetup(SoftwareModule.class);
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, entityManager).toPredicate(
baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).notEqual(eq(baseSoftwareModuleRootMock), eq("abc"));
}
@Test
public void correctRsqlBuildsLessThanPredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name=lt=abc";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(
mock(Predicate.class));
doEntitySetup(SoftwareModule.class);
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, entityManager).toPredicate(
baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).lessThan(eq(pathOfString(baseSoftwareModuleRootMock)), eq("abc"));
}
@Test
public void correctRsqlBuildsLikePredicate() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "name=li=abc";
when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(
mock(Predicate.class));
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock)))).thenReturn(
pathOfString(baseSoftwareModuleRootMock));
doEntitySetup(SoftwareModule.class);
// test
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, entityManager).toPredicate(
baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)),
eq("abc".toUpperCase()));
}
@Test
public void correctRsqlWithEnumValue() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "testfield==bumlux";
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) TestValueEnum.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
doEntitySetup(TestValueEnum.class);
// test
RSQLUtility.parse(correctRsql, TestFieldEnum.class, entityManager).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
// verfication
verify(criteriaBuilderMock, times(1)).and(any(Predicate.class));
verify(criteriaBuilderMock, times(1)).equal(eq(baseSoftwareModuleRootMock), eq(TestValueEnum.BUMLUX));
}
@Test
public void wrongRsqlWithWrongEnumValue() {
reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
final String correctRsql = "testfield==unknownValue";
when(baseSoftwareModuleRootMock.get("testfield")).thenReturn(baseSoftwareModuleRootMock);
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) TestValueEnum.class);
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
doEntitySetup(TestValueEnum.class);
try {
// test
RSQLUtility.parse(correctRsql, TestFieldEnum.class, entityManager).toPredicate(baseSoftwareModuleRootMock,
criteriaQueryMock, criteriaBuilderMock);
fail("missing RSQLParameterUnsupportedFieldException for wrong enum value");
} catch (final RSQLParameterUnsupportedFieldException e) {
// nope expected
}
}
private void doEntitySetup(final Class clasName) {
when(entityManager.getMetamodel()).thenReturn(metamodel);
when(metamodel.managedType(clasName)).thenReturn(managedType);
when(managedType.getJavaType()).thenReturn(clasName);
doAnswer(new Answer<Attribute>() {
@Override
public Attribute answer(final InvocationOnMock invocation) throws Throwable {
return attribute;
}
}).when(managedType).getAttribute(anyString());
when(attribute.isAssociation()).thenReturn(false);
}
@SuppressWarnings("unchecked")
private <Y> Path<Y> pathOfString(final Path<?> path) {
return (Path<Y>) path;
}
private enum TestFieldEnum implements FieldNameProvider {
TESTFIELD;
/*
* (non-Javadoc)
*
* @see
* org.eclipse.hawkbit.server.rest.resource.model.FieldNameProvider#
* getFieldName()
*/
@Override
public String getFieldName() {
return "testfield";
}
}
private enum TestValueEnum {
BUMLUX;
}
}

View File

@@ -0,0 +1,47 @@
/**
* 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.rest.resource;
import java.io.IOException;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.resource.model.PagedList;
import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Utility additions for the REST API tests.
*
*
*
*
*/
public final class ResourceUtility {
private static final ObjectMapper mapper = new ObjectMapper();
static ExceptionInfo convertException(final String jsonExceptionResponse)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(jsonExceptionResponse, ExceptionInfo.class);
}
static ArtifactRest convertArtifactResponse(final String jsonResponse)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(jsonResponse, ArtifactRest.class);
}
static <T> PagedList<T> mapResponse(final Class<T> clazz, final String responseBody)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(responseBody, PagedList.class);
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MvcResult;
/**
* Tests {@link SoftwareModuleResource} in case of missing MongoDB connection.
*
*
*
*
*/
public class SMRessourceMisingMongoDbConnectionTest extends AbstractIntegrationTest {
@BeforeClass
public static void initialize() {
// set property to mongoPort which does not start any mongoDB of
// parallel test execution
System.setProperty("spring.data.mongodb.port", "1020");
}
@Test
public void testMissingMongoDbConnection() throws Exception {
assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
SoftwareModule sm = new SoftwareModule(softwareManagement.findSoftwareModuleTypeByKey("os"), "name 1",
"version 1", null, null);
sm = softwareManagement.createSoftwareModule(sm);
assertThat(artifactRepository.findAll()).hasSize(0);
// create test file
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random);
// upload
final MvcResult mvcResult = mvc
.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isInternalServerError()).andReturn();
// check error result
final ExceptionInfo exceptionInfo = ResourceUtility
.convertException(mvcResult.getResponse().getContentAsString());
assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getKey());
assertThat(exceptionInfo.getMessage()).isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED.getMessage());
// ensure that the JPA transaction was rolled back
assertThat(artifactRepository.findAll()).hasSize(0);
}
}

View File

@@ -0,0 +1,320 @@
/**
* 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.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import com.jayway.jsonpath.JsonPath;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Test for {@link SoftwareModuleTypeResource}.
*
*
*
*
*/
@Features("Component Tests - Management RESTful API")
@Stories("Software Module Type Resource")
public class SoftwareModuleTypeResourceTest extends AbstractIntegrationTest {
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes GET requests.")
public void getSoftwareModuleTypes() throws Exception {
SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
testType.setDescription("Desc1234");
testType = softwareManagement.updateSoftwareModuleType(testType);
mvc.perform(get("/rest/v1/softwaremoduletypes").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content.[?(@.key==" + osType.getKey() + ")][0].name", equalTo(osType.getName())))
.andExpect(jsonPath("$content.[?(@.key==" + osType.getKey() + ")][0].description",
equalTo(osType.getDescription())))
.andExpect(jsonPath("$content.[?(@.key==" + osType.getKey() + ")][0].maxAssignments", equalTo(1)))
.andExpect(jsonPath("$content.[?(@.key==" + osType.getKey() + ")][0].key", equalTo("os")))
.andExpect(jsonPath("$content.[?(@.key==" + runtimeType.getKey() + ")][0].name",
equalTo(runtimeType.getName())))
.andExpect(jsonPath("$content.[?(@.key==" + runtimeType.getKey() + ")][0].description",
equalTo(runtimeType.getDescription())))
.andExpect(jsonPath("$content.[?(@.key==" + runtimeType.getKey() + ")][0].maxAssignments", equalTo(1)))
.andExpect(jsonPath("$content.[?(@.key==" + runtimeType.getKey() + ")][0].key", equalTo("runtime")))
.andExpect(
jsonPath("$content.[?(@.key==" + appType.getKey() + ")][0].name", equalTo(appType.getName())))
.andExpect(jsonPath("$content.[?(@.key==" + appType.getKey() + ")][0].description",
equalTo(appType.getDescription())))
.andExpect(jsonPath("$content.[?(@.key==" + appType.getKey() + ")][0].maxAssignments", equalTo(1)))
.andExpect(jsonPath("$content.[?(@.key==" + appType.getKey() + ")][0].key", equalTo("application")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].name", equalTo("TestName123")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].description", equalTo("Desc1234")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].lastModifiedBy", equalTo("uploadTester")))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].lastModifiedAt",
equalTo(testType.getLastModifiedAt())))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].maxAssignments", equalTo(5)))
.andExpect(jsonPath("$content.[?(@.key==test123)][0].key", equalTo("test123")))
.andExpect(jsonPath("$total", equalTo(4)));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes POST requests.")
public void createSoftwareModuleTypes() throws JSONException, Exception {
final List<SoftwareModuleType> types = new ArrayList<>();
types.add(new SoftwareModuleType("test1", "TestName1", "Desc1", 1));
types.add(new SoftwareModuleType("test2", "TestName2", "Desc2", 2));
types.add(new SoftwareModuleType("test3", "TestName3", "Desc3", 3));
final MvcResult mvcResult = mvc
.perform(post("/rest/v1/softwaremoduletypes/").content(JsonBuilder.softwareModuleTypes(types))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("[0].name", equalTo("TestName1"))).andExpect(jsonPath("[0].key", equalTo("test1")))
.andExpect(jsonPath("[0].description", equalTo("Desc1")))
.andExpect(jsonPath("[0].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[0].maxAssignments", equalTo(1)))
.andExpect(jsonPath("[1].name", equalTo("TestName2"))).andExpect(jsonPath("[1].key", equalTo("test2")))
.andExpect(jsonPath("[1].description", equalTo("Desc2")))
.andExpect(jsonPath("[1].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[1].maxAssignments", equalTo(2)))
.andExpect(jsonPath("[2].name", equalTo("TestName3"))).andExpect(jsonPath("[2].key", equalTo("test3")))
.andExpect(jsonPath("[2].description", equalTo("Desc3")))
.andExpect(jsonPath("[2].createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("[2].createdAt", not(equalTo(0))))
.andExpect(jsonPath("[2].maxAssignments", equalTo(3))).andReturn();
final SoftwareModuleType created1 = softwareManagement.findSoftwareModuleTypeByKey("test1");
final SoftwareModuleType created2 = softwareManagement.findSoftwareModuleTypeByKey("test2");
final SoftwareModuleType created3 = softwareManagement.findSoftwareModuleTypeByKey("test3");
assertThat(
JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/softwaremoduletypes/" + created1.getId());
assertThat(
JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/softwaremoduletypes/" + created2.getId());
assertThat(
JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString())
.isEqualTo("http://localhost/rest/v1/softwaremoduletypes/" + created3.getId());
assertThat(softwareManagement.countSoftwareModuleTypesAll()).isEqualTo(6);
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes/{ID} GET requests.")
public void getSoftwareModuleType() throws Exception {
SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
testType.setDescription("Desc1234");
testType = softwareManagement.updateSoftwareModuleType(testType);
mvc.perform(get("/rest/v1/softwaremoduletypes/{smtId}", testType.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$name", equalTo("TestName123")))
.andExpect(jsonPath("$description", equalTo("Desc1234")))
.andExpect(jsonPath("$maxAssignments", equalTo(5)))
.andExpect(jsonPath("$createdBy", equalTo("uploadTester")))
.andExpect(jsonPath("$createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$lastModifiedBy", equalTo("uploadTester")))
.andExpect(jsonPath("$lastModifiedAt", equalTo(testType.getLastModifiedAt())));
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes/{ID} DELETE requests (hard delete scenario).")
public void deleteSoftwareModuleTypeUnused() throws Exception {
final SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
assertThat(softwareManagement.countSoftwareModuleTypesAll()).isEqualTo(4);
mvc.perform(delete("/rest/v1/softwaremoduletypes/{smId}", testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
assertThat(softwareManagement.countSoftwareModuleTypesAll()).isEqualTo(3);
}
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes/{ID} DELETE requests (soft delete scenario).")
public void deleteSoftwareModuleTypeUsed() throws Exception {
final SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
softwareManagement
.createSoftwareModule(new SoftwareModule(testType, "name", "version", "description", "vendor"));
assertThat(softwareManagement.countSoftwareModuleTypesAll()).isEqualTo(4);
assertThat(softwareModuleTypeRepository.count()).isEqualTo(4);
mvc.perform(delete("/rest/v1/softwaremoduletypes/{smId}", testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
assertThat(softwareModuleTypeRepository.count()).isEqualTo(4);
assertThat(softwareManagement.countSoftwareModuleTypesAll()).isEqualTo(3);
}
@Test
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes/{ID} PUT requests.")
public void updateSoftwareModuleTypeOnlyDescriptionAndNameUntouched() throws Exception {
final SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
final String body = new JSONObject().put("id", testType.getId()).put("description", "foobardesc")
.put("name", "nameShouldNotBeChanged").toString();
mvc.perform(put("/rest/v1/softwaremoduletypes/{smId}", testType.getId()).content(body)
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$description", equalTo("foobardesc")))
.andExpect(jsonPath("$name", equalTo("TestName123"))).andReturn();
}
@Test
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes GET requests with paging.")
public void getSoftwareModuleTypesWithoutAddtionalRequestParameters() throws Exception {
final int types = 3;
mvc.perform(get(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(types)));
}
@Test
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes GET requests with paging.")
public void getSoftwareModuleTypesWithPagingLimitRequestParameter() throws Exception {
final int types = 3;
final int limitSize = 1;
mvc.perform(get(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)
.param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize)));
}
@Test
@Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes GET requests with paging.")
public void getSoftwareModuleTypesWithPagingLimitAndOffsetRequestParameter() throws Exception {
final int types = 3;
final int offsetParam = 2;
final int expectedSize = types - offsetParam;
mvc.perform(get(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING)
.param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam))
.param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(types)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize)))
.andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize)));
}
@Test
@Description("Ensures that the server is behaving as expected on invalid requests (wrong media type, wrong ID etc.).")
public void invalidRequestsOnSoftwaremoduleTypesResource() throws Exception {
final SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<SoftwareModuleType> types = new ArrayList<>();
types.add(testType);
// SM does not exist
mvc.perform(get("/rest/v1/softwaremoduletypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
mvc.perform(delete("/rest/v1/softwaremoduletypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// bad request - no content
mvc.perform(post("/rest/v1/softwaremoduletypes").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
// bad request - bad content
mvc.perform(post("/rest/v1/softwaremoduletypes").content("sdfjsdlkjfskdjf".getBytes())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
// unsupported media type
mvc.perform(post("/rest/v1/softwaremoduletypes").content(JsonBuilder.softwareModuleTypes(types))
.contentType(MediaType.APPLICATION_OCTET_STREAM)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isUnsupportedMediaType());
// not allowed methods
mvc.perform(put("/rest/v1/softwaremoduletypes")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete("/rest/v1/softwaremoduletypes")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
}
@Test
@Description("Search erquest of software module types.")
public void searchSoftwareModuleTypeRsql() throws Exception {
final SoftwareModuleType testType = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5));
final SoftwareModuleType testType2 = softwareManagement
.createSoftwareModuleType(new SoftwareModuleType("test1234", "TestName1234", "Desc123", 5));
final String rsqlFindLikeDs1OrDs2 = "name==TestName123,name==TestName1234";
mvc.perform(get("/rest/v1/softwaremoduletypes?q=" + rsqlFindLikeDs1OrDs2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(2)))
.andExpect(jsonPath("total", equalTo(2))).andExpect(jsonPath("content[0].name", equalTo("TestName123")))
.andExpect(jsonPath("content[1].name", equalTo("TestName1234")));
}
private void createSoftwareModulesAlphabetical(final int amount) {
char character = 'a';
for (int index = 0; index < amount; index++) {
final String str = String.valueOf(character);
final SoftwareModule softwareModule = new SoftwareModule(osType, str, str, str, str);
softwareManagement.createSoftwareModule(softwareModule);
character++;
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* 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.rest.resource;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.eclipse.hawkbit.repository.TargetFields;
import org.junit.Test;
import org.springframework.data.domain.Sort.Order;
/**
*
*/
public class SortUtilityTest {
private static final String SORT_PARAM_1 = "NAME:ASC";
private static final String SORT_PARAM_2 = "NAME:ASC, DESCRIPTION:DESC";
private static final String SYNTAX_FAILURE_SORT_PARAM = "NAME:ASC DESCRIPTION:DESC";
private static final String WRONG_DIRECTION_PARAM = "NAME:ASDF";
private static final String CASE_INSENSITIVE_DIRECTION_PARAM = "NaME:ASC";
private static final String CASE_INSENSITIVE_DIRECTION_PARAM_1 = "name:ASC";
private static final String WRONG_FIELD_PARAM = "ASDF:ASC";
@Test
public void parseSortParam1() {
final List<Order> parse = SortUtility.parse(TargetFields.class, SORT_PARAM_1);
assertEquals(1, parse.size());
}
@Test
public void parseSortParam2() {
final List<Order> parse = SortUtility.parse(TargetFields.class, SORT_PARAM_2);
assertEquals(2, parse.size());
}
@Test(expected = SortParameterSyntaxErrorException.class)
public void parseWrongSyntaxParam() {
SortUtility.parse(TargetFields.class, SYNTAX_FAILURE_SORT_PARAM);
}
@Test
public void parsingIsNotCaseSensitive() {
SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM);
SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM_1);
}
@Test(expected = SortParameterUnsupportedDirectionException.class)
public void parseWrongDirectionParam() {
SortUtility.parse(TargetFields.class, WRONG_DIRECTION_PARAM);
}
@Test(expected = SortParameterUnsupportedFieldException.class)
public void parseWrongFieldParam() {
SortUtility.parse(TargetFields.class, WRONG_FIELD_PARAM);
}
}

View File

@@ -0,0 +1,79 @@
/**
* 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.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.junit.Test;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
*
*
*/
@Features("Component Tests - System Management RESTful API")
@Stories("System Management Resource")
public class SystemManagementResourceTest extends AbstractIntegrationTest {
@Test
@WithUser(tenantId = "mytenant", allSpPermissions = true)
@Description("Tests that a tenant can be deletd by API.")
public void deleteTenant() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
assertThat(distributionSetManagement.findDistributionSetById(dsA.getId())).isNotNull();
mvc.perform(delete("/system/admin/tenants/mytenant")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
assertThat(distributionSetManagement.findDistributionSetById(dsA.getId())).isNull();
}
@Test
@WithUser(tenantId = "mytenant", authorities = { SpPermission.DELETE_TARGET, SpPermission.DELETE_REPOSITORY,
SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY })
@Description("Tenant deletion is only possible for SYSTEM_ADMINs. Repository or target delete is not sufficient.")
public void deleteTenantFailsMissingPermission() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
assertThat(distributionSetManagement.findDistributionSetById(dsA.getId())).isNotNull();
mvc.perform(delete("/system/admin/tenants/mytenant")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
assertThat(distributionSetManagement.findDistributionSetById(dsA.getId())).isNotNull();
}
@Test
public void getCachesReturnStatus200() throws Exception {
mvc.perform(get("/system/admin/caches")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
@Test
public void invalidateCachesReturnStatus200() throws Exception {
mvc.perform(delete("/system/admin/caches")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,41 @@
/**
* 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.rest.resource.model;
import static org.fest.assertions.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class ExceptionInfoTest {
@Test
public void setterAndGetter() {
final String knownExceptionClass = "hawkbit.test.exception.Class";
final String knownErrorCode = "hawkbit.error.code.Known";
final String knownMessage = "a known message";
final List<String> knownParameters = new ArrayList<>();
knownParameters.add("param1");
knownParameters.add("param2");
final ExceptionInfo underTest = new ExceptionInfo();
underTest.setErrorCode(knownErrorCode);
underTest.setExceptionClass(knownExceptionClass);
underTest.setMessage(knownMessage);
underTest.setParameters(knownParameters);
assertThat(underTest.getErrorCode()).isEqualTo(knownErrorCode);
assertThat(underTest.getExceptionClass()).isEqualTo(knownExceptionClass);
assertThat(underTest.getMessage()).isEqualTo(knownMessage);
assertThat(underTest.getParameters()).isEqualTo(knownParameters);
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.rest.resource.model;
import static org.fest.assertions.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class PagedListTest {
@Test(expected = NullPointerException.class)
public void createListWithNullContentThrowsException() {
new PagedList<>(null, 0);
}
@Test
public void createListWithContent() {
final long knownTotal = 2;
final List<String> knownContentList = new ArrayList<>();
knownContentList.add("content1");
knownContentList.add("content2");
final PagedList<String> pagedList = new PagedList<>(knownContentList, knownTotal);
assertThat(pagedList.getTotal()).isEqualTo(knownTotal);
assertThat(pagedList.getSize()).isEqualTo(knownContentList.size());
}
@Test
public void createListWithSmallerTotalThanContentSizeIsOk() {
final long knownTotal = 0;
final List<String> knownContentList = new ArrayList<>();
knownContentList.add("content1");
knownContentList.add("content2");
final PagedList<String> pagedList = new PagedList<>(knownContentList, knownTotal);
assertThat(pagedList.getTotal()).isEqualTo(knownTotal);
assertThat(pagedList.getSize()).isEqualTo(knownContentList.size());
}
}

View File

@@ -0,0 +1,63 @@
#
# 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
#
# used if IM profile is disabled
security.ignored=true
# IM required for integration tests
spring.profiles.active=im,suiteembedded,artifactrepository,redis
server.port=12222
spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value}
spring.data.mongodb.port=28017
# supported: H2, MYSQL
hawkbit.server.database=H2
hawkbit.server.database.env=TEST
spring.main.show_banner=false
hawkbit.server.controller.security.authentication.header=true
hawkbit.server.artifact.repo.upload.maxFileSize=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
spring.jpa.database=${hawkbit.server.database}
#spring.jpa.show-sql=true
flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
# effective DB setting
spring.datasource.url=${${hawkbit.server.database}.spring.datasource.url}
spring.datasource.driverClassName=${${hawkbit.server.database}.spring.datasource.driverClassName}
spring.datasource.username=${${hawkbit.server.database}.spring.datasource.username}
spring.datasource.password=${${hawkbit.server.database}.spring.datasource.password}
# H2
##;AUTOCOMMIT=ON
H2.spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE
#H2.spring.datasource.url=jdbc:h2:./db/sp-db;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;MVCC=TRUE
H2.spring.datasource.driverClassName=org.h2.Driver
H2.spring.datasource.username=sa
H2.spring.datasource.password=sa
# MYSQL
MYSQL.spring.datasource.url=jdbc:mysql://localhost:3306/sp_test
MYSQL.spring.datasource.driverClassName=org.mariadb.jdbc.Driver
MYSQL.spring.datasource.username=root
MYSQL.spring.datasource.password=
# SP Controller configuration
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00