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

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