From ecfe774e53d5b5b5ae66f765b97ddbf03b89be0a Mon Sep 17 00:00:00 2001 From: Jeroen Laverman Date: Thu, 14 Jun 2018 12:48:31 +0200 Subject: [PATCH] Target attributes update can be (re-)triggered by management API (#690) * extend Management API with attribute `requestAttributes` * introduce new DMF Event Topic `REQUEST_ATTRIBUTES_UPDATE` * enhance REST documentation by new functionality * add tests for: * Management API * Amqp Message Dispatcher Service * Repository (Target Management) Signed-off-by: Jeroen Jan Laverman (INST/ESY3) --- .../amqp/AmqpMessageDispatcherService.java | 23 +++++++- .../AmqpMessageDispatcherServiceTest.java | 24 +++++++- .../hawkbit/dmf/amqp/api/EventTopic.java | 6 +- .../hawkbit/repository/TargetManagement.java | 24 +++++++- .../repository/builder/TargetUpdate.java | 6 ++ .../TargetAttributesRequestedEvent.java | 58 +++++++++++++++++++ .../builder/AbstractTargetUpdateCreate.java | 7 +++ .../repository/jpa/JpaTargetManagement.java | 21 +++++++ .../repository/jpa/TargetRepository.java | 6 +- .../repository/jpa/TargetManagementTest.java | 12 ++++ .../mgmt/json/model/target/MgmtTarget.java | 12 ++++ .../model/target/MgmtTargetRequestBody.java | 10 ++++ .../mgmt/rest/resource/MgmtTargetMapper.java | 1 + .../rest/resource/MgmtTargetResource.java | 11 +++- .../rest/resource/MgmtTargetResourceTest.java | 51 ++++++++++++++++ .../AbstractApiRestDocumentation.java | 2 + .../documentation/MgmtApiModelProperties.java | 2 + .../TargetResourceDocumentationTest.java | 16 +++-- 18 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAttributesRequestedEvent.java diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index 1f41b4bbe..3cbb824df 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -157,8 +158,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { * window available, the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is * returned else {@link EventTopic#DOWNLOAD} is returned. * - * @param target - * for which to find the event type + * @param maintenanceWindowAvailable + * valid maintenance window or not. * * @return {@link EventTopic} to use for message. */ @@ -199,6 +200,12 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { sendDeleteMessage(deleteEvent.getTenant(), deleteEvent.getControllerId(), deleteEvent.getTargetAddress()); } + @EventListener(classes = TargetAttributesRequestedEvent.class) + protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedEvent updateAttributesEvent) { + sendUpdateAttributesMessageToTarget(updateAttributesEvent.getTenant(), updateAttributesEvent.getControllerId(), + updateAttributesEvent.getTargetAddress()); + } + protected void sendUpdateMessageToTarget(final String tenant, final Target target, final Long actionId, final Map> modules, final boolean maintenanceWindowAvailable) { @@ -270,6 +277,18 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { } + protected void sendUpdateAttributesMessageToTarget(final String tenant, final String controllerId, + final String targetAddress) { + if (!hasValidAddress(targetAddress)) { + return; + } + + final Message message = new Message(null, + createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.REQUEST_ATTRIBUTES_UPDATE)); + + amqpSenderService.sendMessage(message, URI.create(targetAddress)); + } + private static MessageProperties createConnectorMessagePropertiesEvent(final String tenant, final String controllerId, final EventTopic topic) { final MessageProperties messageProperties = createConnectorMessageProperties(tenant, controllerId); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index 2c5c41ef0..099005c34 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -38,6 +38,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfMetadata; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration; @@ -213,6 +214,19 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { }); } } + + @Test + @Description("Verifies that sending update controller attributes event works.") + public void sendUpdateAttributesRequest() { + final String amqpUri = "amqp://anyhost"; + final TargetAttributesRequestedEvent targetAttributesRequestedEvent = new TargetAttributesRequestedEvent(TENANT, + 1L, CONTROLLER_ID, amqpUri, Target.class.getName(), serviceMatcher.getServiceId()); + + amqpMessageDispatcherService.targetTriggerUpdateAttributes(targetAttributesRequestedEvent); + + final Message sendMessage = createArgumentCapture(URI.create(amqpUri)); + assertUpdateAttributesMessage(sendMessage); + } @Test @Description("Verifies that send cancel event works") @@ -299,7 +313,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(sendMessage, DmfDownloadAndUpdateRequest.class); assertEquals(downloadAndUpdateRequest.getActionId(), action); - assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL, + assertEquals("The topic of the event should contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC)); assertEquals("Security token of target", TEST_TOKEN, downloadAndUpdateRequest.getTargetSecurityToken()); @@ -307,6 +321,14 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { } + private void assertUpdateAttributesMessage(final Message sendMessage) { + assertEventMessage(sendMessage); + + assertEquals("The topic of the event should contain REQUEST_ATTRIBUTES_UPDATE", + EventTopic.REQUEST_ATTRIBUTES_UPDATE, + sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC)); + } + private void assertEventMessage(final Message sendMessage) { assertNotNull("The message should not be null", sendMessage); diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java index 0facaa3a1..29bb5bd75 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/EventTopic.java @@ -32,6 +32,10 @@ public enum EventTopic { /** * Topic when sending a download only task, skipping the install. */ - DOWNLOAD; + DOWNLOAD, + /** + * Topic when an update of device attributes is requested. + */ + REQUEST_ATTRIBUTES_UPDATE; } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 4be0071d9..070854990 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -109,8 +109,7 @@ public interface TargetManagement { Long installedOrAssignedDistributionSetId, Boolean selectTargetWithNoTag, String... tagNames); /** - * Counts number of targets with given - * {@link Target#getInstalledDistributionSet()}. + * Counts number of targets with given with given distribution set Id * * @param distId * to search for @@ -630,4 +629,25 @@ public interface TargetManagement { * if target with given ID does not exist */ Map getControllerAttributes(@NotEmpty String controllerId); + + /** + * Trigger given {@link Target} to update its attributes. + * + * @param controllerId + * of the target + * + * @throws EntityNotFoundException + * if target with given ID does not exist + */ + void requestControllerAttributes(@NotEmpty String controllerId); + + /** + * Check if update of given {@link Target} attributes is already requested. + * + * @param controllerId + * of target + * @return {@code true}: update of controller attributes triggered. + * {@code false}: update of controller attributes not requested. + */ + boolean isControllerAttributesRequested(@NotEmpty String controllerId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java index f5db14d97..76154b2ca 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java @@ -67,4 +67,10 @@ public interface TargetUpdate { * @return updated builder instance */ TargetUpdate status(@NotNull TargetUpdateStatus status); + + /** + * @param requestAttributes for {@link Target#isRequestControllerAttributes()} + * @return updated builder instance + */ + TargetUpdate requestAttributes(Boolean requestAttributes); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAttributesRequestedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAttributesRequestedEvent.java new file mode 100644 index 000000000..c580151d2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetAttributesRequestedEvent.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2018 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.repository.event.remote; + +import org.eclipse.hawkbit.repository.model.Target; + +/** + * Defines the remote event of triggering attribute updates of a {@link Target}. + */ +public class TargetAttributesRequestedEvent extends RemoteIdEvent { + private static final long serialVersionUID = 1L; + private String controllerId; + private String targetAddress; + + /** + * Default constructor. + */ + public TargetAttributesRequestedEvent() { + // for serialization libs like jackson + } + + /** + * Constructor json serialization + * + * @param tenant + * the tenant + * @param entityId + * the entity id + * @param controllerId + * the controllerId of the target + * @param targetAddress + * the target address + * @param entityClass + * the entity class + * @param applicationId + * the origin application id + */ + public TargetAttributesRequestedEvent(final String tenant, final Long entityId, final String controllerId, + final String targetAddress, final String entityClass, final String applicationId) { + super(entityId, tenant, entityClass, applicationId); + this.controllerId = controllerId; + this.targetAddress = targetAddress; + } + + public String getControllerId() { + return controllerId; + } + + public String getTargetAddress() { + return targetAddress; + } +} diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java index 6bd6b6040..405402339 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java @@ -34,6 +34,8 @@ public class AbstractTargetUpdateCreate extends AbstractNamedEntityBuilder protected Long lastTargetQuery; protected TargetUpdateStatus status; + protected Boolean requestAttributes; + protected AbstractTargetUpdateCreate(final String controllerId) { this.controllerId = StringUtils.trimWhitespace(controllerId); } @@ -62,6 +64,11 @@ public class AbstractTargetUpdateCreate extends AbstractNamedEntityBuilder return (T) this; } + public T requestAttributes(final Boolean requestAttributes) { + this.requestAttributes = requestAttributes; + return (T) this; + } + public T lastTargetQuery(final Long lastTargetQuery) { this.lastTargetQuery = lastTargetQuery; return (T) this; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index d9e4c0cab..218457dab 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.TimestampCalculator; import org.eclipse.hawkbit.repository.builder.TargetCreate; import org.eclipse.hawkbit.repository.builder.TargetUpdate; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate; @@ -640,4 +641,24 @@ public class JpaTargetManagement implements TargetManagement { return target.getControllerAttributes(); } + @Override + public void requestControllerAttributes(final String controllerId) { + final JpaTarget target = (JpaTarget) getByControllerID(controllerId) + .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); + + target.setRequestControllerAttributes(true); + + eventPublisher.publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(), + target.getControllerId(), target.getAddress() != null ? target.getAddress().toString() : null, + JpaTarget.class.getName(), applicationContext.getId())); + } + + @Override + public boolean isControllerAttributesRequested(final String controllerId) { + final JpaTarget target = (JpaTarget) getByControllerID(controllerId) + .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); + + return target.isRequestControllerAttributes(); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java index 655d8ba33..23a0a8877 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java @@ -178,8 +178,7 @@ public interface TargetRepository extends BaseEntityRepository, Page findByAssignedDistributionSetId(final Pageable pageable, final Long setID); /** - * Counts number of targets with given - * {@link Target#getAssignedDistributionSet()}. + * Counts number of targets with given distribution set Id. * * @param distId * to search for @@ -189,8 +188,7 @@ public interface TargetRepository extends BaseEntityRepository, Long countByAssignedDistributionSetId(final Long distId); /** - * Counts number of targets with given - * {@link Target#getInstalledDistributionSet()}. + * Counts number of targets with given distribution set Id. * * @param distId * to search for diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index ac9840ce7..f13b0f39b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -880,4 +880,16 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { final List collect = foundDs.stream().map(Target::getId).collect(Collectors.toList()); assertThat(collect).containsAll(searchIds); } + + @Test + @Description("Verify that the flag for requesting controller attributes is set correctly.") + public void verifyRequestControllerAttributes() { + final String knownTargetId = "KnownControllerId"; + createTargetWithAttributes(knownTargetId); + + assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isFalse(); + targetManagement.requestControllerAttributes(knownTargetId); + assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isTrue(); + + } } diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java index 40dd553a5..7553cb947 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java @@ -46,6 +46,9 @@ public class MgmtTarget extends MgmtNamedEntity { @JsonProperty private String securityToken; + @JsonProperty + private boolean requestAttributes; + /** * @return the controllerId */ @@ -167,4 +170,13 @@ public class MgmtTarget extends MgmtNamedEntity { public void setSecurityToken(final String securityToken) { this.securityToken = securityToken; } + + public boolean isRequestAttributes() { + return requestAttributes; + } + + @JsonIgnore + public void setRequestAttributes(final boolean requestAttributes) { + this.requestAttributes = requestAttributes; + } } diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java index f7a297be1..e526ad44e 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java @@ -24,6 +24,9 @@ public class MgmtTargetRequestBody { @JsonProperty private String securityToken; + @JsonProperty + private Boolean requestAttributes; + public String getSecurityToken() { return securityToken; } @@ -88,4 +91,11 @@ public class MgmtTargetRequestBody { this.address = address; } + public Boolean isRequestAttributes() { + return requestAttributes; + } + + public void setRequestAttributes(final Boolean requestAttributes) { + this.requestAttributes = requestAttributes; + } } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java index 69a1ff41d..6c1ed2337 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java @@ -133,6 +133,7 @@ public final class MgmtTargetMapper { targetRest.setLastModifiedAt(target.getLastModifiedAt()); targetRest.setSecurityToken(target.getSecurityToken()); + targetRest.setRequestAttributes(target.isRequestControllerAttributes()); // last target query is the last controller request date final Long lastTargetQuery = target.getLastTargetQuery(); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index bcc2e3d60..85055a0a5 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -120,9 +120,18 @@ public class MgmtTargetResource implements MgmtTargetRestApi { public ResponseEntity updateTarget(@PathVariable("controllerId") final String controllerId, @RequestBody final MgmtTargetRequestBody targetRest) { + if (targetRest.isRequestAttributes() != null) { + if (targetRest.isRequestAttributes()) { + targetManagement.requestControllerAttributes(controllerId); + } else { + return ResponseEntity.badRequest().build(); + } + } + final Target updateTarget = this.targetManagement.update(entityFactory.target().update(controllerId) .name(targetRest.getName()).description(targetRest.getDescription()).address(targetRest.getAddress()) - .securityToken(targetRest.getSecurityToken())); + .securityToken(targetRest.getSecurityToken()).requestAttributes(targetRest.isRequestAttributes())); + final MgmtTarget response = MgmtTargetMapper.toResponse(updateTarget); MgmtTargetMapper.addPollStatus(updateTarget, response); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 701826673..a3bff21c5 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -68,6 +68,7 @@ import com.jayway.jsonpath.JsonPath; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Stories; /** @@ -1539,6 +1540,56 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest .andDo(MockMvcResultPrinter.print()).andExpect(status().is(HttpStatus.NO_CONTENT.value())); } + @Test + @Description("Request update of Controller Attributes.") + public void triggerControllerAttributesUpdate() throws Exception { + // create target with attributes + final String knownTargetId = "targetIdNeedsUpdate"; + final Map knownControllerAttrs = new HashMap<>(); + knownControllerAttrs.put("a", "1"); + knownControllerAttrs.put("b", "2"); + testdataFactory.createTarget(knownTargetId); + controllerManagement.updateControllerAttributes(knownTargetId, knownControllerAttrs, null); + assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isFalse(); + + verifyAttributeUpdateCanBeRequested(knownTargetId); + + verifyRequestAttributesAttributeIsOptional(knownTargetId); + + verifyResettingRequestAttributesIsNotAllowed(knownTargetId); + } + + @Step + private void verifyAttributeUpdateCanBeRequested(final String knownTargetId) throws Exception { + final String body = new JSONObject().put("requestAttributes", true).toString(); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId).content(body) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + + assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isTrue(); + } + + @Step + private void verifyRequestAttributesAttributeIsOptional(final String knownTargetId) throws Exception { + final String body = new JSONObject().put("description", "verify attribute can be missing").toString(); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId).content(body) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + } + + @Step + private void verifyResettingRequestAttributesIsNotAllowed(final String knownTargetId) throws Exception { + final String body = new JSONObject().put("requestAttributes", false).toString(); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId).content(body) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()); + + assertThat(targetManagement.isControllerAttributesRequested(knownTargetId)).isTrue(); + } + @Test public void searchTargetsUsingRsqlQuery() throws Exception { final int amountTargets = 10; diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java index 460353084..324b8cb98 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/AbstractApiRestDocumentation.java @@ -219,6 +219,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati .type("enum") .attributes(key("value").value("['error', 'in_sync', 'pending', 'registered', 'unknown']")), fieldWithPath(fieldArrayPrefix + "securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN), + fieldWithPath(fieldArrayPrefix + "requestAttributes") + .description(MgmtApiModelProperties.REQUEST_ATTRIBUTES), fieldWithPath(fieldArrayPrefix + "installedAt").description(MgmtApiModelProperties.INSTALLED_AT), fieldWithPath(fieldArrayPrefix + "lastModifiedAt") .description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"), diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java index 82dbac86b..e1059b3a9 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java @@ -143,6 +143,8 @@ public final class MgmtApiModelProperties { public static final String SECURITY_TOKEN = "Pre-Shared key that allows targets to authenticate at Direct Device Integration API if enabled in the tenant settings."; + public static final String REQUEST_ATTRIBUTES = "Request re-transmission of target attributes."; + public static final String META_DATA = "List of metadata."; public static final String META_DATA_KEY = "Metadata property key."; diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java index a65d5ca15..5be76d624 100644 --- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java +++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetResourceDocumentationTest.java @@ -90,6 +90,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .type("enum").attributes( key("value").value("['error', 'in_sync', 'pending', 'registered', 'unknown']")), fieldWithPath("content[].securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN), + fieldWithPath("content[].requestAttributes").description(MgmtApiModelProperties.REQUEST_ATTRIBUTES), fieldWithPath("content[].installedAt").description(MgmtApiModelProperties.INSTALLED_AT), fieldWithPath("content[].lastModifiedAt") .description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"), @@ -141,6 +142,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .attributes(key("value") .value("['error', 'in_sync', 'pending', 'registered', 'unknown']")), fieldWithPath("[]securityToken").description(MgmtApiModelProperties.SECURITY_TOKEN), + fieldWithPath("[]requestAttributes").description(MgmtApiModelProperties.REQUEST_ATTRIBUTES), fieldWithPath("[]_links.self").ignored()))); } @@ -180,12 +182,13 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio .andDo(this.document.document( pathParameters( parameterWithName("controllerId").description(ApiModelPropertiesGeneric.ITEM_ID)), - requestFields(requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME), - requestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION), - requestFieldWithPath("controllerId").description(ApiModelPropertiesGeneric.ITEM_ID), - requestFieldWithPath("address").description(MgmtApiModelProperties.ADDRESS), - requestFieldWithPath("securityToken") - .description(MgmtApiModelProperties.SECURITY_TOKEN)), + requestFields(optionalRequestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME), + optionalRequestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION), + optionalRequestFieldWithPath("controllerId").description(ApiModelPropertiesGeneric.ITEM_ID), + optionalRequestFieldWithPath("address").description(MgmtApiModelProperties.ADDRESS), + optionalRequestFieldWithPath("securityToken") + .description(MgmtApiModelProperties.SECURITY_TOKEN), + optionalRequestFieldWithPath("requestAttributes").description(MgmtApiModelProperties.REQUEST_ATTRIBUTES)), getResponseFieldTarget(false))); } @@ -570,6 +573,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio target.put("name", name); target.put("address", "https://192.168.0.1"); target.put("securityToken", "2345678DGGDGFTDzztgf"); + target.put("requestAttributes", true); return this.objectMapper.writeValueAsString(target); }