Move DDI resources and annotation to module hawkbit-ddi-resource

- created module and moved resources

Signed-off-by: Jonathan Philip Knoblauch <JonathanPhilip.Knoblauch@bosch-si.com>
This commit is contained in:
Jonathan Philip Knoblauch
2016-04-18 12:39:28 +02:00
parent 22f4d9ffcf
commit 3145709824
17 changed files with 182 additions and 15 deletions

View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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.ddi.api.ArtifactStoreControllerDdiApi;
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.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
/**
* The {@link ArtifactStoreController} of the Rollouts 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.
*
* TODO
*/
@RestController
public class ArtifactStoreController implements ArtifactStoreControllerDdiApi {
private static final Logger LOG = LoggerFactory.getLogger(ArtifactStoreController.class);
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private CacheWriteNotify cacheWriteNotify;
@Autowired
private HawkbitSecurityProperties securityProperties;
@Override
public ResponseEntity<Void> downloadArtifactByFilename(final String fileName, final HttpServletResponse response,
final HttpServletRequest request, final String targetid) {
ResponseEntity<Void> result;
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;
}
@Override
public ResponseEntity<Void> downloadArtifactMD5ByFilename(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);
}
private Action checkAndReportDownloadByTarget(final HttpServletRequest request, final String targetid,
final LocalArtifact artifact) {
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader()));
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;
}
}

View File

@@ -0,0 +1,157 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol;
import org.eclipse.hawkbit.ddi.ControllerConstants;
import org.eclipse.hawkbit.ddi.model.Artifact;
import org.eclipse.hawkbit.ddi.model.Chunk;
import org.eclipse.hawkbit.ddi.model.Config;
import org.eclipse.hawkbit.ddi.model.ControllerBase;
import org.eclipse.hawkbit.ddi.model.Polling;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
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.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import com.google.common.base.Charsets;
/**
* Utility class for the Controller API.
*/
public final class DataConversionHelper {
@Autowired
ArtifactUrlHandler artifactUrlHandler;
// utility class, private constructor.
private DataConversionHelper() {
}
static List<Chunk> createChunks(final String targetid, final Action uAction,
final ArtifactUrlHandler artifactUrlHandler) {
return uAction.getDistributionSet().getModules().stream()
.map(module -> new Chunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
module.getName(), createArtifacts(targetid, module, artifactUrlHandler)))
.collect(Collectors.toList());
}
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
* @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 ArtifactUrlHandler artifactUrlHandler) {
final List<Artifact> files = new ArrayList<>();
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());
final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP);
final String linkHttps = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTPS);
file.add(new Link(linkHttps).withRel("download"));
file.add(new Link(linkHttps + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum"));
file.add(new Link(linkHttp).withRel("download-http"));
file.add(new Link(linkHttp + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http"));
files.add(file);
});
return files;
}
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,30 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.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 Direct Device API.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface EnableDirectDeviceApi {
}

View File

@@ -0,0 +1,466 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.ddi.api.RootControllerDdiApi;
import org.eclipse.hawkbit.ddi.model.ActionFeedback;
import org.eclipse.hawkbit.ddi.model.Cancel;
import org.eclipse.hawkbit.ddi.model.CancelActionToStop;
import org.eclipse.hawkbit.ddi.model.Chunk;
import org.eclipse.hawkbit.ddi.model.ConfigData;
import org.eclipse.hawkbit.ddi.model.ControllerBase;
import org.eclipse.hawkbit.ddi.model.Deployment;
import org.eclipse.hawkbit.ddi.model.Deployment.HandlingType;
import org.eclipse.hawkbit.ddi.model.DeploymentBase;
import org.eclipse.hawkbit.ddi.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.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.security.HawkbitSecurityProperties;
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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* The {@link RootController} of the hawkBit server controller API that is
* queried by the hawkBit controller in order to pull {@link Action}s that have
* to be fulfilled and report status updates concerning the {@link Action}
* processing.
*
* Transactional (read-write) as all queries at least update the last poll time.
*
*/
@RestController
public class RootController implements RootControllerDdiApi {
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 ({}).";
@Autowired
private ControllerManagement controllerManagement;
@Autowired
private SoftwareManagement softwareManagement;
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private CacheWriteNotify cacheWriteNotify;
@Autowired
private HawkbitSecurityProperties securityProperties;
@Autowired
private TenantAware tenantAware;
@Autowired
private ArtifactUrlHandler artifactUrlHandler;
@Override
public ResponseEntity<List<org.eclipse.hawkbit.ddi.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, artifactUrlHandler),
HttpStatus.OK);
}
@Override
public ResponseEntity<ControllerBase> getControllerBase(@PathVariable final String targetid,
final HttpServletRequest request) {
LOG.debug("getControllerBase({})", targetid);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid,
IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader()));
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, securityProperties.getClients().getRemoteIpHeader()));
}
return new ResponseEntity<>(
DataConversionHelper.fromTarget(target, controllerManagement.findActionByTargetAndActive(target),
controllerManagement.findPollingTime(), tenantAware),
HttpStatus.OK);
}
@Override
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;
final Target target = controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader()));
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 static boolean checkModule(final String fileName, final SoftwareModule module) {
return null == module || !module.getLocalArtifactByFilename(fileName).isPresent();
}
@Override
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, securityProperties.getClients().getRemoteIpHeader()));
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);
}
@Override
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, securityProperties.getClients().getRemoteIpHeader()));
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, artifactUrlHandler);
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<>(base, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@Override
public ResponseEntity<Void> 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, securityProperties.getClients().getRemoteIpHeader()));
if (!actionId.equals(feedback.getId())) {
LOG.warn(
"provideBasedeploymentActionFeedback: action in payload ({}) was not identical to action in path ({}).",
feedback.getId(), actionId);
return new ResponseEntity<>(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<>(HttpStatus.GONE);
}
controllerManagement.addUpdateActionStatus(
generateUpdateStatus(feedback, targetid, feedback.getId(), action), action);
return new ResponseEntity<>(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:
handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus);
break;
default:
handleDefaultUpdateStatus(feedback, targetid, actionid, actionStatus);
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 static void handleDefaultUpdateStatus(final ActionFeedback feedback, final String targetid,
final Long actionid, final ActionStatus actionStatus) {
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage("Controller reported: " + feedback.getStatus().getExecution());
}
private static void handleClosedUpdateStatus(final ActionFeedback feedback, final String targetid,
final Long actionid, final ActionStatus actionStatus) {
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!");
}
}
@Override
public ResponseEntity<Void> putConfigData(@Valid @RequestBody final ConfigData configData,
@PathVariable final String targetid, final HttpServletRequest request) {
controllerManagement.updateLastTargetQuery(targetid,
IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader()));
controllerManagement.updateControllerAttributes(targetid, configData.getData());
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
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, securityProperties.getClients().getRemoteIpHeader()));
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, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@Override
public ResponseEntity<Void> 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, securityProperties.getClients().getRemoteIpHeader()));
if (!actionId.equals(feedback.getId())) {
LOG.warn(
"provideBasedeploymentActionFeedback: action in payload ({}) was not identical to action in path ({}).",
feedback.getId(), actionId);
return new ResponseEntity<>(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<>(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:
handleClosedCancelStatus(feedback, actionStatus);
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 static void handleClosedCancelStatus(final ActionFeedback feedback, final ActionStatus actionStatus) {
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR);
} else {
actionStatus.setStatus(Status.CANCELED);
}
}
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,573 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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 - Direct Device Integration API")
@Stories("Artifact Download Resource")
public class ArtifactDownloadTest extends AbstractIntegrationTestWithMongoDB {
public ArtifactDownloadTest() {
LOG = LoggerFactory.getLogger(ArtifactDownloadTest.class);
}
private volatile 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("The same file that was uploaded is expected when downloaded",
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("The same file that was uploaded is expected when downloaded",
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,552 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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 - Direct Device Integration 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
.findActionWithDetails(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
.findActionWithDetails(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
.findActionWithDetails(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.findActionWithDetails(
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.findActionWithDetails(
deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }).getActions().get(0));
final Action updateAction2 = deploymentManagement.findActionWithDetails(
deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }).getActions().get(0));
final Action updateAction3 = deploymentManagement.findActionWithDetails(
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.findActionWithDetails(
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.ddi.resource;
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 - Direct Device Integration 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,990 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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 - Direct Device Integration API")
@Stories("Deployment Action Resource")
public class DeploymentBaseTest extends AbstractIntegrationTestWithMongoDB {
@Test()
@Description("Ensures that artifacts are not found, when softare module does not exists.")
public void artifactsNotFound() 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 artifacts are found, when software module exists.")
public void artifactsExists() 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("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()).getAssignedEntity();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedEntity();
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();
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("https://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("https://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.download-http.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-http.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("https://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("https://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download-http.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-http.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())
.getAssignedEntity();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedEntity();
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();
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("https://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("https://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("https://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("https://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download-http.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-http.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()).getAssignedEntity();
assertThat(deploymentManagement.findActiveActionsByTarget(savedTarget)).hasSize(1);
final Action action = deploymentManagement.findActiveActionsByTarget(savedTarget).get(0);
assertThat(actionRepository.findAll()).hasSize(1);
saved = deploymentManagement.assignDistributionSet(ds2, saved).getAssignedEntity();
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();
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("https://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("https://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[0]._links.download-http.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-http.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("https://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("https://localhost/" + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(jsonPath("$deployment.chunks[?(@.part==os)][0].artifacts[1]._links.download-http.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-http.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.findActionWithDetails(
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.findActionWithDetails(
deploymentManagement.assignDistributionSet(ds1.getId(), new String[] { "4712" }).getActions().get(0));
final Action action2 = deploymentManagement.findActionWithDetails(
deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }).getActions().get(0));
final Action action3 = deploymentManagement.findActionWithDetails(
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();
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());
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();
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());
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();
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());
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();
mvc.perform(post("/{tenant}/controller/v1/4712/deploymentBase/" + action.getId() + "/feedback",
tenantAware.getCurrentTenant())
.content(JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed", "failure",
"error message"))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
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();
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());
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).getAssignedEntity().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,294 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ddi.resource;
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.AbstractIntegrationTestWithMongoDB;
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.tenancy.configuration.TenantConfigurationKey;
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 - Direct Device Integration API")
@Stories("Root Poll Resource")
public class RootControllerTest extends AbstractIntegrationTestWithMongoDB {
@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
@Description("Ensures that target poll request does not change audit data on the entity.")
@WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET }, allSpPermissions = false)
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), () -> {
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
@Description("Ensures that server returns a not found response in case of empty controlloer ID.")
public void rootRsWithoutId() throws Exception {
mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test
@Description("Ensures that the system creates a new target in plug and play manner, i.e. target is authenticated but does not exist yet.")
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
@Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.")
@WithUser(principal = "knownpricipal", allSpPermissions = false)
public void pollWithModifiedGloablPollingTime() throws Exception {
securityRule.runAs(
WithSpringAuthorityRule.withUser("tenantadmin", SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION),
() -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
"00:02:00");
return null;
});
securityRule.runAs(
WithSpringAuthorityRule.withUser("controller", SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS), () -> {
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:02:00")));
return null;
});
}
@Test
@Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.")
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
@Description("Ensures that the target state machine of a precomissioned target switches from "
+ "UNKNOWN to REGISTERED when the target polls for the first time.")
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")));
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
@Description("Ensures that the source IP address of the polling target is correctly stored in repository")
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).getAssignedEntity().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());
}
}