Prioritisation of assignments via mgmt-API (#895)

* Updating the schema for targetfilterquery and rollout
* Updating the weight validation logic and tests
* Make weight optional
* Fix existing multi assignment tests by adding weight, remove weight from TargetFilterQuery
* Add weight validation tests, fix tests
* Add mgmt api tests for assignment and getting action with weight
* Add management layer validation and tests for creating rollouts with weight
* Fix amqp test, add repo level validation to resource tests
* Add weight to rollout mgmt-api and tests
* Add weight to mgmt api target Filter create and update
* Add target filter auto assign weight. disable enforcement of setting a weight in multiassign mode
* Remove ignored tests, fix api doc
* Fix minor findings
* Fix findings
* Remove hardcoded min weight
* Add docu text, fix findings
* Fix api documentation
* Expose weight via DMF
* Expose actions according to weight via ddi
* Fix documentation
* Add method to get actions ordered by weight to deploymentManagement
* Updating the schema for targetfilterquery and rollout
* Updated the indentation
* Updated the helper class, fixed the randomUID in test factory
* Updated the class name with prefix JPA
* Adding the missing License for WeightValidationHelper class
* Adding documentation to the dmf api on weight
* Removed the merger markers
* Updated the class name
* Removed the redundant method
* Addressed final PR comments
* Updated the missing testcase with latest default weight value
* Reverting the default value of weight back to 1000 and updated tests

Signed-off-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>
This commit is contained in:
Stefan Klotz
2019-11-08 10:47:35 +01:00
committed by Stefan Behl
parent 09f2d8a481
commit 9cb5d31396
98 changed files with 2425 additions and 875 deletions

View File

@@ -365,6 +365,8 @@ type=EVENT <br /> tenant=tenant123 <br /> thingId=abc <br /> topic=DOWNLOAD\_A
If `multi.assignments.enabled` is enabled, this message is sent instead of DOWNLOAD_AND_INSTALL, DOWNLOAD, or CANCEL_DOWNLOAD If `multi.assignments.enabled` is enabled, this message is sent instead of DOWNLOAD_AND_INSTALL, DOWNLOAD, or CANCEL_DOWNLOAD
by hawkBit to initialize update, download, or cancel task(s). by hawkBit to initialize update, download, or cancel task(s).
With weight, one can set the priority to the action. The higher the weight, the higher is the priority of an action.
Header | Description | Type | Mandatory Header | Description | Type | Mandatory
------- | -------------------------------- | ----------------------------------- | ------------------------------------------------------------- ------- | -------------------------------- | ----------------------------------- | -------------------------------------------------------------
type | Type of the message | Fixed string "EVENT" | true type | Type of the message | Fixed string "EVENT" | true
@@ -381,6 +383,7 @@ Payload Template (the Java representation is [DmfMultiActionRequest](https://git
```json ```json
[{ [{
"topic": "String", "topic": "String",
"weight": long,
"action": { "action": {
"actionId": long, "actionId": long,
"targetSecurityToken": "String", "targetSecurityToken": "String",
@@ -413,6 +416,7 @@ Payload Template (the Java representation is [DmfMultiActionRequest](https://git
}, },
{ {
"topic": "String", "topic": "String",
"weight": long,
"action": { "action": {
"actionId": long, "actionId": long,
"targetSecurityToken": "String", "targetSecurityToken": "String",
@@ -454,6 +458,7 @@ type=EVENT <br /> tenant=tenant123 <br /> thingId=abc <br /> topic=MULTI\_ACTI
```json ```json
[{ [{
"topic": "DOWNLOAD_AND_INSTALL", "topic": "DOWNLOAD_AND_INSTALL",
"weight": 600,
"action": { "action": {
"actionId":137, "actionId":137,
"targetSecurityToken":"bH7XXAprK1ChnLfKSdtlsp7NOlPnZAYY", "targetSecurityToken":"bH7XXAprK1ChnLfKSdtlsp7NOlPnZAYY",
@@ -486,6 +491,7 @@ type=EVENT <br /> tenant=tenant123 <br /> thingId=abc <br /> topic=MULTI\_ACTI
}, },
{ {
"topic": "DOWNLOAD", "topic": "DOWNLOAD",
"weight": 500,
"action": { "action": {
"actionId":138, "actionId":138,
"targetSecurityToken":"bH7XXAprK1ChnLfKSdtlsp7NOlPnZAYY", "targetSecurityToken":"bH7XXAprK1ChnLfKSdtlsp7NOlPnZAYY",

View File

@@ -52,6 +52,10 @@ The cascading execution of the deployment groups is based on two thresholds that
One of the main paradigms of Eclipse hawkBit is, that a Distribution Set represents the currently installed software of a device. Hence, a device can have only one Distribution Set assigned/installed at a time. With _Multi-Assignments_ enabled, this paradigm shifts. Multi-Assignments allows to assign multiple Distribution Sets to a device simultaneously, without cancelling each other. As a consequence, an operator can trigger multiple campaigns addressing the same devices in parallel. One of the main paradigms of Eclipse hawkBit is, that a Distribution Set represents the currently installed software of a device. Hence, a device can have only one Distribution Set assigned/installed at a time. With _Multi-Assignments_ enabled, this paradigm shifts. Multi-Assignments allows to assign multiple Distribution Sets to a device simultaneously, without cancelling each other. As a consequence, an operator can trigger multiple campaigns addressing the same devices in parallel.
### Action weight
To differentiate between important and less important updates a property called _weight_ is used. When multi-assignments is enabled every action has a weight value between (and including) 0 and 1000. The higher the weight the more important is the assignment represented by the action. Also when defining a _rollout_ or an _auto-assignment_ and multi-assignments is enabled a weight value has to be provided. This value is passed to the actions created during the execution of these _rollouts_ and _auto-assignments_. If no weight was provided the highest value of 1000 is used instead.
### Consequences ### Consequences
While this feature provides more flexibility to the user and enables new use-cases, there are also some consequences one should be aware of: While this feature provides more flexibility to the user and enables new use-cases, there are also some consequences one should be aware of:
@@ -60,11 +64,10 @@ While this feature provides more flexibility to the user and enables new use-cas
* This feature is in beta and may change unannounced. * This feature is in beta and may change unannounced.
* For now, this feature is **opt-in only**. Once activated, it cannot be deactivated. * For now, this feature is **opt-in only**. Once activated, it cannot be deactivated.
* Currently, there is no mechanism to hint devices to process the actions in a certain order.
**Minor** **Minor**
* While on DMF-API a MULTI_ACTION request is sent, DDI-API only exposes the oldest open action. * While on DMF-API a MULTI_ACTION request is sent, DDI-API only exposes the next action which has the highest priority in the list of open actions(according to their weight property).
* All information regarding the currently assigned or installed Distribution Set does only respect the last assignment, as well as the last successfully installed Distribution set. This also affects: * All information regarding the currently assigned or installed Distribution Set does only respect the last assignment, as well as the last successfully installed Distribution set. This also affects:
* Pinning a target or Distribution Set in Deployment View. * Pinning a target or Distribution Set in Deployment View.
* Statistics about installed or assigned Distribution Sets. * Statistics about installed or assigned Distribution Sets.

View File

@@ -256,7 +256,13 @@ public enum SpServerError {
* *
*/ */
SP_MULTIASSIGNMENT_NOT_ENABLED("hawkbit.server.error.multiassignmentNotEnabled", SP_MULTIASSIGNMENT_NOT_ENABLED("hawkbit.server.error.multiassignmentNotEnabled",
"The requested operation requires Multiassignments to be enabled."); "The requested operation requires multi assignments to be enabled."),
/**
*
*/
SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE("hawkbit.server.error.noWeightProvidedInMultiAssignmentMode",
"The requested operation requires a weight to be specified when multi assignments is enabled.");
private final String key; private final String key;
private final String message; private final String message;

View File

@@ -21,7 +21,11 @@ public enum ActionFields implements FieldNameProvider, FieldValueConverter<Actio
/** /**
* The id field. * The id field.
*/ */
ID("id"); ID("id"),
/**
* The weight field.
*/
WEIGHT("weight");
private static final String ACTIVE = "pending"; private static final String ACTIVE = "pending";
private static final String INACTIVE = "finished"; private static final String INACTIVE = "finished";

View File

@@ -176,8 +176,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> { .filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> {
final List<Action> activeActions = deploymentManagement final List<Action> activeActions = deploymentManagement
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId()) .findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
.getContent();
activeActions.forEach(action -> action.getDistributionSet().getModules().forEach( activeActions.forEach(action -> action.getDistributionSet().getModules().forEach(
module -> softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata))); module -> softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata)));
@@ -204,7 +203,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
actions.forEach(action -> { actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, action, final DmfActionRequest actionRequest = createDmfActionRequest(target, action,
getSoftwareModuleMetaData.apply(action)); getSoftwareModuleMetaData.apply(action));
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest); final int weight = deploymentManagement.getWeightConsideringDefault(action);
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
}); });
final Message message = getMessageConverter().toMessage(multiActionRequest, final Message message = getMessageConverter().toMessage(multiActionRequest,

View File

@@ -54,7 +54,6 @@ import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -233,8 +232,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
LOG.debug("Target {} reported online state.", thingId); LOG.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target); sendUpdateCommandToTarget(target);
} catch (final EntityAlreadyExistsException e) { } catch (final EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException( throw new AmqpRejectAndDontRequeueException("Tried to register previously registered target, message will be ignored!", e);
"Tried to register previously registered target, message will be ignored!", e);
} }
} }
@@ -254,7 +252,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private void sendCurrentActionsAsMultiActionToTarget(final Target target) { private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
final List<Action> actions = controllerManagement final List<Action> actions = controllerManagement
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId()).getContent(); .findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet) final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@@ -267,7 +265,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private void sendOldestActionToTarget(final Target target) { private void sendOldestActionToTarget(final Target target) {
final Optional<Action> actionOptional = controllerManagement final Optional<Action> actionOptional = controllerManagement
.findOldestActiveActionByTarget(target.getControllerId()); .findActiveActionWithHighestWeight(target.getControllerId());
if (!actionOptional.isPresent()) { if (!actionOptional.isPresent()) {
return; return;

View File

@@ -181,17 +181,19 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.") @Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.")
public void createThing() { public void createThing() {
final String knownThingId = "1"; final String knownThingId = "1";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
final Target targetMock = mock(Target.class); final Target targetMock = mock(Target.class);
targetIdCaptor = ArgumentCaptor.forClass(String.class); final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
uriCaptor = ArgumentCaptor.forClass(URI.class); final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(), when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
uriCaptor.capture())).thenReturn(targetMock); uriCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty()); when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
amqpMessageHandlerService.onMessage(createMessage(new byte[0], getThingCreatedMessageProperties(knownThingId)), amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
// verify // verify
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId); assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
@@ -214,7 +216,7 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(), when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock); uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty()); when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
amqpMessageHandlerService.onMessage( amqpMessageHandlerService.onMessage(
createMessage(targetProperties, getThingCreatedMessageProperties(knownThingId)), createMessage(targetProperties, getThingCreatedMessageProperties(knownThingId)),
@@ -488,7 +490,8 @@ public class AmqpMessageHandlerServiceTest {
when(create.status(any())).thenReturn(create); when(create.status(any())).thenReturn(create);
when(entityFactoryMock.actionStatus()).thenReturn(builder); when(entityFactoryMock.actionStatus()).thenReturn(builder);
// for the test the same action can be used // for the test the same action can be used
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.of(action)); when(controllerManagementMock.findActiveActionWithHighestWeight(any()))
.thenReturn(Optional.of(action));
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());

View File

@@ -188,16 +188,17 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient() final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient()
.getMessageConverter().fromMessage(replyMessage); .getMessageConverter().fromMessage(replyMessage);
Assert.assertThat(dsModules, assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId);
SoftwareModuleJsonMatcher.containsExactly(downloadAndUpdateRequest.getSoftwareModules())); }
downloadAndUpdateRequest.getSoftwareModules() protected void assertDmfDownloadAndUpdateRequest(final DmfDownloadAndUpdateRequest request,
final Set<SoftwareModule> softwareModules, final String controllerId) {
Assert.assertThat(softwareModules, SoftwareModuleJsonMatcher.containsExactly(request.getSoftwareModules()));
request.getSoftwareModules()
.forEach(dmfModule -> assertThat(dmfModule.getMetadata()).containsExactly( .forEach(dmfModule -> assertThat(dmfModule.getMetadata()).containsExactly(
new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE))); new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE)));
final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(controllerId)); final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(controllerId));
assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken());
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
} }
protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> softwareModules, protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> softwareModules,

View File

@@ -15,6 +15,7 @@ import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ON
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -26,11 +27,13 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus; import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest; import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest.DmfMultiActionElement; import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest.DmfMultiActionElement;
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule; import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent; import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
@@ -49,8 +52,10 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -100,7 +105,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "sendDownloadStatusBeforeWindowStartTime"; final String controllerId = TARGET_PREFIX + "sendDownloadStatusBeforeWindowStartTime";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet); testdataFactory.addSoftwareModuleMetadata(distributionSet);
assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2), assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2),
getTestDuration(10), getTestTimeZone()); getTestDuration(10), getTestTimeZone());
@@ -122,7 +127,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow"; final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet); testdataFactory.addSoftwareModuleMetadata(distributionSet);
assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5), assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5),
getTestDuration(10), getTestTimeZone()); getTestDuration(10), getTestTimeZone());
@@ -145,7 +150,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "assignDistributionSetMultipleTimes"; final String controllerId = TARGET_PREFIX + "assignDistributionSetMultipleTimes";
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId); final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet distributionSet2 = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet2); testdataFactory.addSoftwareModuleMetadata(distributionSet2);
assignDistributionSet(distributionSet2.getId(), controllerId); assignDistributionSet(distributionSet2.getId(), controllerId);
assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId); assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId);
@@ -171,17 +176,74 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "assignMultipleDsInMultiAssignMode"; final String controllerId = TARGET_PREFIX + "assignMultipleDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final Long actionId1 = assignNewDsToTarget(controllerId); final Long actionId1 = assignNewDsToTarget(controllerId, 450);
final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL); final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install)); assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install));
final Long actionId2 = assignNewDsToTarget(controllerId); final Long actionId2 = assignNewDsToTarget(controllerId, 111);
final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL); final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install)); assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
} }
@Test
@Description("Verify payload of multi action messages.")
public void assertMultiActionMessagePayloads() {
final int expectedWeightIfNotSet = 1000;
final int weight1 = 600;
final String controllerId = UUID.randomUUID().toString();
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(ds);
final Long installActionId = makeAssignment(DeploymentManagement.deploymentRequest(controllerId, ds.getId())
.setActionType(ActionType.FORCED).build()).getAssignedEntity().get(0).getId();
enableMultiAssignments();
final Long downloadActionId = makeAssignment(DeploymentManagement.deploymentRequest(controllerId, ds.getId())
.setActionType(ActionType.DOWNLOAD_ONLY).setWeight(weight1).build()).getAssignedEntity().get(0).getId();
final Long cancelActionId = makeAssignment(
DeploymentManagement.deploymentRequest(controllerId, ds.getId()).setWeight(DEFAULT_TEST_WEIGHT).build())
.getAssignedEntity().get(0).getId();
// make sure the latest message in the queue is the one triggered by the
// cancellation
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL, EventTopic.MULTI_ACTION,
EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(cancelActionId);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final List<DmfMultiActionElement> multiActionMessages = getLatestMultiActionMessages(controllerId);
assertThat(multiActionMessages).hasSize(3);
final DmfMultiActionElement installMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL)).findFirst().get();
final DmfMultiActionElement downloadMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.DOWNLOAD)).findFirst().get();
final DmfMultiActionElement cancelMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.CANCEL_DOWNLOAD)).findFirst().get();
assertThat(installMessage.getWeight()).isEqualTo(expectedWeightIfNotSet);
assertThat(downloadMessage.getWeight()).isEqualTo(weight1);
assertThat(cancelMessage.getWeight()).isEqualTo(DEFAULT_TEST_WEIGHT);
assertThat(installMessage.getAction()).isExactlyInstanceOf(DmfDownloadAndUpdateRequest.class)
.hasFieldOrPropertyWithValue("actionId", installActionId);
assertThat(downloadMessage.getAction()).isExactlyInstanceOf(DmfDownloadAndUpdateRequest.class)
.hasFieldOrPropertyWithValue("actionId", downloadActionId);
assertThat(cancelMessage.getAction()).isExactlyInstanceOf(DmfActionRequest.class)
.hasFieldOrPropertyWithValue("actionId", cancelActionId);
assertDmfDownloadAndUpdateRequest((DmfDownloadAndUpdateRequest) installMessage.getAction(), ds.getModules(),
controllerId);
assertDmfDownloadAndUpdateRequest((DmfDownloadAndUpdateRequest) downloadMessage.getAction(), ds.getModules(),
controllerId);
}
private List<DmfMultiActionElement> getLatestMultiActionMessages(final String expectedControllerId) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
.isEqualTo(expectedControllerId);
return ((DmfMultiActionRequest) getDmfClient().getMessageConverter().fromMessage(multiactionMessage))
.getElements();
}
@Test @Test
@Description("Handle cancelation process of an action in multi assignment mode.") @Description("Handle cancelation process of an action in multi assignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -197,8 +259,8 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "cancelActionInMultiAssignMode"; final String controllerId = TARGET_PREFIX + "cancelActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId); final long actionId1 = assignNewDsToTarget(controllerId, 675);
final long actionId2 = assignNewDsToTarget(controllerId); final long actionId2 = assignNewDsToTarget(controllerId, 343);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(actionId1); deploymentManagement.cancelAction(actionId1);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
@@ -229,8 +291,8 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "finishActionInMultiAssignMode"; final String controllerId = TARGET_PREFIX + "finishActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId); final long actionId1 = assignNewDsToTarget(controllerId, 66);
final long actionId2 = assignNewDsToTarget(controllerId); final long actionId2 = assignNewDsToTarget(controllerId, 767);
final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL); final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
@@ -254,11 +316,11 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
enableMultiAssignments(); enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignDsMultipleTimesInMultiAssignMode"; final String controllerId = TARGET_PREFIX + "assignDsMultipleTimesInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final Long dsId = testdataFactory.createDistributionSet().getId();
final Long actionId1 = getFirstAssignedActionId(assignDistributionSet(ds.getId(), controllerId)); final Long actionId1 = getFirstAssignedAction(assignDistributionSet(dsId, controllerId, 344)).getId();
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Long actionId2 = getFirstAssignedActionId(assignDistributionSet(ds.getId(), controllerId)); final Long actionId2 = getFirstAssignedAction(assignDistributionSet(dsId, controllerId, 775)).getId();
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL); final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
@@ -272,8 +334,14 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
} }
private Long assignNewDsToTarget(final String controllerId) { private Long assignNewDsToTarget(final String controllerId) {
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); return assignNewDsToTarget(controllerId, null);
final Long actionId = getFirstAssignedActionId(assignDistributionSet(ds.getId(), controllerId)); }
private Long assignNewDsToTarget(final String controllerId, final Integer weight) {
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long actionId = getFirstAssignedActionId(
assignDistributionSet(ds.getId(), Collections.singletonList(controllerId), ActionType.FORCED,
RepositoryModelConstants.NO_FORCE_TIME, weight));
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
return actionId; return actionId;
} }
@@ -296,15 +364,15 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "startRolloutsWithSameDsInMultiAssignMode"; final String controllerId = TARGET_PREFIX + "startRolloutsWithSameDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet ds = testdataFactory.createDistributionSet();
final Set<Long> smIds = getSoftwareModuleIds(ds); final Set<Long> smIds = getSoftwareModuleIds(ds);
final String filterQuery = "controllerId==" + controllerId; final String filterQuery = "controllerId==" + controllerId;
createAndStartRollout(ds, filterQuery); createAndStartRollout(ds, filterQuery, 122);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds)); assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds));
createAndStartRollout(ds, filterQuery); createAndStartRollout(ds, filterQuery, 43);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds, smIds)); assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds, smIds));
} }
@@ -327,15 +395,15 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final String filterQuery = "controllerId==" + controllerId; final String filterQuery = "controllerId==" + controllerId;
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet ds1 = testdataFactory.createDistributionSet();
final Set<Long> smIds1 = getSoftwareModuleIds(ds1); final Set<Long> smIds1 = getSoftwareModuleIds(ds1);
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet ds2 = testdataFactory.createDistributionSet();
final Set<Long> smIds2 = getSoftwareModuleIds(ds2); final Set<Long> smIds2 = getSoftwareModuleIds(ds2);
createAndStartRollout(ds1, filterQuery); createAndStartRollout(ds1, filterQuery, 12);
createAndStartRollout(ds2, filterQuery); createAndStartRollout(ds2, filterQuery, 45);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
createAndStartRollout(ds1, filterQuery); createAndStartRollout(ds1, filterQuery, 65);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1)); assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1));
@@ -357,8 +425,12 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
} }
private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery) { private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery) {
return createAndStartRollout(ds, filterQuery, null);
}
private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery, final Integer weight) {
final Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1, final Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1,
filterQuery, ds, "50", "5"); filterQuery, ds, "50", "5", ActionType.FORCED, weight);
rolloutManagement.start(rollout.getId()); rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
return rollout; return rollout;
@@ -483,11 +555,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
} }
private List<Entry<Long, EventTopic>> getLatestMultiActionMessageActions(final String expectedControllerId) { private List<Entry<Long, EventTopic>> getLatestMultiActionMessageActions(final String expectedControllerId) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION); final List<DmfMultiActionElement> multiActionRequest = getLatestMultiActionMessages(expectedControllerId);
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
.isEqualTo(expectedControllerId);
final List<DmfMultiActionElement> multiActionRequest = ((DmfMultiActionRequest) getDmfClient()
.getMessageConverter().fromMessage(multiactionMessage)).getElements();
return multiActionRequest.stream() return multiActionRequest.stream()
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic())) .map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
.collect(Collectors.toList()); .collect(Collectors.toList());

View File

@@ -52,10 +52,11 @@ public class DmfMultiActionRequest {
elements.add(element); elements.add(element);
} }
public void addElement(final EventTopic topic, final DmfActionRequest action) { public void addElement(final EventTopic topic, final DmfActionRequest action, final int weight) {
final DmfMultiActionElement element = new DmfMultiActionElement(); final DmfMultiActionElement element = new DmfMultiActionElement();
element.setTopic(topic); element.setTopic(topic);
element.setAction(action); element.setAction(action);
element.setWeight(weight);
addElement(element); addElement(element);
} }
@@ -70,6 +71,9 @@ public class DmfMultiActionRequest {
@JsonProperty @JsonProperty
private DmfActionRequest action; private DmfActionRequest action;
@JsonProperty
private int weight;
public DmfActionRequest getAction() { public DmfActionRequest getAction() {
return action; return action;
} }
@@ -89,6 +93,13 @@ public class DmfMultiActionRequest {
this.topic = actionType; this.topic = actionType;
} }
public void setWeight(final int weight) {
this.weight = weight;
}
public int getWeight() {
return weight;
}
} }
} }

View File

@@ -46,8 +46,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface ControllerManagement { public interface ControllerManagement {
/** /**
* Adds an {@link ActionStatus} for a cancel {@link Action} including potential * Adds an {@link ActionStatus} for a cancel {@link Action} including
* state changes for the target and the {@link Action} itself. * potential state changes for the target and the {@link Action} itself.
* *
* @param create * @param create
* to be added * to be added
@@ -93,8 +93,8 @@ public interface ControllerManagement {
@NotNull Collection<Long> moduleId); @NotNull Collection<Long> moduleId);
/** /**
* Simple addition of a new {@link ActionStatus} entry to the {@link Action} . * Simple addition of a new {@link ActionStatus} entry to the
* No state changes. * {@link Action}. No state changes.
* *
* @param create * @param create
* to add to the action * to add to the action
@@ -136,36 +136,48 @@ public interface ControllerManagement {
Action addUpdateActionStatus(@NotNull @Valid ActionStatusCreate create); Action addUpdateActionStatus(@NotNull @Valid ActionStatusCreate create);
/** /**
* Retrieves oldest {@link Action} that is active and assigned to a * Retrieves active {@link Action} with highest priority that is assigned to
* {@link Target}. * a {@link Target}.
* *
* For performance reasons this method does not throw * For performance reasons this method does not throw
* {@link EntityNotFoundException} in case target with given controlelrId * {@link EntityNotFoundException} in case target with given controllerId
* does not exist but will return an {@link Optional#empty()} instead. * does not exist but will return an {@link Optional#empty()} instead.
* *
* @param controllerId * @param controllerId
* identifies the target to retrieve the actions from * identifies the target to retrieve the action from
* @return a list of actions assigned to given target which are active * @return the action
* *
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Optional<Action> findOldestActiveActionByTarget(@NotEmpty String controllerId); Optional<Action> findActiveActionWithHighestWeight(@NotEmpty String controllerId);
/** /**
* Retrieves all active actions which are assigned to the target with the given * Retrieves active {@link Action}s with highest weight that are assigned to
* controller ID. * a {@link Target}.
* *
* @param pageable
* pagination parameter
* @param controllerId * @param controllerId
* of the target * identifies the target to retrieve the action from
* @return the requested {@link Page} with {@link Action}s * @param maxActionCount
* max size of returned list
* @return the action
*
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Page<Action> findActiveActionsByTarget(@NotNull Pageable pageable, @NotEmpty String controllerId); List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount);
/** /**
* Get the {@link Action} entity for given actionId with all lazy attributes. * Get weight of an Action. Returns the default value if the weight is null
* according to the properties.
*
* @param action
* to extract the weight from
* @return weight of the action
*/
int getWeightConsideringDefault(final Action action);
/**
* Get the {@link Action} entity for given actionId with all lazy
* attributes.
* *
* @param actionId * @param actionId
* to be id of the action * to be id of the action
@@ -175,7 +187,8 @@ public interface ControllerManagement {
Optional<Action> findActionWithDetails(long actionId); Optional<Action> findActionWithDetails(long actionId);
/** /**
* Retrieves all the {@link ActionStatus} entries of the given {@link Action}. * Retrieves all the {@link ActionStatus} entries of the given
* {@link Action}.
* *
* @param pageReq * @param pageReq
* pagination parameter * pagination parameter
@@ -190,10 +203,11 @@ public interface ControllerManagement {
Page<ActionStatus> findActionStatusByAction(@NotNull Pageable pageReq, long actionId); Page<ActionStatus> findActionStatusByAction(@NotNull Pageable pageReq, long actionId);
/** /**
* Register new target in the repository (plug-and-play) and in case it already * Register new target in the repository (plug-and-play) and in case it
* exists updates {@link Target#getAddress()} and * already exists updates {@link Target#getAddress()} and
* {@link Target#getLastTargetQuery()} and switches if * {@link Target#getLastTargetQuery()} and switches if
* {@link TargetUpdateStatus#UNKNOWN} to {@link TargetUpdateStatus#REGISTERED}. * {@link TargetUpdateStatus#UNKNOWN} to
* {@link TargetUpdateStatus#REGISTERED}.
* *
* @param controllerId * @param controllerId
* reference * reference
@@ -205,10 +219,11 @@ public interface ControllerManagement {
Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address); Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address);
/** /**
* Register new target in the repository (plug-and-play) and in case it already * Register new target in the repository (plug-and-play) and in case it
* exists updates {@link Target#getAddress()} and * already exists updates {@link Target#getAddress()} and
* {@link Target#getLastTargetQuery()} and {@link Target#getName()} and switches if * {@link Target#getLastTargetQuery()} and {@link Target#getName()} and
* {@link TargetUpdateStatus#UNKNOWN} to {@link TargetUpdateStatus#REGISTERED}. * switches if {@link TargetUpdateStatus#UNKNOWN} to
* {@link TargetUpdateStatus#REGISTERED}.
* *
* @param controllerId * @param controllerId
* reference * reference
@@ -222,14 +237,14 @@ public interface ControllerManagement {
Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name); Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name);
/** /**
* Retrieves last {@link Action} for a download of an artifact of given module * Retrieves last {@link Action} for a download of an artifact of given
* and target if exists and is not canceled. * module and target if exists and is not canceled.
* *
* @param controllerId * @param controllerId
* to look for * to look for
* @param moduleId * @param moduleId
* of the the {@link SoftwareModule} that should be assigned to the * of the the {@link SoftwareModule} that should be assigned to
* target * the target
* @return last {@link Action} for given combination * @return last {@link Action} for given combination
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -267,18 +282,18 @@ public interface ControllerManagement {
int getMaintenanceWindowPollCount(); int getMaintenanceWindowPollCount();
/** /**
* Returns polling time based on the maintenance window for an action. Server * Returns polling time based on the maintenance window for an action.
* will reduce the polling interval as the start time for maintenance window * Server will reduce the polling interval as the start time for maintenance
* approaches, so that at least these many attempts are made between current * window approaches, so that at least these many attempts are made between
* polling until start of maintenance window. Poll time keeps reducing with * current polling until start of maintenance window. Poll time keeps
* MinPollingTime as lower limit * reducing with MinPollingTime as lower limit
* {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start of * {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start
* maintenance window, it resets to default * of maintenance window, it resets to default
* {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
* *
* @param actionId * @param actionId
* id the {@link Action} for which polling time is calculated based * id the {@link Action} for which polling time is calculated
* on it having maintenance window or not * based on it having maintenance window or not
* *
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
*/ */
@@ -286,20 +301,20 @@ public interface ControllerManagement {
String getPollingTimeForAction(long actionId); String getPollingTimeForAction(long actionId);
/** /**
* Checks if a given target has currently or has even been assigned to the given * Checks if a given target has currently or has even been assigned to the
* artifact through the action history list. This can e.g. indicate if a target * given artifact through the action history list. This can e.g. indicate if
* is allowed to download a given artifact because it has currently assigned or * a target is allowed to download a given artifact because it has currently
* had ever been assigned to the target and so it's visible to a specific target * assigned or had ever been assigned to the target and so it's visible to a
* e.g. for downloading. * specific target e.g. for downloading.
* *
* @param controllerId * @param controllerId
* the ID of the target to check * the ID of the target to check
* @param sha1Hash * @param sha1Hash
* of the artifact to verify if the given target had even been * of the artifact to verify if the given target had even been
* assigned to * assigned to
* @return {@code true} if the given target has currently or had ever a relation * @return {@code true} if the given target has currently or had ever a
* to the given artifact through the action history, otherwise * relation to the given artifact through the action history,
* {@code false} * otherwise {@code false}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
@@ -308,20 +323,20 @@ public interface ControllerManagement {
boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash); boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash);
/** /**
* Checks if a given target has currently or has even been assigned to the given * Checks if a given target has currently or has even been assigned to the
* artifact through the action history list. This can e.g. indicate if a target * given artifact through the action history list. This can e.g. indicate if
* is allowed to download a given artifact because it has currently assigned or * a target is allowed to download a given artifact because it has currently
* had ever been assigned to the target and so it's visible to a specific target * assigned or had ever been assigned to the target and so it's visible to a
* e.g. for downloading. * specific target e.g. for downloading.
* *
* @param targetId * @param targetId
* the ID of the target to check * the ID of the target to check
* @param sha1Hash * @param sha1Hash
* of the artifact to verify if the given target had even been * of the artifact to verify if the given target had even been
* assigned to * assigned to
* @return {@code true} if the given target has currently or had ever a relation * @return {@code true} if the given target has currently or had ever a
* to the given artifact through the action history, otherwise * relation to the given artifact through the action history,
* {@code false} * otherwise {@code false}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
@@ -330,8 +345,8 @@ public interface ControllerManagement {
boolean hasTargetArtifactAssigned(long targetId, @NotEmpty String sha1Hash); boolean hasTargetArtifactAssigned(long targetId, @NotEmpty String sha1Hash);
/** /**
* Registers retrieved status for given {@link Target} and {@link Action} if it * Registers retrieved status for given {@link Target} and {@link Action} if
* does not exist yet. * it does not exist yet.
* *
* @param actionId * @param actionId
* to the handle status for * to the handle status for
@@ -385,8 +400,9 @@ public interface ControllerManagement {
Optional<Target> getByControllerId(@NotEmpty String controllerId); Optional<Target> getByControllerId(@NotEmpty String controllerId);
/** /**
* Finds {@link Target} based on given ID returns found Target without details, * Finds {@link Target} based on given ID returns found Target without
* i.e. NO {@link Target#getTags()} and {@link Target#getActions()} possible. * details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()}
* possible.
* *
* @param targetId * @param targetId
* to look for. * to look for.
@@ -398,20 +414,21 @@ public interface ControllerManagement {
Optional<Target> get(long targetId); Optional<Target> get(long targetId);
/** /**
* Retrieves the specified number of messages from action history of the given * Retrieves the specified number of messages from action history of the
* {@link Action} based on messageCount. Regardless of the value of * given {@link Action} based on messageCount. Regardless of the value of
* messageCount, in order to restrict resource utilisation by controllers, * messageCount, in order to restrict resource utilisation by controllers,
* maximum number of messages that are retrieved from database is limited by * maximum number of messages that are retrieved from database is limited by
* {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount less * {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount
* then zero, retrieves the maximum allowed number of action status messages * less then zero, retrieves the maximum allowed number of action status
* from history; messageCount equal zero, does not retrieve any message; and * messages from history; messageCount equal zero, does not retrieve any
* messageCount larger then zero, retrieves the specified number of messages, * message; and messageCount larger then zero, retrieves the specified
* limited by maximum allowed number. A controller sends the feedback for an * number of messages, limited by maximum allowed number. A controller sends
* {@link ActionStatus} as a list of messages; while returning the messages, * the feedback for an {@link ActionStatus} as a list of messages; while
* even though the messages from multiple {@link ActionStatus} are retrieved in * returning the messages, even though the messages from multiple
* descending order by the reported time ({@link ActionStatus#getOccurredAt()}), * {@link ActionStatus} are retrieved in descending order by the reported
* i.e. latest ActionStatus first, the sub-ordering of messages from within * time ({@link ActionStatus#getOccurredAt()}), i.e. latest ActionStatus
* single {@link ActionStatus} is unspecified. * first, the sub-ordering of messages from within single
* {@link ActionStatus} is unspecified.
* *
* @param actionId * @param actionId
* to be filtered on * to be filtered on
@@ -424,10 +441,10 @@ public interface ControllerManagement {
List<String> getActionHistoryMessages(long actionId, int messageCount); List<String> getActionHistoryMessages(long actionId, int messageCount);
/** /**
* Cancels given {@link Action} for this {@link Target}. However, it might be * Cancels given {@link Action} for this {@link Target}. However, it might
* possible that the controller will continue to work on the cancellation. The * be possible that the controller will continue to work on the
* controller needs to acknowledge or reject the cancellation using * cancellation. The controller needs to acknowledge or reject the
* {@link DdiRootController#postCancelActionFeedback}. * cancellation using {@link DdiRootController#postCancelActionFeedback}.
* *
* @param actionId * @param actionId
* to be canceled * to be canceled
@@ -454,7 +471,8 @@ public interface ControllerManagement {
void updateActionExternalRef(long actionId, @NotEmpty String externalRef); void updateActionExternalRef(long actionId, @NotEmpty String externalRef);
/** /**
* Retrieves list of {@link Action}s which matches the provided externalRefs. * Retrieves list of {@link Action}s which matches the provided
* externalRefs.
* *
* @param externalRefs * @param externalRefs
* for which the actions need to be fetched. * for which the actions need to be fetched.

View File

@@ -14,6 +14,7 @@ import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -76,7 +77,7 @@ public interface DeploymentManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets( List<DistributionSetAssignmentResult> assignDistributionSets(
@NotEmpty List<DeploymentRequest> deploymentRequests); @Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
/** /**
* Assigns {@link DistributionSet}s to {@link Target}s according to the * Assigns {@link DistributionSet}s to {@link Target}s according to the
@@ -107,7 +108,7 @@ public interface DeploymentManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets( List<DistributionSetAssignmentResult> assignDistributionSets(
@NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage); @Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage);
/** /**
* build a {@link DeploymentRequest} for a target distribution set * build a {@link DeploymentRequest} for a target distribution set
@@ -372,6 +373,30 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Action> findInActiveActionsByTarget(@NotNull Pageable pageable, @NotEmpty String controllerId); Page<Action> findInActiveActionsByTarget(@NotNull Pageable pageable, @NotEmpty String controllerId);
/**
* Retrieves active {@link Action}s with highest weight that are assigned to
* a {@link Target}.
*
* @param controllerId
* identifies the target to retrieve the action from
* @param maxActionCount
* max size of returned list
* @return the action
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount);
/**
* Get weight of an Action. Returns the default value if the weight is null
* according to the properties.
*
* @param action
* to extract the weight from
* @return weight of the action
*/
int getWeightConsideringDefault(final Action action);
/** /**
* Force cancels given {@link Action} for given {@link Target}. Force * Force cancels given {@link Action} for given {@link Target}. Force
* canceling means that the action is marked as canceled on the SP server * canceling means that the action is marked as canceled on the SP server

View File

@@ -27,8 +27,8 @@ public class RepositoryProperties {
* especially useful if the action status feedback channel order from the * especially useful if the action status feedback channel order from the
* device cannot be guaranteed. * device cannot be guaranteed.
* *
* Note: if this is enforced you have to make sure that the feedback * Note: if this is enforced you have to make sure that the feedback channel
* channel from the devices is in order. * from the devices is in order.
*/ */
private boolean rejectActionStatusForClosedAction; private boolean rejectActionStatusForClosedAction;
@@ -54,6 +54,11 @@ public class RepositoryProperties {
*/ */
private boolean eagerPollPersistence; private boolean eagerPollPersistence;
/**
* If an {@link Action} has a weight of null this value is used as weight.
*/
private int actionWeightIfAbsent = 1000;
public boolean isEagerPollPersistence() { public boolean isEagerPollPersistence() {
return eagerPollPersistence; return eagerPollPersistence;
} }
@@ -94,4 +99,12 @@ public class RepositoryProperties {
this.publishTargetPollEvent = publishTargetPollEvent; this.publishTargetPollEvent = publishTargetPollEvent;
} }
public int getActionWeightIfAbsent() {
return actionWeightIfAbsent;
}
public void setActionWeightIfAbsent(final int actionWeightIfAbsent) {
this.actionWeightIfAbsent = actionWeightIfAbsent;
}
} }

View File

@@ -129,7 +129,7 @@ public interface RolloutManagement {
* exceeded. * exceeded.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull RolloutCreate create, int amountGroup, @NotNull RolloutGroupConditions conditions); Rollout create(@NotNull @Valid RolloutCreate create, int amountGroup, @NotNull RolloutGroupConditions conditions);
/** /**
* Persists a new rollout entity. The filter within the * Persists a new rollout entity. The filter within the
@@ -166,7 +166,7 @@ public interface RolloutManagement {
* exceeded. * exceeded.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull @Valid RolloutCreate rollout, @NotNull @Valid List<RolloutGroupCreate> groups, Rollout create(@Valid @NotNull RolloutCreate rollout, @NotNull @Valid List<RolloutGroupCreate> groups,
RolloutGroupConditions conditions); RolloutGroupConditions conditions);
/** /**
@@ -342,9 +342,9 @@ public interface RolloutManagement {
/** /**
* Approves or denies a created rollout being in state * Approves or denies a created rollout being in state
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it * {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved,
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state * it switches state to {@link RolloutStatus#READY}, otherwise it switches
* {@link RolloutStatus#APPROVAL_DENIED} * to state {@link RolloutStatus#APPROVAL_DENIED}
* *
* @param rolloutId * @param rolloutId
* the rollout to be approved or denied. * the rollout to be approved or denied.
@@ -365,9 +365,9 @@ public interface RolloutManagement {
/** /**
* Approves or denies a created rollout being in state * Approves or denies a created rollout being in state
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it * {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved,
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state * it switches state to {@link RolloutStatus#READY}, otherwise it switches
* {@link RolloutStatus#APPROVAL_DENIED} * to state {@link RolloutStatus#APPROVAL_DENIED}
* *
* @param rolloutId * @param rolloutId
* the rollout to be approved or denied. * the rollout to be approved or denied.

View File

@@ -15,6 +15,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -208,7 +209,7 @@ public interface TargetFilterQueryManagement {
* if fields are not filled as specified. Check * if fields are not filled as specified. Check
* {@link TargetFilterQueryUpdate} for field constraints. * {@link TargetFilterQueryUpdate} for field constraints.
* *
* @throws AssignmentQuotaExceededException * @throws QuotaExceededException
* if the update contains a new query which addresses too many * if the update contains a new query which addresses too many
* targets (auto-assignments only) * targets (auto-assignments only)
*/ */
@@ -216,46 +217,10 @@ public interface TargetFilterQueryManagement {
TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update); TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update);
/** /**
* Updates the the auto-assign {@link DistributionSet} and sets default * Updates the auto assign settings of an {@link TargetFilterQuery}.
* (FORCED) {@link ActionType} of the addressed {@link TargetFilterQuery}.
* *
* @param queryId * @param autoAssignDistributionSetUpdate
* of the target filter query to be updated * the new auto assignment
* @param dsId
* to be updated or <code>null</code> in order to remove it
* together with the auto-assign {@link ActionType}
*
* @return the updated {@link TargetFilterQuery}
*
* @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found
*
* @throws AssignmentQuotaExceededException
* if the query that is already associated with this filter
* query addresses too many targets (auto-assignments only)
*
* @throws InvalidAutoAssignDistributionSetException
* if the provided auto-assign {@link DistributionSet} is not
* valid (incomplete or soft deleted)
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
default TargetFilterQuery updateAutoAssignDS(final long queryId, final Long dsId) {
return updateAutoAssignDSWithActionType(queryId, dsId, null);
}
/**
* Updates the the auto-assign {@link DistributionSet} and
* {@link ActionType} of the addressed {@link TargetFilterQuery}.
*
* @param queryId
* of the target filter query to be updated
* @param dsId
* to be updated or <code>null</code> in order to remove it
* together with the auto-assign {@link ActionType}
* @param actionType
* to be updated or <code>null</code> for default (FORCED) if
* distribution set Id is present
* *
* @return the updated {@link TargetFilterQuery} * @return the updated {@link TargetFilterQuery}
* *
@@ -276,5 +241,6 @@ public interface TargetFilterQueryManagement {
* valid (incomplete or soft deleted) * valid (incomplete or soft deleted)
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery updateAutoAssignDSWithActionType(long queryId, Long dsId, ActionType actionType); TargetFilterQuery updateAutoAssignDS(
@NotNull @Valid AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate);
} }

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2019 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.builder;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/**
* Builder to update the auto assign {@link DistributionSet} of a
* {@link TargetFilterQuery} entry. Defines all fields that can be updated.
*/
public class AutoAssignDistributionSetUpdate {
private final long targetFilterId;
private Long dsId;
private ActionType actionType;
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private Integer weight;
/**
* Constructor
*
* @param targetFilterId
* ID of {@link TargetFilterQuery} to update
*/
public AutoAssignDistributionSetUpdate(final long targetFilterId) {
this.targetFilterId = targetFilterId;
}
/**
* Specify {@link DistributionSet}
*
* @param dsId
* ID of the {@link DistributionSet}
* @return updated builder instance
*/
public AutoAssignDistributionSetUpdate ds(final Long dsId) {
this.dsId = dsId;
return this;
}
/**
* Specify {@link DistributionSet}
*
* @param actionType
* {@link ActionType} used for the auto assignment
* @return updated builder instance
*/
public AutoAssignDistributionSetUpdate actionType(final ActionType actionType) {
this.actionType = actionType;
return this;
}
/**
* Specify weight of resulting {@link Action}
*
* @param weight
* weight used for the auto assignment
* @return updated builder instance
*/
public AutoAssignDistributionSetUpdate weight(final Integer weight) {
this.weight = weight;
return this;
}
public Long getDsId() {
return dsId;
}
public ActionType getActionType() {
return actionType;
}
public Integer getWeight() {
return weight;
}
public long getTargetFilterId() {
return targetFilterId;
}
}

View File

@@ -28,6 +28,8 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
*/ */
public interface RolloutCreate { public interface RolloutCreate {
/** /**
* Set name
*
* @param name * @param name
* for {@link Rollout#getName()} * for {@link Rollout#getName()}
* @return updated builder instance * @return updated builder instance
@@ -35,6 +37,8 @@ public interface RolloutCreate {
RolloutCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name); RolloutCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/** /**
* Set description
*
* @param description * @param description
* for {@link Rollout#getDescription()} * for {@link Rollout#getDescription()}
* @return updated builder instance * @return updated builder instance
@@ -42,6 +46,8 @@ public interface RolloutCreate {
RolloutCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description); RolloutCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/** /**
* Set the {@link DistributionSet}
*
* @param set * @param set
* for {@link Rollout#getDistributionSet()} * for {@link Rollout#getDistributionSet()}
* @return updated builder instance * @return updated builder instance
@@ -51,6 +57,8 @@ public interface RolloutCreate {
} }
/** /**
* Set the id of the {@link DistributionSet}
*
* @param setId * @param setId
* for {@link Rollout#getDistributionSet()} * for {@link Rollout#getDistributionSet()}
* @return updated builder instance * @return updated builder instance
@@ -58,6 +66,8 @@ public interface RolloutCreate {
RolloutCreate set(long setId); RolloutCreate set(long setId);
/** /**
* Set the {@link TargetFilterQuery}
*
* @param targetFilterQuery * @param targetFilterQuery
* for {@link Rollout#getTargetFilterQuery()} * for {@link Rollout#getTargetFilterQuery()}
* @return updated builder instance * @return updated builder instance
@@ -66,6 +76,8 @@ public interface RolloutCreate {
@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String targetFilterQuery); @Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String targetFilterQuery);
/** /**
* Set the {@link ActionType}
*
* @param actionType * @param actionType
* for {@link Rollout#getActionType()} * for {@link Rollout#getActionType()}
* @return updated builder instance * @return updated builder instance
@@ -73,6 +85,8 @@ public interface RolloutCreate {
RolloutCreate actionType(@NotNull ActionType actionType); RolloutCreate actionType(@NotNull ActionType actionType);
/** /**
* Set the forcedTime of the resulting {@link Actions}
*
* @param forcedTime * @param forcedTime
* for {@link Rollout#getForcedTime()} * for {@link Rollout#getForcedTime()}
* @return updated builder instance * @return updated builder instance
@@ -80,6 +94,17 @@ public interface RolloutCreate {
RolloutCreate forcedTime(Long forcedTime); RolloutCreate forcedTime(Long forcedTime);
/** /**
* Set the weight of the resulting {@link Actions}
*
* @param weight
* for {@link Rollout#getWeight()}
* @return updated builder instance
*/
RolloutCreate weight(Integer weight);
/**
* set start
*
* @param startAt * @param startAt
* for {@link Rollout#getStartAt()} * for {@link Rollout#getStartAt()}
* @return updated builder instance * @return updated builder instance

View File

@@ -12,6 +12,7 @@ import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
@@ -22,6 +23,8 @@ import org.eclipse.hawkbit.repository.model.Rollout;
*/ */
public interface RolloutUpdate { public interface RolloutUpdate {
/** /**
* Set name of the {@link Rollout}
*
* @param name * @param name
* for {@link Rollout#getName()} * for {@link Rollout#getName()}
* @return updated builder instance * @return updated builder instance
@@ -29,6 +32,8 @@ public interface RolloutUpdate {
RolloutUpdate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name); RolloutUpdate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/** /**
* Set description of the {@link Rollout}
*
* @param description * @param description
* for {@link Rollout#getDescription()} * for {@link Rollout#getDescription()}
* @return updated builder instance * @return updated builder instance
@@ -36,6 +41,8 @@ public interface RolloutUpdate {
RolloutUpdate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description); RolloutUpdate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/** /**
* Set ID of {@link DistributionSet} of the {@link Rollout}
*
* @param setId * @param setId
* for {@link Rollout#getDistributionSet()} * for {@link Rollout#getDistributionSet()}
* @return updated builder instance * @return updated builder instance
@@ -43,6 +50,8 @@ public interface RolloutUpdate {
RolloutUpdate set(long setId); RolloutUpdate set(long setId);
/** /**
* Set action type of the {@link Rollout}
*
* @param actionType * @param actionType
* for {@link Rollout#getActionType()} * for {@link Rollout#getActionType()}
* @return updated builder instance * @return updated builder instance
@@ -50,6 +59,8 @@ public interface RolloutUpdate {
RolloutUpdate actionType(@NotNull Action.ActionType actionType); RolloutUpdate actionType(@NotNull Action.ActionType actionType);
/** /**
* Set forcedTime of the {@link Rollout}
*
* @param forcedTime * @param forcedTime
* for {@link Rollout#getForcedTime()} * for {@link Rollout#getForcedTime()}
* @return updated builder instance * @return updated builder instance
@@ -57,10 +68,20 @@ public interface RolloutUpdate {
RolloutUpdate forcedTime(Long forcedTime); RolloutUpdate forcedTime(Long forcedTime);
/** /**
* Set weight of {@link Action}s created by the {@link Rollout}
*
* @param weight
* for {@link Rollout#getWeight()}
* @return updated builder instance
*/
RolloutUpdate weight(Integer weight);
/**
* Set start time of the {@link Rollout}
*
* @param startAt * @param startAt
* for {@link Rollout#getStartAt()} * for {@link Rollout#getStartAt()}
* @return updated builder instance * @return updated builder instance
*/ */
RolloutUpdate startAt(Long startAt); RolloutUpdate startAt(Long startAt);
} }

View File

@@ -16,12 +16,23 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
*/ */
public interface TargetFilterQueryBuilder { public interface TargetFilterQueryBuilder {
/** /**
* Used to update a {@link TargetFilterQuery}
*
* @param id * @param id
* of the updatable entity * of the updatable entity
* @return builder instance * @return builder instance
*/ */
TargetFilterQueryUpdate update(long id); TargetFilterQueryUpdate update(long id);
/**
* Used to update a the auto assignment of a {@link TargetFilterQuery}
*
* @param id
* of the updatable entity
* @return builder instance
*/
AutoAssignDistributionSetUpdate updateAutoAssign(long id);
/** /**
* @return builder instance * @return builder instance
*/ */

View File

@@ -13,6 +13,7 @@ import java.util.Optional;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -27,6 +28,8 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
*/ */
public interface TargetFilterQueryCreate { public interface TargetFilterQueryCreate {
/** /**
* Set filter name
*
* @param name * @param name
* of {@link TargetFilterQuery#getName()} * of {@link TargetFilterQuery#getName()}
* @return updated builder instance * @return updated builder instance
@@ -34,6 +37,8 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name); TargetFilterQueryCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/** /**
* Set filter query
*
* @param query * @param query
* of {@link TargetFilterQuery#getQuery()} * of {@link TargetFilterQuery#getQuery()}
* @return updated builder instance * @return updated builder instance
@@ -41,6 +46,8 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String query); TargetFilterQueryCreate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String query);
/** /**
* Set {@link DistributionSet} for auto assignment
*
* @param distributionSet * @param distributionSet
* for {@link TargetFilterQuery#getAutoAssignDistributionSet()} * for {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return updated builder instance * @return updated builder instance
@@ -50,6 +57,8 @@ public interface TargetFilterQueryCreate {
} }
/** /**
* Set ID of {@link DistributionSet} for auto assignment
*
* @param dsId * @param dsId
* for {@link TargetFilterQuery#getAutoAssignDistributionSet()} * for {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return updated builder instance * @return updated builder instance
@@ -57,12 +66,23 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate autoAssignDistributionSet(Long dsId); TargetFilterQueryCreate autoAssignDistributionSet(Long dsId);
/** /**
* Set {@link ActionType} for auto assignment
*
* @param actionType * @param actionType
* for {@link TargetFilterQuery#getAutoAssignActionType()} * for {@link TargetFilterQuery#getAutoAssignActionType()}
* @return updated builder instance * @return updated builder instance
*/ */
TargetFilterQueryCreate autoAssignActionType(ActionType actionType); TargetFilterQueryCreate autoAssignActionType(ActionType actionType);
/**
* Set weight of {@link Action} created during auto assignment
*
* @param weight
* weight of {@link Action} generated within auto assignment
* @return updated builder instance
*/
TargetFilterQueryCreate autoAssignWeight(Integer weight);
/** /**
* @return peek on current state of {@link TargetFilterQuery} in the builder * @return peek on current state of {@link TargetFilterQuery} in the builder
*/ */

View File

@@ -32,5 +32,4 @@ public interface TargetFilterQueryUpdate {
* @return updated builder instance * @return updated builder instance
*/ */
TargetFilterQueryUpdate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) String query); TargetFilterQueryUpdate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) String query);
} }

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2019 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.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* This exception is thrown if multi assignments is enabled and an target
* distribution set assignment request does not contain a weight value
*
*/
public class NoWeightProvidedInMultiAssignmentModeException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE;
/**
* Default constructor.
*/
public NoWeightProvidedInMultiAssignmentModeException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public NoWeightProvidedInMultiAssignmentModeException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public NoWeightProvidedInMultiAssignmentModeException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public NoWeightProvidedInMultiAssignmentModeException(final String message) {
super(message, THIS_ERROR);
}
}

View File

@@ -39,6 +39,15 @@ public interface Action extends TenantAwareBaseEntity {
*/ */
int EXTERNAL_REF_MAX_LENGTH = 512; int EXTERNAL_REF_MAX_LENGTH = 512;
/**
* Minimum weight to indicate the priority of {@link Action}.
*/
int WEIGHT_MIN = 0;
/**
* Maximum weight to indicate the priority of {@link Action}.
*/
int WEIGHT_MAX = 1000;
/** /**
* @return the distributionSet * @return the distributionSet
*/ */
@@ -80,6 +89,11 @@ public interface Action extends TenantAwareBaseEntity {
*/ */
long getForcedTime(); long getForcedTime();
/**
* @return priority of the {@link Action}.
*/
Optional<Integer> getWeight();
/** /**
* @return rolloutGroup related to this {@link Action}. * @return rolloutGroup related to this {@link Action}.
*/ */
@@ -106,7 +120,8 @@ public interface Action extends TenantAwareBaseEntity {
String getMaintenanceWindowTimeZone(); String getMaintenanceWindowTimeZone();
/** /**
* @param externalRef associated with this action * @param externalRef
* associated with this action
*/ */
void setExternalRef(@NotEmpty String externalRef); void setExternalRef(@NotEmpty String externalRef);

View File

@@ -10,6 +10,8 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Objects; import java.util.Objects;
import javax.validation.Valid;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException; import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -19,6 +21,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
*/ */
public class DeploymentRequest { public class DeploymentRequest {
private final Long distributionSetId; private final Long distributionSetId;
@Valid
private final TargetWithActionType targetWithActionType; private final TargetWithActionType targetWithActionType;
/** /**
@@ -34,6 +37,8 @@ public class DeploymentRequest {
* specified for the action. * specified for the action.
* @param forceTime * @param forceTime
* at what time the type soft turns into forced. * at what time the type soft turns into forced.
* @param weight
* the priority of an {@link Action}.
* @param maintenanceSchedule * @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance * is the cron expression to be used for scheduling maintenance
* windows. Expression has 6 mandatory fields and 1 last optional * windows. Expression has 6 mandatory fields and 1 last optional
@@ -51,11 +56,10 @@ public class DeploymentRequest {
* if the parameters do not define a valid maintenance schedule. * if the parameters do not define a valid maintenance schedule.
*/ */
public DeploymentRequest(final String controllerId, final Long distributionSetId, final ActionType actionType, public DeploymentRequest(final String controllerId, final Long distributionSetId, final ActionType actionType,
final long forceTime, final String maintenanceSchedule, final String maintenanceWindowDuration, final long forceTime, final Integer weight, final String maintenanceSchedule,
final String maintenanceWindowTimeZone) { final String maintenanceWindowDuration, final String maintenanceWindowTimeZone) {
this.targetWithActionType = new TargetWithActionType(controllerId, actionType, forceTime, maintenanceSchedule, this.targetWithActionType = new TargetWithActionType(controllerId, actionType, forceTime, weight,
maintenanceWindowDuration, maintenanceSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
maintenanceWindowTimeZone);
this.distributionSetId = distributionSetId; this.distributionSetId = distributionSetId;
} }
@@ -71,14 +75,13 @@ public class DeploymentRequest {
return targetWithActionType; return targetWithActionType;
} }
@Override @Override
public String toString() { public String toString() {
return String.format( return String.format(
"DeploymentRequest [controllerId=%s, distributionSetId=%d, actionType=%s, forceTime=%d, maintenanceSchedule=%s, maintenanceWindowDuration=%s, maintenanceWindowTimeZone=%s]", "DeploymentRequest [controllerId=%s, distributionSetId=%d, actionType=%s, forceTime=%d, weight=%d, maintenanceSchedule=%s, maintenanceWindowDuration=%s, maintenanceWindowTimeZone=%s]",
targetWithActionType.getControllerId(), getDistributionSetId(), targetWithActionType.getActionType(), targetWithActionType.getControllerId(), getDistributionSetId(), targetWithActionType.getActionType(),
targetWithActionType.getForceTime(), targetWithActionType.getMaintenanceSchedule(), targetWithActionType.getForceTime(), targetWithActionType.getWeight(),
targetWithActionType.getMaintenanceWindowDuration(), targetWithActionType.getMaintenanceSchedule(), targetWithActionType.getMaintenanceWindowDuration(),
targetWithActionType.getMaintenanceWindowTimeZone()); targetWithActionType.getMaintenanceWindowTimeZone());
} }

View File

@@ -18,7 +18,7 @@ public class DeploymentRequestBuilder {
private final String controllerId; private final String controllerId;
private final Long distributionSetId; private final Long distributionSetId;
private Integer weight;
private long forceTime = RepositoryModelConstants.NO_FORCE_TIME; private long forceTime = RepositoryModelConstants.NO_FORCE_TIME;
private ActionType actionType = ActionType.FORCED; private ActionType actionType = ActionType.FORCED;
private String maintenanceSchedule; private String maintenanceSchedule;
@@ -63,6 +63,18 @@ public class DeploymentRequestBuilder {
return this; return this;
} }
/**
* Set the weight of the action.
*
* @param weight
* the priority given to the action.
* @return builder
*/
public DeploymentRequestBuilder setWeight(final Integer weight) {
this.weight = weight;
return this;
}
/** /**
* Set a maintenanceWindow * Set a maintenanceWindow
* *
@@ -94,8 +106,8 @@ public class DeploymentRequestBuilder {
* @return the request object * @return the request object
*/ */
public DeploymentRequest build() { public DeploymentRequest build() {
return new DeploymentRequest(controllerId, distributionSetId, actionType, forceTime, maintenanceSchedule, return new DeploymentRequest(controllerId, distributionSetId, actionType, forceTime, weight,
maintenanceWindowDuration, maintenanceWindowTimeZone); maintenanceSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
} }
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.repository.model; package org.eclipse.hawkbit.repository.model;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -101,6 +102,11 @@ public interface Rollout extends NamedEntity {
*/ */
String getApprovalRemark(); String getApprovalRemark();
/**
* @return the priority of {@link Rollout}.
*/
Optional<Integer> getWeight();
/** /**
* *
* State machine for rollout. * State machine for rollout.
@@ -197,5 +203,4 @@ public interface Rollout extends NamedEntity {
*/ */
DENIED DENIED
} }
} }

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -70,4 +71,10 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity {
*/ */
ActionType getAutoAssignActionType(); ActionType getAutoAssignActionType();
/**
* @return the weight of the {@link Action}s created during an auto
* assignment.
*/
Optional<Integer> getAutoAssignWeight();
} }

View File

@@ -10,6 +10,9 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Objects; import java.util.Objects;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException; import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -21,6 +24,9 @@ public class TargetWithActionType {
private final String controllerId; private final String controllerId;
private final ActionType actionType; private final ActionType actionType;
private final long forceTime; private final long forceTime;
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private final Integer weight;
private String maintenanceSchedule; private String maintenanceSchedule;
private String maintenanceWindowDuration; private String maintenanceWindowDuration;
private String maintenanceWindowTimeZone; private String maintenanceWindowTimeZone;
@@ -32,7 +38,7 @@ public class TargetWithActionType {
* ID if the controller * ID if the controller
*/ */
public TargetWithActionType(final String controllerId) { public TargetWithActionType(final String controllerId) {
this(controllerId, ActionType.FORCED, 0); this(controllerId, ActionType.FORCED, 0, null);
} }
/** /**
@@ -43,13 +49,17 @@ public class TargetWithActionType {
* @param actionType * @param actionType
* specified for the action. * specified for the action.
* @param forceTime * @param forceTime
* after that point in time the action is exposed as forcen in * after that point in time the action is exposed as forced in
* case the type is {@link ActionType#TIMEFORCED} * case the type is {@link ActionType#TIMEFORCED}
* @param weight
* the priority of an {@link Action}
*/ */
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime) { public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime,
final Integer weight) {
this.controllerId = controllerId; this.controllerId = controllerId;
this.actionType = actionType != null ? actionType : ActionType.FORCED; this.actionType = actionType != null ? actionType : ActionType.FORCED;
this.forceTime = forceTime; this.forceTime = forceTime;
this.weight = weight;
} }
/** /**
@@ -61,8 +71,10 @@ public class TargetWithActionType {
* @param actionType * @param actionType
* specified for the action. * specified for the action.
* @param forceTime * @param forceTime
* after that point in time the action is exposed as forcen in * after that point in time the action is exposed as forced in
* case the type is {@link ActionType#TIMEFORCED} * case the type is {@link ActionType#TIMEFORCED}
* @param weight
* the priority of an {@link Action}
* @param maintenanceSchedule * @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance * is the cron expression to be used for scheduling maintenance
* windows. Expression has 6 mandatory fields and 1 last optional * windows. Expression has 6 mandatory fields and 1 last optional
@@ -80,9 +92,9 @@ public class TargetWithActionType {
* if the parameters do not define a valid maintenance schedule. * if the parameters do not define a valid maintenance schedule.
*/ */
public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime, public TargetWithActionType(final String controllerId, final ActionType actionType, final long forceTime,
final String maintenanceSchedule, final String maintenanceWindowDuration, final Integer weight, final String maintenanceSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) { final String maintenanceWindowTimeZone) {
this(controllerId, actionType, forceTime); this(controllerId, actionType, forceTime, weight);
this.maintenanceSchedule = maintenanceSchedule; this.maintenanceSchedule = maintenanceSchedule;
this.maintenanceWindowDuration = maintenanceWindowDuration; this.maintenanceWindowDuration = maintenanceWindowDuration;
@@ -104,6 +116,11 @@ public class TargetWithActionType {
return RepositoryModelConstants.NO_FORCE_TIME; return RepositoryModelConstants.NO_FORCE_TIME;
} }
public Integer getWeight() {
return weight;
}
public String getControllerId() { public String getControllerId() {
return controllerId; return controllerId;
} }
@@ -138,14 +155,14 @@ public class TargetWithActionType {
@Override @Override
public String toString() { public String toString() {
return "TargetWithActionType [controllerId=" + controllerId + ", actionType=" + getActionType() + ", forceTime=" return "TargetWithActionType [controllerId=" + controllerId + ", actionType=" + getActionType() + ", forceTime="
+ getForceTime() + ", maintenanceSchedule=" + getMaintenanceSchedule() + ", maintenanceWindowDuration=" + getForceTime() + ", weight=" + getWeight() + ", maintenanceSchedule=" + getMaintenanceSchedule()
+ getMaintenanceWindowDuration() + ", maintenanceWindowTimeZone=" + getMaintenanceWindowTimeZone() + ", maintenanceWindowDuration=" + getMaintenanceWindowDuration() + ", maintenanceWindowTimeZone="
+ "]"; + getMaintenanceWindowTimeZone() + "]";
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(actionType, controllerId, forceTime, maintenanceSchedule, maintenanceWindowDuration, return Objects.hash(actionType, controllerId, forceTime, weight, maintenanceSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone); maintenanceWindowTimeZone);
} }
@@ -163,10 +180,9 @@ public class TargetWithActionType {
} }
final TargetWithActionType other = (TargetWithActionType) obj; final TargetWithActionType other = (TargetWithActionType) obj;
return Objects.equals(actionType, other.actionType) && Objects.equals(controllerId, other.controllerId) return Objects.equals(actionType, other.actionType) && Objects.equals(controllerId, other.controllerId)
&& Objects.equals(forceTime, other.forceTime) && Objects.equals(forceTime, other.forceTime) && Objects.equals(weight, other.weight)
&& Objects.equals(maintenanceSchedule, other.maintenanceSchedule) && Objects.equals(maintenanceSchedule, other.maintenanceSchedule)
&& Objects.equals(maintenanceWindowDuration, other.maintenanceWindowDuration) && Objects.equals(maintenanceWindowDuration, other.maintenanceWindowDuration)
&& Objects.equals(maintenanceWindowTimeZone, other.maintenanceWindowTimeZone); && Objects.equals(maintenanceWindowTimeZone, other.maintenanceWindowTimeZone);
} }
} }

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation; import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.integration.support.locks.LockRegistry; import org.springframework.integration.support.locks.LockRegistry;
@@ -54,12 +55,18 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
protected final RolloutApprovalStrategy rolloutApprovalStrategy; protected final RolloutApprovalStrategy rolloutApprovalStrategy;
protected final TenantConfigurationManagement tenantConfigurationManagement;
protected final SystemSecurityContext systemSecurityContext;
protected AbstractRolloutManagement(final TargetManagement targetManagement, protected AbstractRolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final LockRegistry lockRegistry, final TenantAware tenantAware, final LockRegistry lockRegistry,
final RolloutApprovalStrategy rolloutApprovalStrategy) { final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
this.rolloutGroupManagement = rolloutGroupManagement; this.rolloutGroupManagement = rolloutGroupManagement;
@@ -70,6 +77,8 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry; this.lockRegistry = lockRegistry;
this.rolloutApprovalStrategy = rolloutApprovalStrategy; this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
} }
protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter, protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
@@ -156,5 +165,4 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
return new AsyncResult<>(validateTargetsInGroups( return new AsyncResult<>(validateTargetsInGroups(
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets)); groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
} }
} }

View File

@@ -10,8 +10,13 @@ package org.eclipse.hawkbit.repository.builder;
import java.util.Optional; import java.util.Optional;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.eclipse.hawkbit.repository.ValidString; import org.eclipse.hawkbit.repository.ValidString;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -30,26 +35,77 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
protected Long forcedTime; protected Long forcedTime;
protected Long startAt; protected Long startAt;
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
protected Integer weight;
/**
* {@link DistributionSet} of rollout
*
* @param set
* ID of the set
* @return this builder
*/
public T set(final long set) { public T set(final long set) {
this.set = set; this.set = set;
return (T) this; return (T) this;
} }
/**
* Filter of the rollout
*
* @param targetFilterQuery
* query
* @return this builder
*/
public T targetFilterQuery(final String targetFilterQuery) { public T targetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = StringUtils.trimWhitespace(targetFilterQuery); this.targetFilterQuery = StringUtils.trimWhitespace(targetFilterQuery);
return (T) this; return (T) this;
} }
/**
* {@link ActionType} used for {@link Action}s
*
* @param actionType
* type
* @return this builder
*/
public T actionType(final ActionType actionType) { public T actionType(final ActionType actionType) {
this.actionType = actionType; this.actionType = actionType;
return (T) this; return (T) this;
} }
/**
* forcedTime used for {@link Action}s
*
* @param forcedTime
* time
* @return this builder
*/
public T forcedTime(final Long forcedTime) { public T forcedTime(final Long forcedTime) {
this.forcedTime = forcedTime; this.forcedTime = forcedTime;
return (T) this; return (T) this;
} }
/**
* weight used for {@link Action}s
*
* @param weight
* weight
* @return this builder
*/
public T weight(final Integer weight) {
this.weight = weight;
return (T) this;
}
/**
* Set start of the Rollout
*
* @param startAt
* start time point
* @return this builder
*/
public T startAt(final Long startAt) { public T startAt(final Long startAt) {
this.startAt = startAt; this.startAt = startAt;
return (T) this; return (T) this;
@@ -67,6 +123,10 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
return Optional.ofNullable(forcedTime); return Optional.ofNullable(forcedTime);
} }
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public Optional<Long> getStartAt() { public Optional<Long> getStartAt() {
return Optional.ofNullable(startAt); return Optional.ofNullable(startAt);
} }

View File

@@ -10,8 +10,13 @@ package org.eclipse.hawkbit.repository.builder;
import java.util.Optional; import java.util.Optional;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.eclipse.hawkbit.repository.ValidString; import org.eclipse.hawkbit.repository.ValidString;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -31,6 +36,17 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
protected ActionType actionType; protected ActionType actionType;
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
protected Integer weight;
/**
* Set DS ID of the {@link Action} created during auto assignment
*
* @param distributionSetId
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T autoAssignDistributionSet(final Long distributionSetId) { public T autoAssignDistributionSet(final Long distributionSetId) {
this.distributionSetId = distributionSetId; this.distributionSetId = distributionSetId;
return (T) this; return (T) this;
@@ -40,15 +56,46 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
return Optional.ofNullable(distributionSetId); return Optional.ofNullable(distributionSetId);
} }
/**
* Set {@link ActionType} of the {@link Action} created during auto
* assignment
*
* @param actionType
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T autoAssignActionType(final ActionType actionType) { public T autoAssignActionType(final ActionType actionType) {
this.actionType = actionType; this.actionType = actionType;
return (T) this; return (T) this;
} }
/**
* Set weight of the {@link Action} created during auto assignment
*
* @param weight
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T autoAssignWeight(final Integer weight) {
this.weight = weight;
return (T) this;
}
public Optional<Integer> getAutoAssignWeight() {
return Optional.ofNullable(weight);
}
public Optional<ActionType> getAutoAssignActionType() { public Optional<ActionType> getAutoAssignActionType() {
return Optional.ofNullable(actionType); return Optional.ofNullable(actionType);
} }
/**
* Set name of the filter
*
* @param name
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T name(final String name) { public T name(final String name) {
this.name = StringUtils.trimWhitespace(name); this.name = StringUtils.trimWhitespace(name);
return (T) this; return (T) this;
@@ -58,6 +105,13 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
return Optional.ofNullable(name); return Optional.ofNullable(name);
} }
/**
* Set query used by the filter
*
* @param query
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T query(final String query) { public T query(final String query) {
this.query = StringUtils.trimWhitespace(query); this.query = StringUtils.trimWhitespace(query);
return (T) this; return (T) this;

View File

@@ -16,5 +16,4 @@ public class GenericRolloutUpdate extends AbstractRolloutUpdateCreate<RolloutUpd
public GenericRolloutUpdate(final Long id) { public GenericRolloutUpdate(final Long id) {
super.id = id; super.id = id;
} }
} }

View File

@@ -212,6 +212,7 @@ public abstract class AbstractDsAssignmentStrategy {
final JpaAction actionForTarget = new JpaAction(); final JpaAction actionForTarget = new JpaAction();
actionForTarget.setActionType(targetWithActionType.getActionType()); actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime()); actionForTarget.setForcedTime(targetWithActionType.getForceTime());
actionForTarget.setWeight(targetWithActionType.getWeight());
actionForTarget.setActive(true); actionForTarget.setActive(true);
actionForTarget.setTarget(target); actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set); actionForTarget.setDistributionSet(set);

View File

@@ -28,7 +28,6 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -101,20 +100,33 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
List<Action> findByTargetAndActiveOrderByIdAsc(JpaTarget target, boolean active); List<Action> findByTargetAndActiveOrderByIdAsc(JpaTarget target, boolean active);
/** /**
* Retrieves the oldest {@link Action} that is active and referring to the * Retrieves the active {@link Action}s with the highest weights that refer
* given {@link Target}. * to the given {@link Target}. If {@link Action}s have the same weight they
* are ordered ascending by ID (oldest ones first).
* *
* @param sort * @param pageable
* order * pageable
* @param controllerId * @param controllerId
* the target to find assigned actions * the target to find assigned actions
* @param active * @return the found {@link Action}s
* the action active flag
*
* @return the found {@link Action}
*/ */
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
Optional<Action> findFirstByTargetControllerIdAndActive(Sort sort, String controllerId, boolean active); Page<Action> findByTargetControllerIdAndActiveIsTrueAndWeightIsNotNullOrderByWeightDescIdAsc(Pageable pageable,
String controllerId);
/**
* Retrieves the active {@link Action}s with the lowest IDs (the oldest one)
* whose weight is null and that that refers to the given {@link Target}.
*
* @param pageable
* pageable
* @param controllerId
* the target to find assigned actions
* @return the found {@link Action}s
*/
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
Page<Action> findByTargetControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(Pageable pageable,
String controllerId);
/** /**
* Checks if an active action exists for given * Checks if an active action exists for given

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2019 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.jpa;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.model.Action;
import org.springframework.data.domain.PageRequest;
/**
* Implements utility methods for managing {@link Action}s
*/
public class JpaActionManagement {
protected final ActionRepository actionRepository;
protected final RepositoryProperties repositoryProperties;
protected JpaActionManagement(final ActionRepository actionRepository,
final RepositoryProperties repositoryProperties) {
this.actionRepository = actionRepository;
this.repositoryProperties = repositoryProperties;
}
protected List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
final int maxActionCount) {
if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
return Collections.emptyList();
}
final List<Action> actions = new ArrayList<>();
final PageRequest pageable = PageRequest.of(0, maxActionCount);
actions.addAll(actionRepository
.findByTargetControllerIdAndActiveIsTrueAndWeightIsNotNullOrderByWeightDescIdAsc(pageable, controllerId)
.getContent());
actions.addAll(actionRepository
.findByTargetControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(pageable, controllerId)
.getContent());
final Comparator<Action> actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault)
.reversed().thenComparing(Action::getId);
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
}
protected int getWeightConsideringDefault(final Action action) {
return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent());
}
}

View File

@@ -110,7 +110,7 @@ import com.google.common.collect.Sets;
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
public class JpaControllerManagement implements ControllerManagement { public class JpaControllerManagement extends JpaActionManagement implements ControllerManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaControllerManagement.class); private static final Logger LOG = LoggerFactory.getLogger(JpaControllerManagement.class);
private final BlockingDeque<TargetPoll> queue; private final BlockingDeque<TargetPoll> queue;
@@ -118,9 +118,6 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@Autowired
private ActionRepository actionRepository;
@Autowired @Autowired
private TargetRepository targetRepository; private TargetRepository targetRepository;
@@ -157,10 +154,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired @Autowired
private TenantAware tenantAware; private TenantAware tenantAware;
private final RepositoryProperties repositoryProperties;
JpaControllerManagement(final ScheduledExecutorService executorService, JpaControllerManagement(final ScheduledExecutorService executorService,
final RepositoryProperties repositoryProperties) { final RepositoryProperties repositoryProperties, final ActionRepository actionRepository) {
super(actionRepository, repositoryProperties);
if (!repositoryProperties.isEagerPollPersistence()) { if (!repositoryProperties.isEagerPollPersistence()) {
executorService.scheduleWithFixedDelay(this::flushUpdateQueue, executorService.scheduleWithFixedDelay(this::flushUpdateQueue,
@@ -171,8 +167,6 @@ public class JpaControllerManagement implements ControllerManagement {
} else { } else {
queue = null; queue = null;
} }
this.repositoryProperties = repositoryProperties;
} }
@Override @Override
@@ -346,23 +340,19 @@ public class JpaControllerManagement implements ControllerManagement {
} }
@Override @Override
public Optional<Action> findOldestActiveActionByTarget(final String controllerId) { public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) {
if (!actionRepository.activeActionExistsForControllerId(controllerId)) { return findActiveActionsWithHighestWeight(controllerId, 1).stream().findFirst();
return Optional.empty();
}
// used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to
// DATAJPA-841 issue.
return actionRepository.findFirstByTargetControllerIdAndActive(new Sort(Direction.ASC, "id"), controllerId,
true);
} }
@Override @Override
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final String controllerId) { public List<Action> findActiveActionsWithHighestWeight(final String controllerId,
if (!actionRepository.activeActionExistsForControllerId(controllerId)) { final int maxActionCount) {
return Page.empty(); return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
} }
return actionRepository.findByActiveAndTarget(pageable, controllerId, true);
@Override
public int getWeightConsideringDefault(final Action action) {
return super.getWeightConsideringDefault(action);
} }
@Override @Override
@@ -376,7 +366,7 @@ public class JpaControllerManagement implements ControllerManagement {
} }
@Override @Override
public void deleteExistingTarget(@NotEmpty String controllerId) { public void deleteExistingTarget(@NotEmpty final String controllerId) {
final Target target = targetRepository.findByControllerId(controllerId) final Target target = targetRepository.findByControllerId(controllerId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
targetRepository.deleteById(target.getId()); targetRepository.deleteById(target.getId());

View File

@@ -8,7 +8,6 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
import java.io.Serializable; import java.io.Serializable;
@@ -38,6 +37,7 @@ import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
@@ -57,6 +57,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -103,7 +105,7 @@ import com.google.common.collect.Lists;
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
public class JpaDeploymentManagement implements DeploymentManagement { public class JpaDeploymentManagement extends JpaActionManagement implements DeploymentManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class); private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class);
@@ -124,7 +126,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
} }
private final EntityManager entityManager; private final EntityManager entityManager;
private final ActionRepository actionRepository;
private final DistributionSetRepository distributionSetRepository; private final DistributionSetRepository distributionSetRepository;
private final TargetRepository targetRepository; private final TargetRepository targetRepository;
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
@@ -146,9 +147,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database) { final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database,
final RepositoryProperties repositoryProperties) {
super(actionRepository, repositoryProperties);
this.entityManager = entityManager; this.entityManager = entityManager;
this.actionRepository = actionRepository;
this.distributionSetRepository = distributionSetRepository; this.distributionSetRepository = distributionSetRepository;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.actionStatusRepository = actionStatusRepository; this.actionStatusRepository = actionStatusRepository;
@@ -194,6 +196,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets( public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests, final String actionMessage) { final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.validate(deploymentRequests);
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy); return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
} }
@@ -275,8 +279,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AbstractDsAssignmentStrategy assignmentStrategy) { final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID); final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID);
final List<String> targetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId).distinct() final List<String> targetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.collect(Collectors.toList()); .distinct().collect(Collectors.toList());
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(targetIds, final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(targetIds,
distributionSetEntity.getId()); distributionSetEntity.getId());
@@ -285,8 +289,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return allTargetsAlreadyAssignedResult(distributionSetEntity, targetsWithActionType.size()); return allTargetsAlreadyAssignedResult(distributionSetEntity, targetsWithActionType.size());
} }
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(targetsWithActionType, final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(targetsWithActionType, actionMessage,
actionMessage, assignmentStrategy, distributionSetEntity, targetEntities); assignmentStrategy, distributionSetEntity, targetEntities);
return buildAssignmentResult(distributionSetEntity, assignedActions, targetsWithActionType.size()); return buildAssignmentResult(distributionSetEntity, assignedActions, targetsWithActionType.size());
} }
@@ -659,6 +663,16 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return actionRepository.findByActiveAndTarget(pageable, controllerId, false); return actionRepository.findByActiveAndTarget(pageable, controllerId, false);
} }
@Override
public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) {
return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
}
@Override
public int getWeightConsideringDefault(final Action action) {
return super.getWeightConsideringDefault(action);
}
@Override @Override
public long countActionsByTarget(final String controllerId) { public long countActionsByTarget(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId); throwExceptionIfTargetDoesNotExist(controllerId);
@@ -823,7 +837,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
} }
private boolean isMultiAssignmentsEnabled() { private boolean isMultiAssignmentsEnabled() {
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class); return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isMultiAssignmentsEnabled();
} }
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) { private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate; import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
@@ -43,6 +44,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEve
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.NoWeightProvidedInMultiAssignmentModeException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -57,6 +60,8 @@ import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -74,6 +79,7 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -165,9 +171,12 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy) { final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context, super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy); virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy,
tenantConfigurationManagement, systemSecurityContext);
this.eventPublisherHolder = eventPublisherHolder; this.eventPublisherHolder = eventPublisherHolder;
this.database = database; this.database = database;
} }
@@ -226,7 +235,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
} }
private JpaRollout createRollout(final JpaRollout rollout) { private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final Long totalTargets = targetManagement.countByRsql(rollout.getTargetFilterQuery()); final Long totalTargets = targetManagement.countByRsql(rollout.getTargetFilterQuery());
if (totalTargets == 0) { if (totalTargets == 0) {
throw new ValidationException("Rollout does not match any existing targets"); throw new ValidationException("Rollout does not match any existing targets");
@@ -618,6 +627,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
action.setStatus(Status.SCHEDULED); action.setStatus(Status.SCHEDULED);
action.setRollout(rollout); action.setRollout(rollout);
action.setRolloutGroup(rolloutGroup); action.setRolloutGroup(rolloutGroup);
rollout.getWeight().ifPresent(action::setWeight);
actionRepository.save(action); actionRepository.save(action);
}); });
} }
@@ -1008,6 +1018,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
update.getDescription().ifPresent(rollout::setDescription); update.getDescription().ifPresent(rollout::setDescription);
update.getActionType().ifPresent(rollout::setActionType); update.getActionType().ifPresent(rollout::setActionType);
update.getForcedTime().ifPresent(rollout::setForcedTime); update.getForcedTime().ifPresent(rollout::setForcedTime);
update.getWeight().ifPresent(rollout::setWeight);
update.getStartAt().ifPresent(rollout::setStartAt); update.getStartAt().ifPresent(rollout::setStartAt);
update.getSet().ifPresent(setId -> { update.getSet().ifPresent(setId -> {
final DistributionSet set = distributionSetManagement.get(setId) final DistributionSet set = distributionSetManagement.get(setId)
@@ -1136,5 +1147,4 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId); actionRepository::countByTargetId);
} }
} }

View File

@@ -18,6 +18,8 @@ import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
@@ -32,11 +34,14 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
@@ -67,19 +72,24 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private final Database database; private final Database database;
JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository, JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final Database database) { final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
this.database = database; this.database = database;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
} }
@Override @Override
@@ -92,6 +102,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
// enforce the 'max targets per auto assign' quota right here even if // enforce the 'max targets per auto assign' quota right here even if
// the result of the filter query can vary over time // the result of the filter query can vary over time
if (create.getAutoAssignDistributionSetId().isPresent()) { if (create.getAutoAssignDistributionSetId().isPresent()) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(create);
create.getQuery().ifPresent(this::assertMaxTargetsQuota); create.getQuery().ifPresent(this::assertMaxTargetsQuota);
} }
@@ -222,30 +233,26 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
@Override @Override
@Transactional @Transactional
public TargetFilterQuery updateAutoAssignDSWithActionType(final long queryId, final Long dsId, public TargetFilterQuery updateAutoAssignDS(final AutoAssignDistributionSetUpdate update) {
final ActionType actionType) { final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(
final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(queryId); update.getTargetFilterId());
if (update.getDsId() == null) {
if (dsId == null) {
targetFilterQuery.setAutoAssignDistributionSet(null); targetFilterQuery.setAutoAssignDistributionSet(null);
targetFilterQuery.setAutoAssignActionType(null); targetFilterQuery.setAutoAssignActionType(null);
targetFilterQuery.setAutoAssignWeight(null);
} else { } else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
// we cannot be sure that the quota was enforced at creation time // we cannot be sure that the quota was enforced at creation time
// because the Target Filter Query REST API does not allow to // because the Target Filter Query REST API does not allow to
// specify an // specify an
// auto-assign distribution set when creating a target filter query // auto-assign distribution set when creating a target filter query
assertMaxTargetsQuota(targetFilterQuery.getQuery()); assertMaxTargetsQuota(targetFilterQuery.getQuery());
final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId());
final JpaDistributionSet distributionSetToAutoAssign = findDistributionSetAndThrowExceptionIfNotFound(dsId); verifyDistributionSetAndThrowExceptionIfNotValid(ds);
// must be completed and not soft deleted targetFilterQuery.setAutoAssignDistributionSet(ds);
verifyDistributionSetAndThrowExceptionIfNotValid(distributionSetToAutoAssign); targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
targetFilterQuery.setAutoAssignWeight(update.getWeight());
targetFilterQuery.setAutoAssignDistributionSet(distributionSetToAutoAssign);
// the action type is set to FORCED per default (when not explicitly
// specified)
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(actionType));
} }
return targetFilterQueryRepository.save(targetFilterQuery); return targetFilterQueryRepository.save(targetFilterQuery);
} }
@@ -288,5 +295,4 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetRepository.count(RSQLUtility.parse(query, TargetFields.class, virtualPropertyReplacer, database)), targetRepository.count(RSQLUtility.parse(query, TargetFields.class, virtualPropertyReplacer, database)),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class); quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
} }
} }

View File

@@ -541,9 +541,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetRepository targetRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final TargetRepository targetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final JpaProperties properties) { final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository, return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository,
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase()); virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(),
tenantConfigurationManagement, systemSecurityContext);
} }
/** /**
@@ -620,10 +622,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy) { final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement, return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager, distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager,
tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy); tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy,
tenantConfigurationManagement, systemSecurityContext);
} }
/** /**
@@ -671,11 +676,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final PlatformTransactionManager txManager, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
final JpaProperties properties) { final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository, return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer, actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer,
txManager, tenantConfigurationManagement, quotaManagement, systemSecurityContext, tenantAware, txManager, tenantConfigurationManagement, quotaManagement, systemSecurityContext, tenantAware,
properties.getDatabase()); properties.getDatabase(), repositoryProperties);
} }
/** /**
@@ -686,8 +691,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
ControllerManagement controllerManagement(final ScheduledExecutorService executorService, ControllerManagement controllerManagement(final ScheduledExecutorService executorService,
final RepositoryProperties repositoryProperties) { final RepositoryProperties repositoryProperties, final ActionRepository actionRepository) {
return new JpaControllerManagement(executorService, repositoryProperties); return new JpaControllerManagement(executorService, repositoryProperties, actionRepository);
} }
@Bean @Bean

View File

@@ -97,6 +97,8 @@ public class AutoAssignChecker {
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest); final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest);
// we should ensure that the filter queries are executed
// in the order of weights
for (final TargetFilterQuery filterQuery : filterQueries) { for (final TargetFilterQuery filterQuery : filterQueries) {
checkByTargetFilterQueryAndAssignDS(filterQuery); checkByTargetFilterQueryAndAssignDS(filterQuery);
} }
@@ -143,7 +145,8 @@ public class AutoAssignChecker {
return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets", return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets",
Isolation.READ_COMMITTED.value(), status -> { Isolation.READ_COMMITTED.value(), status -> {
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests( final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(
targetFilterQuery.getQuery(), dsId, targetFilterQuery.getAutoAssignActionType(), PAGE_SIZE); targetFilterQuery.getQuery(), dsId, targetFilterQuery.getAutoAssignActionType(),
targetFilterQuery.getAutoAssignWeight().orElse(null), PAGE_SIZE);
final int count = deploymentRequests.size(); final int count = deploymentRequests.size();
if (count > 0) { if (count > 0) {
deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage); deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage);
@@ -168,17 +171,15 @@ public class AutoAssignChecker {
* @return list of targets with action type * @return list of targets with action type
*/ */
private List<DeploymentRequest> createAssignmentRequests(final String targetFilterQuery, final Long dsId, private List<DeploymentRequest> createAssignmentRequests(final String targetFilterQuery, final Long dsId,
final ActionType type, final int count) { final ActionType type, final Integer weight, final int count) {
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNonDS(PageRequest.of(0, count), dsId, final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNonDS(PageRequest.of(0, count), dsId,
targetFilterQuery); targetFilterQuery);
// the action type is set to FORCED per default (when not explicitly // the action type is set to FORCED per default (when not explicitly
// specified) // specified)
final ActionType autoAssignActionType = type == null ? ActionType.FORCED : type; final ActionType autoAssignActionType = type == null ? ActionType.FORCED : type;
return targets.getContent().stream() return targets.getContent().stream().map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
.map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId) .setActionType(autoAssignActionType).setWeight(weight).build()).collect(Collectors.toList());
.setActionType(autoAssignActionType).build())
.collect(Collectors.toList());
} }
} }

View File

@@ -31,6 +31,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
rollout.setDistributionSet(findDistributionSetAndThrowExceptionIfNotFound(set)); rollout.setDistributionSet(findDistributionSetAndThrowExceptionIfNotFound(set));
rollout.setTargetFilterQuery(targetFilterQuery); rollout.setTargetFilterQuery(targetFilterQuery);
rollout.setStartAt(startAt); rollout.setStartAt(startAt);
rollout.setWeight(weight);
if (actionType != null) { if (actionType != null) {
rollout.setActionType(actionType); rollout.setActionType(actionType);

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa.builder; package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
@@ -36,4 +37,9 @@ public class JpaTargetFilterQueryBuilder implements TargetFilterQueryBuilder {
return new JpaTargetFilterQueryCreate(distributionSetManagement); return new JpaTargetFilterQueryCreate(distributionSetManagement);
} }
@Override
public AutoAssignDistributionSetUpdate updateAutoAssign(final long id) {
return new AutoAssignDistributionSetUpdate(id);
}
} }

View File

@@ -36,7 +36,8 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC
return new JpaTargetFilterQuery(name, query, return new JpaTargetFilterQuery(name, query,
getAutoAssignDistributionSetId().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null), getAutoAssignDistributionSetId().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null),
getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null)); getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null),
weight);
} }
private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) { private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {

View File

@@ -28,6 +28,8 @@ import javax.persistence.NamedEntityGraphs;
import javax.persistence.NamedSubgraph; import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper; import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
@@ -89,6 +91,11 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "forced_time") @Column(name = "forced_time")
private long forcedTime; private long forcedTime;
@Column(name = "weight")
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private Integer weight;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@ObjectTypeConverter(name = "status", objectType = Action.Status.class, dataType = Integer.class, conversionValues = { @ObjectTypeConverter(name = "status", objectType = Action.Status.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FINISHED", dataValue = "0"), @ConversionValue(objectValue = "FINISHED", dataValue = "0"),
@@ -192,6 +199,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.forcedTime = forcedTime; this.forcedTime = forcedTime;
} }
@Override
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
@Override @Override
public RolloutGroup getRolloutGroup() { public RolloutGroup getRolloutGroup() {
return rolloutGroup; return rolloutGroup;
@@ -213,8 +229,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Override @Override
public String toString() { public String toString() {
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id=" return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id="
+ getId() + ", actionType=" + getActionType() + ", isActive=" + isActive() + ", createdAt=" + getId() + ", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive()
+ getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + "]"; + ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + "]";
} }
@Override @Override
@@ -337,7 +353,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
} }
@Override @Override
public void setExternalRef(String externalRef) { public void setExternalRef(final String externalRef) {
this.externalRef = externalRef; this.externalRef = externalRef;
} }

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.persistence.Column; import javax.persistence.Column;
@@ -23,6 +24,8 @@ import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient; import javax.persistence.Transient;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
@@ -90,7 +93,7 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@ConversionValue(objectValue = "DELETING", dataValue = "9"), @ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10"), @ConversionValue(objectValue = "DELETED", dataValue = "10"),
@ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"), @ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"),
@ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12")}) @ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12") })
@Convert("rolloutstatus") @Convert("rolloutstatus")
@NotNull @NotNull
private RolloutStatus status = RolloutStatus.CREATING; private RolloutStatus status = RolloutStatus.CREATING;
@@ -131,6 +134,11 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Size(max = Rollout.APPROVAL_REMARK_MAX_SIZE) @Size(max = Rollout.APPROVAL_REMARK_MAX_SIZE)
private String approvalRemark; private String approvalRemark;
@Column(name = "weight")
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private Integer weight;
@Transient @Transient
private transient TotalTargetCountStatus totalTargetCountStatus; private transient TotalTargetCountStatus totalTargetCountStatus;
@@ -204,6 +212,15 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.forcedTime = forcedTime; this.forcedTime = forcedTime;
} }
@Override
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
@Override @Override
public long getTotalTargets() { public long getTotalTargets() {
return totalTargets; return totalTargets;
@@ -299,5 +316,4 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
public void setApprovalRemark(final String approvalRemark) { public void setApprovalRemark(final String approvalRemark) {
this.approvalRemark = approvalRemark; this.approvalRemark = approvalRemark;
} }
} }

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.model; package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Optional;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ConstraintMode; import javax.persistence.ConstraintMode;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -66,11 +68,15 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
@ObjectTypeConverter(name = "autoAssignActionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = { @ObjectTypeConverter(name = "autoAssignActionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FORCED", dataValue = "0"), @ConversionValue(objectValue = "FORCED", dataValue = "0"),
@ConversionValue(objectValue = "SOFT", dataValue = "1"), @ConversionValue(objectValue = "SOFT", dataValue = "1"),
// Conversion for 'TIMEFORCED' is disabled because it is not permitted in autoAssignment // Conversion for 'TIMEFORCED' is disabled because it is not
@ConversionValue(objectValue = "DOWNLOAD_ONLY", dataValue = "3")}) // permitted in autoAssignment
@ConversionValue(objectValue = "DOWNLOAD_ONLY", dataValue = "3") })
@Convert("autoAssignActionType") @Convert("autoAssignActionType")
private ActionType autoAssignActionType; private ActionType autoAssignActionType;
@Column(name = "auto_assign_weight", nullable = true)
private Integer autoAssignWeight;
public JpaTargetFilterQuery() { public JpaTargetFilterQuery() {
// Default constructor for JPA. // Default constructor for JPA.
} }
@@ -86,13 +92,16 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
* of the {@link TargetFilterQuery}. * of the {@link TargetFilterQuery}.
* @param autoAssignActionType * @param autoAssignActionType
* of the {@link TargetFilterQuery}. * of the {@link TargetFilterQuery}.
* @param autoAssignWeight
* of the {@link TargetFilterQuery}.
*/ */
public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet, public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet,
final ActionType autoAssignActionType) { final ActionType autoAssignActionType, final Integer autoAssignWeight) {
this.name = name; this.name = name;
this.query = query; this.query = query;
this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet; this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet;
this.autoAssignActionType = autoAssignActionType; this.autoAssignActionType = autoAssignActionType;
this.autoAssignWeight = autoAssignWeight;
} }
@Override @Override
@@ -131,6 +140,15 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
this.autoAssignActionType = actionType; this.autoAssignActionType = actionType;
} }
@Override
public Optional<Integer> getAutoAssignWeight() {
return Optional.ofNullable(autoAssignWeight);
}
public void setAutoAssignWeight(final Integer weight) {
this.autoAssignWeight = weight;
}
@Override @Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) { public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent( EventPublisherHolder.getInstance().getEventPublisher().publishEvent(

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2019 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.jpa.utils;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
/**
* A collection of static helper methods for the tenant configuration
*/
public final class TenantConfigHelper {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private TenantConfigHelper(final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
}
/**
* Setting the context of the tenant.
*
* @param systemSecurityContext
* Security context used to get the tenant and for execution
* @param tenantConfigurationManagement
* to get the value from
* @return is active
*/
public static TenantConfigHelper usingContext(final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
return new TenantConfigHelper(systemSecurityContext, tenantConfigurationManagement);
}
/**
* Is multi-assignments enabled for the current tenant
*
* @return is active
*/
public boolean isMultiAssignmentsEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
}
}

View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2019 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.jpa.utils;
import java.util.List;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.NoWeightProvidedInMultiAssignmentModeException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.security.SystemSecurityContext;
/**
* Utility class to handle weight validation in Rollout, Auto Assignments, and
* Online Assignment.
*/
public final class WeightValidationHelper {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private WeightValidationHelper(final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
}
/**
* Setting the context of the tenant
*
* @param systemSecurityContext
* security context used to get the tenant and for execution
* @param tenantConfigurationManagement
* to get the value from
*/
public static WeightValidationHelper usingContext(final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
return new WeightValidationHelper(systemSecurityContext, tenantConfigurationManagement);
}
/**
* Validating weights associated with all the {@link DeploymentRequest}s
*
* @param deploymentRequests
* the {@linkplain List} of {@link DeploymentRequest}s
*/
public void validate(final List<DeploymentRequest> deploymentRequests) {
final long assignmentsWithWeight = deploymentRequests.stream()
.filter(request -> request.getTargetWithActionType().getWeight() != null).count();
final boolean containsAssignmentWithWeight = assignmentsWithWeight > 0;
final boolean containsAssignmentWithoutWeight = assignmentsWithWeight < deploymentRequests.size();
validateWeight(containsAssignmentWithWeight, containsAssignmentWithoutWeight);
}
/**
* Validating weight associated with the {@link Rollout}
*
* @param rollout
* the {@linkplain Rollout}
*/
public void validate(final Rollout rollout) {
validateWeight(rollout.getWeight().orElse(null));
}
/**
* Validating weight associated with the target filter query
*
* @param targetFilterQueryCreate
* the target filter query
*/
public void validate(final JpaTargetFilterQueryCreate targetFilterQueryCreate) {
validateWeight(targetFilterQueryCreate.getAutoAssignWeight().orElse(null));
}
/**
* Validating weight associated with the auto assignment
*
* @param autoAssignDistributionSetUpdate
* the auto assignment distribution set update
*/
public void validate(final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate) {
validateWeight(autoAssignDistributionSetUpdate.getWeight());
}
/**
* Checks if the weight is valid
*
* @param weight
* weight tied to the rollout, auto assignment, or online
* assignment.
*/
public void validateWeight(final Integer weight) {
final boolean hasWeight = weight != null;
validateWeight(hasWeight, !hasWeight);
}
/**
* Checks if the weight is valid with the multi-assignments being turned
* off/on.
*
* @param hasWeight
* indicator of the weight if it has numerical value
* @param hasNoWeight
* indicator of the weight if it doesn't have a numerical value
*/
public void validateWeight(final boolean hasWeight, final boolean hasNoWeight) {
// remove bypassing the weight enforcement as soon as weight can be set
// via UI
final boolean bypassWeightEnforcement = true;
final boolean multiAssignmentsEnabled = TenantConfigHelper
.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
if (!multiAssignmentsEnabled && hasWeight) {
throw new MultiAssignmentIsNotEnabledException();
} else if (bypassWeightEnforcement) {
return;
} else if (multiAssignmentsEnabled && hasNoWeight) {
throw new NoWeightProvidedInMultiAssignmentModeException();
}
}
}

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_action ADD weight INT;
ALTER TABLE sp_rollout ADD weight INT;
ALTER TABLE sp_target_filter_query ADD auto_assign_weight INT;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_action ADD weight INT;
ALTER TABLE sp_rollout ADD weight INT;
ALTER TABLE sp_target_filter_query ADD auto_assign_weight INT;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_action ADD weight INT;
ALTER TABLE sp_rollout ADD weight INT;
ALTER TABLE sp_target_filter_query ADD auto_assign_weight INT;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_action ADD weight INT;
ALTER TABLE sp_rollout ADD weight INT;
ALTER TABLE sp_target_filter_query ADD auto_assign_weight INT;

View File

@@ -105,5 +105,4 @@ public class RemoteIdEventTest extends AbstractRemoteEventTest {
// gets added because events inherit from of java.util.EventObject // gets added because events inherit from of java.util.EventObject
assertThat(underTestCreatedEvent).isEqualToIgnoringGivenFields(event, "source"); assertThat(underTestCreatedEvent).isEqualToIgnoringGivenFields(event, "source");
} }
} }

View File

@@ -98,5 +98,4 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
assertThat(actionProperties).isEqualToComparingFieldByField(new ActionProperties(action)); assertThat(actionProperties).isEqualToComparingFieldByField(new ActionProperties(action));
assertThat(underTest.getDistributionSetId()).isEqualTo(action.getDistributionSet().getId()); assertThat(underTest.getDistributionSetId()).isEqualTo(action.getDistributionSet().getId());
} }
} }

View File

@@ -104,7 +104,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(controllerManagement.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), assertThat(controllerManagement.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(),
module.getId())).isNotPresent(); module.getId())).isNotPresent();
assertThat(controllerManagement.findOldestActiveActionByTarget(NOT_EXIST_ID)).isNotPresent(); assertThat(controllerManagement.findActiveActionWithHighestWeight(NOT_EXIST_ID)).isNotPresent();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse(); assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse(); assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse();
@@ -457,7 +457,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.getContent(); .getContent();
assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus); assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus);
if (actionActive) { if (actionActive) {
assertThat(controllerManagement.findOldestActiveActionByTarget(controllerId).get().getId()) assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId())
.isEqualTo(actionId); .isEqualTo(actionId);
} }
} }
@@ -501,7 +501,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Register a controller which does not exist") @Description("Register a controller which does not exist")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 2)}) @Expect(type = TargetPollEvent.class, count = 2) })
public void findOrRegisterTargetIfItDoesNotExist() { public void findOrRegisterTargetIfItDoesNotExist() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull(); assertThat(target).as("target should not be null").isNotNull();
@@ -560,8 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST)); .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any()); verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any());
} } finally {
finally {
// revert // revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository); ((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
} }
@@ -597,22 +596,23 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Register a controller which does not exist, then update the controller twice, first time by providing a name property and second time without a new name") @Description("Register a controller which does not exist, then update the controller twice, first time by providing a name property and second time without a new name")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 3) , @Expect(type = TargetPollEvent.class, count = 3), @Expect(type = TargetUpdatedEvent.class, count = 1) })
@Expect(type = TargetUpdatedEvent.class, count = 1)})
public void findOrRegisterTargetIfItDoesNotExistDoesUpdateNameOnExistingTargetProperly() { public void findOrRegisterTargetIfItDoesNotExistDoesUpdateNameOnExistingTargetProperly() {
String controllerId = "12345"; final String controllerId = "12345";
String targetName = "UpdatedName"; final String targetName = "UpdatedName";
final Target newTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST); final Target newTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST);
assertThat(newTarget.getName()).isEqualTo(controllerId); assertThat(newTarget.getName()).isEqualTo(controllerId);
final Target firstTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId,
Target firstTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST, targetName); LOCALHOST, targetName);
assertThat(firstTimeUpdatedTarget.getName()).isEqualTo(targetName); assertThat(firstTimeUpdatedTarget.getName()).isEqualTo(targetName);
//Name should not change to default (name=targetId) if target is updated without new name provided // Name should not change to default (name=targetId) if target is
Target secondTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST); // updated without new name provided
final Target secondTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId,
LOCALHOST);
assertThat(secondTimeUpdatedTarget.getName()).isEqualTo(targetName); assertThat(secondTimeUpdatedTarget.getName()).isEqualTo(targetName);
} }
@@ -633,8 +633,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST)); .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any()); verify(mockTargetRepository, times(1)).findOne(any());
verify(mockTargetRepository, times(1)).save(any()); verify(mockTargetRepository, times(1)).save(any());
} } finally {
finally {
// revert // revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository); ((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
} }
@@ -651,13 +650,11 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class); when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class);
try { try {
assertThatExceptionOfType(RuntimeException.class) assertThatExceptionOfType(RuntimeException.class).as("Expected a RuntimeException to be thrown!")
.as("Expected a RuntimeException to be thrown!")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId", .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId",
LOCALHOST)); LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any()); verify(mockTargetRepository, times(1)).findOne(any());
} } finally {
finally {
// revert // revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository); ((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
} }
@@ -1054,9 +1051,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
Collections.singletonMap(keyValid, valueTooLong), null)); Collections.singletonMap(keyValid, valueTooLong), null));
assertThatExceptionOfType(InvalidTargetAttributeException.class) assertThatExceptionOfType(InvalidTargetAttributeException.class)
.as("Attribute with key NULL should not be created") .as("Attribute with key NULL should not be created").isThrownBy(() -> controllerManagement
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId, .updateControllerAttributes(controllerId, Collections.singletonMap(keyNull, valueValid), null));
Collections.singletonMap(keyNull, valueValid), null));
} }
@Test @Test
@@ -1298,9 +1294,9 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId).isNotNull(); assertThat(actionId).isNotNull();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates") .as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates").isThrownBy(
.isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement () -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement.addUpdateActionStatus(
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)))); entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates") .as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates")
@@ -1326,18 +1322,18 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId).isNotNull(); assertThat(actionId).isNotNull();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates") .as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates").isThrownBy(
.isThrownBy( ()-> IntStream.range(0, maxMessages).forEach(i -> controllerManagement () -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement.addUpdateActionStatus(
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)))); entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates") .as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates")
.isThrownBy(()->IntStream.range(0, maxMessages).forEach(i -> controllerManagement .isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates") .as("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates")
.isThrownBy(()->IntStream.range(0, maxMessages).forEach(i -> controllerManagement .isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))));
} }
@@ -1472,6 +1468,39 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertActionStatus(actionId, controllerId, TargetUpdateStatus.IN_SYNC, actionStatus, actionStatus, false); assertActionStatus(actionId, controllerId, TargetUpdateStatus.IN_SYNC, actionStatus, actionStatus, false);
} }
@Test
@Description("Actions are exposed according to thier weight in multi assignment mode.")
public void actionsAreExposedAccordingToTheirWeight() {
final String targetId = testdataFactory.createTarget().getControllerId();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long actionWeightNull = assignDistributionSet(ds.getId(), targetId).getAssignedEntity().get(0).getId();
enableMultiAssignments();
final Long actionWeight500old = assignDistributionSet(ds.getId(), targetId, 500).getAssignedEntity().get(0)
.getId();
final Long actionWeight500new = assignDistributionSet(ds.getId(), targetId, 500).getAssignedEntity().get(0)
.getId();
final Long actionWeight1000 = assignDistributionSet(ds.getId(), targetId, 1000).getAssignedEntity().get(0)
.getId();
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeightNull);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionWeightNull).status(Status.FINISHED));
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight1000);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionWeight1000).status(Status.FINISHED));
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight500old);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionWeight500old).status(Status.FINISHED));
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight500new);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionWeight500new).status(Status.FINISHED));
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId)).isEmpty();
}
private void assertAssignedDistributionSetId(final String controllerId, final Long dsId) { private void assertAssignedDistributionSetId(final String controllerId, final Long dsId) {
final Optional<Target> target = controllerManagement.getByControllerId(controllerId); final Optional<Target> target = controllerManagement.getByControllerId(controllerId);
assertThat(target).isPresent(); assertThat(target).isPresent();
@@ -1494,12 +1523,10 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false); assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false);
} }
@Test @Test
@Description("Delete a target on requested target deletion from client side") @Description("Delete a target on requested target deletion from client side")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetDeletedEvent.class, count = 1) })
@Expect(type = TargetDeletedEvent.class, count = 1)})
public void deleteTargetWithValidThingId() { public void deleteTargetWithValidThingId() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull(); assertThat(target).as("target should not be null").isNotNull();
@@ -1512,7 +1539,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Delete a target with a non existing thingId") @Description("Delete a target with a non existing thingId")
@ExpectEvents({@Expect(type = TargetDeletedEvent.class, count = 0)}) @ExpectEvents({ @Expect(type = TargetDeletedEvent.class, count = 0) })
public void deleteTargetWithInvalidThingId() { public void deleteTargetWithInvalidThingId() {
assertThatExceptionOfType(EntityNotFoundException.class) assertThatExceptionOfType(EntityNotFoundException.class)
.as("No EntityNotFoundException thrown when deleting a non-existing target") .as("No EntityNotFoundException thrown when deleting a non-existing target")
@@ -1522,9 +1549,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Delete a target after it has been deleted already") @Description("Delete a target after it has been deleted already")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetDeletedEvent.class, count = 1) })
@Expect(type = TargetDeletedEvent.class, count = 1)})
public void deleteTargetAfterItWasDeleted() { public void deleteTargetAfterItWasDeleted() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull(); assertThat(target).as("target should not be null").isNotNull();

View File

@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -171,7 +173,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds1, Collections.singletonList(testTarget))); .isThrownBy(() -> assignDistributionSet(ds1.getId(), testTarget.getControllerId(), 77));
} }
@Test @Test
@@ -563,16 +565,17 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public void previousAssignmentsAreNotCanceledInMultiAssignMode() { public void previousAssignmentsAreNotCanceledInMultiAssignMode() {
enableMultiAssignments(); enableMultiAssignments();
final List<Target> targets = testdataFactory.createTargets(10); final List<Target> targets = testdataFactory.createTargets(10);
final List<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toList());
// First assignment // First assignment
final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1"); final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1");
assignDistributionSet(ds1, targets); assignDistributionSet(ds1.getId(), targetIds, 77);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING); assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
// Second assignment // Second assignment
final DistributionSet ds2 = testdataFactory.createDistributionSet("Multi-assign-2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("Multi-assign-2");
assignDistributionSet(ds2, targets); assignDistributionSet(ds2.getId(), targetIds, 45);
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING); assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING); assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
@@ -601,7 +604,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public void multiassignmentInOneRequest() { public void multiassignmentInOneRequest() {
final List<Target> targets = testdataFactory.createTargets(2); final List<Target> targets = testdataFactory.createTargets(2);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2); final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets); final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
enableMultiAssignments(); enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement final List<DistributionSetAssignmentResult> results = deploymentManagement
@@ -614,7 +617,14 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); .stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds); assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
}); });
}
protected List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
final Collection<Target> targets, final int weight) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests.add(DeploymentManagement
.deploymentRequest(target.getControllerId(), ds.getId()).setWeight(weight).build())));
return deploymentRequests;
} }
@Test @Test
@@ -624,9 +634,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2); final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
final DeploymentRequest targetToDS0 = DeploymentManagement final DeploymentRequest targetToDS0 = DeploymentManagement
.deploymentRequest(target.getControllerId(), distributionSets.get(0).getId()).build(); .deploymentRequest(target.getControllerId(), distributionSets.get(0).getId()).setWeight(78).build();
final DeploymentRequest targetToDS1 = DeploymentManagement final DeploymentRequest targetToDS1 = DeploymentManagement
.deploymentRequest(target.getControllerId(), distributionSets.get(1).getId()).build(); .deploymentRequest(target.getControllerId(), distributionSets.get(1).getId()).setWeight(565).build();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class) Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1))); .isThrownBy(() -> deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)));
@@ -638,17 +649,26 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Duplicate Assignments are removed from a request when multiassignment is disabled, otherwise not") @Description("Duplicate Assignments are removed from a request when multiassignment is disabled, otherwise not")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 3), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = MultiActionEvent.class, count = 1) })
public void duplicateAssignmentsInRequestAreOnlyRemovedIfMultiassignmentDisabled() { public void duplicateAssignmentsInRequestAreOnlyRemovedIfMultiassignmentDisabled() {
final Target target = testdataFactory.createTarget(); final String targetId = testdataFactory.createTarget().getControllerId();
final DistributionSet ds = testdataFactory.createDistributionSet(); final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2, final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2,
DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).build()); DeploymentManagement.deploymentRequest(targetId, dsId).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments))) assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments)))
.isEqualTo(1); .isEqualTo(1);
enableMultiAssignments(); enableMultiAssignments();
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments))) final List<DeploymentRequest> twoEqualAssignmentsWithWeight = Collections.nCopies(2,
DeploymentManagement.deploymentRequest(targetId, dsId).setWeight(555).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignmentsWithWeight)))
.isEqualTo(2); .isEqualTo(2);
} }
@@ -668,7 +688,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> deploymentRequests = Collections.nCopies(maxActions + 1, final List<DeploymentRequest> deploymentRequests = Collections.nCopies(maxActions + 1,
DeploymentManagement.deploymentRequest(controllerId, dsId).build()); DeploymentManagement.deploymentRequest(controllerId, dsId).setWeight(24).build());
enableMultiAssignments(); enableMultiAssignments();
Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class) Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class)
@@ -676,6 +696,67 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.countByTargetControllerId(controllerId)).isEqualTo(0); assertThat(actionRepository.countByTargetControllerId(controllerId)).isEqualTo(0);
} }
@Test
@Description("An assignment request without a weight is ok when multi assignment in enabled.")
public void weightNotRequiredInMultiAssignmentMode() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest assignWithoutWeight = DeploymentManagement.deploymentRequest(targetId, dsId).build();
final DeploymentRequest assignWithWeight = DeploymentManagement.deploymentRequest(targetId, dsId).setWeight(567)
.build();
enableMultiAssignments();
deploymentManagement.assignDistributionSets(Arrays.asList(assignWithoutWeight, assignWithWeight));
}
@Test
@Description("An assignment request containing a weight causes an error when multi assignment in disabled.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest assignWithoutWeight = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(456).build();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class).isThrownBy(
() -> deploymentManagement.assignDistributionSets(Collections.singletonList(assignWithoutWeight)));
}
@Test
@Description("Weights are validated and contained in the resulting Action.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = MultiActionEvent.class, count = 2) })
public void weightValidatedAndSaved() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest valideRequest1 = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(Action.WEIGHT_MAX).build();
final DeploymentRequest valideRequest2 = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(Action.WEIGHT_MIN).build();
final DeploymentRequest weightTooLow = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(Action.WEIGHT_MIN - 1).build();
final DeploymentRequest weightTooHigh = DeploymentManagement.deploymentRequest(targetId, dsId)
.setWeight(Action.WEIGHT_MAX + 1).build();
enableMultiAssignments();
Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(Collections.singletonList(weightTooLow)));
Assertions.assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> deploymentManagement.assignDistributionSets(Collections.singletonList(weightTooHigh)));
final Long valideActionId1 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest1)).get(0)).getId();
final Long valideActionId2 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest2)).get(0)).getId();
assertThat(actionRepository.getById(valideActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX);
assertThat(actionRepository.getById(valideActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN);
}
/** /**
* test a simple deployment by calling the * test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} * {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
@@ -1143,13 +1224,15 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Tests the computation of already assigned entities returned as a result of an assignment") @Description("Tests the computation of already assigned entities returned as a result of an assignment")
public void testAlreadyAssignedAndAssignedActionsInAssignmentResult(){ public void testAlreadyAssignedAndAssignedActionsInAssignmentResult() {
// create target1, distributionSet, assign ds to target1 and finish update (close all actions) // create target1, distributionSet, assign ds to target1 and finish
// update (close all actions)
final Action action = prepareFinishedUpdate("target1", "ds", false); final Action action = prepareFinishedUpdate("target1", "ds", false);
final Target target2 = testdataFactory.createTarget("target2"); final Target target2 = testdataFactory.createTarget("target2");
final Target target3 = testdataFactory.createTarget("target3"); final Target target3 = testdataFactory.createTarget("target3");
// assign ds to target2, but don't finish update (actions should be still open) // assign ds to target2, but don't finish update (actions should be
// still open)
assignDistributionSet(action.getDistributionSet().getId(), target2.getControllerId()); assignDistributionSet(action.getDistributionSet().getId(), target2.getControllerId());
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet( final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(
@@ -1283,7 +1366,5 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public List<Long> getUndeployedTargetIDs() { public List<Long> getUndeployedTargetIDs() {
return undeployedTargetIDs; return undeployedTargetIDs;
} }
} }
} }

View File

@@ -17,12 +17,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException; import javax.validation.ValidationException;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutCreate;
@@ -42,6 +45,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -126,7 +130,6 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Action rolloutCreatedAction = actionsByKnownTarget.stream() final Action rolloutCreatedAction = actionsByKnownTarget.stream()
.filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get(); .filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get();
assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.FINISHED); assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.FINISHED);
} }
@Test @Test
@@ -599,7 +602,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final String successCondition = "50"; final String successCondition = "50";
final String errorCondition = "80"; final String errorCondition = "80";
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition, ActionType.DOWNLOAD_ONLY); amountOtherTargets, amountGroups, successCondition, errorCondition, ActionType.DOWNLOAD_ONLY, null);
// targets have not started // targets have not started
Map<TotalTargetCountStatus.Status, Long> validationMap = createInitStatusMap(); Map<TotalTargetCountStatus.Status, Long> validationMap = createInitStatusMap();
@@ -619,7 +622,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED); changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
// 4 targets are ready, 2 are finished(with DOWNLOADED action status) and 2 are running // 4 targets are ready, 2 are finished(with DOWNLOADED action status)
// and 2 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L);
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L);
@@ -628,7 +632,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED); changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
// 2 targets are ready, 4 are finished(with DOWNLOADED action status) and 2 are running // 2 targets are ready, 4 are finished(with DOWNLOADED action status)
// and 2 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L);
@@ -637,7 +642,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED); changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
// 0 targets are ready, 6 are finished(with DOWNLOADED action status) and 2 are running // 0 targets are ready, 6 are finished(with DOWNLOADED action status)
// and 2 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L);
validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
@@ -645,7 +651,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.FINISHED); changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
// 0 targets are ready, 6 are finished(with DOWNLOADED action status), 2 are finished and 0 are running // 0 targets are ready, 6 are finished(with DOWNLOADED action status), 2
// are finished and 0 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L);
validateRolloutActionStatus(createdRollout.getId(), validationMap); validateRolloutActionStatus(createdRollout.getId(), validationMap);
@@ -1745,14 +1752,80 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// verify that all running actions keep running // verify that all running actions keep running
assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.RUNNING) assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.RUNNING)
.getNumberOfElements()).isEqualTo(2); .getNumberOfElements()).isEqualTo(2);
}
@Test
@Description("Creating a rollout without weight value when multi assignment in enabled.")
public void weightNotRequiredInMultiAssignmentMode() {
enableMultiAssignments();
createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50", "80", ActionType.FORCED, null);
}
@Test
@Description("Creating a rollout with a weight causes an error when multi assignment in disabled.")
public void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50", "80",
ActionType.FORCED, 66));
}
@Test
@Description("Weight is validated and saved to the Rollout.")
public void weightValidatedAndSaved() {
final String targetPrefix = UUID.randomUUID().toString();
testdataFactory.createTargets(4, targetPrefix);
enableMultiAssignments();
Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> createTestRolloutWithTargetsAndDistributionSet(4, 2, "50", "80",
UUID.randomUUID().toString(), UUID.randomUUID().toString(), Action.WEIGHT_MAX + 1));
Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> createTestRolloutWithTargetsAndDistributionSet(4, 2, "50", "80",
UUID.randomUUID().toString(), UUID.randomUUID().toString(), Action.WEIGHT_MIN - 1));
final Rollout createdRollout1 = createTestRolloutWithTargetsAndDistributionSet(4, 2, "50", "80",
UUID.randomUUID().toString(), UUID.randomUUID().toString(), Action.WEIGHT_MAX);
final Rollout createdRollout2 = createTestRolloutWithTargetsAndDistributionSet(4, 2, "50", "80",
UUID.randomUUID().toString(), UUID.randomUUID().toString(), Action.WEIGHT_MIN);
assertThat(rolloutRepository.findById(createdRollout1.getId()).get().getWeight()).get()
.isEqualTo(Action.WEIGHT_MAX);
assertThat(rolloutRepository.findById(createdRollout2.getId()).get().getWeight()).get()
.isEqualTo(Action.WEIGHT_MIN);
}
@Test
@Description("A Rollout with weight creats actions with weights")
public void actionsWithWeightAreCreated() {
final int amountOfTargets = 5;
final int weight = 99;
enableMultiAssignments();
final Long rolloutId = createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2, amountOfTargets,
"80", "50", null, weight).getId();
rolloutManagement.start(rolloutId);
rolloutManagement.handleRollouts();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets);
assertThat(actions).allMatch(action -> action.getWeight().get() == weight);
}
@Test
@Description("Rollout can be created without weight in single assignment and be started in multi assignment")
public void createInSingleStartInMultiassigMode() {
final int amountOfTargets = 5;
final Long rolloutId = createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2, amountOfTargets,
"80", "50", null, null).getId();
enableMultiAssignments();
rolloutManagement.start(rolloutId);
rolloutManagement.handleRollouts();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets);
assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
} }
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage, private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
final String targetFilter) { final String targetFilter) {
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc") return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")
.targetPercentage(Float.valueOf(percentage)).targetFilterQuery(targetFilter); .targetPercentage(Float.valueOf(percentage)).targetFilterQuery(targetFilter);
} }
private RolloutCreate generateTargetsAndRollout(final String rolloutName, final int amountTargetsForRollout) { private RolloutCreate generateTargetsAndRollout(final String rolloutName, final int amountTargetsForRollout) {
@@ -1790,27 +1863,35 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final int amountOtherTargets, final int groupSize, final String successCondition, final int amountOtherTargets, final int groupSize, final String successCondition,
final String errorCondition) { final String errorCondition) {
return createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountOtherTargets, return createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountOtherTargets,
groupSize, successCondition, errorCondition, ActionType.FORCED); groupSize, successCondition, errorCondition, ActionType.FORCED, null);
} }
private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
final int amountOtherTargets, final int groupSize, final String successCondition, final int amountOtherTargets, final int groupSize, final String successCondition,
final String errorCondition, final ActionType actionType) { final String errorCondition, final ActionType actionType, final Integer weight) {
final DistributionSet rolloutDS = testdataFactory.createDistributionSet("rolloutDS"); final DistributionSet rolloutDS = testdataFactory.createDistributionSet("rolloutDS");
testdataFactory.createTargets(amountTargetsForRollout, "rollout-", "rollout"); testdataFactory.createTargets(amountTargetsForRollout, "rollout-", "rollout");
testdataFactory.createTargets(amountOtherTargets, "others-", "rollout"); testdataFactory.createTargets(amountOtherTargets, "others-", "rollout");
final String filterQuery = "controllerId==rollout-*"; final String filterQuery = "controllerId==rollout-*";
return testdataFactory.createRolloutByVariables("test-rollout-name-1", "test-rollout-description-1", groupSize, return testdataFactory.createRolloutByVariables("test-rollout-name-1", "test-rollout-description-1", groupSize,
filterQuery, rolloutDS, successCondition, errorCondition, actionType); filterQuery, rolloutDS, successCondition, errorCondition, actionType, weight);
} }
private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
final int groupSize, final String successCondition, final String errorCondition, final String rolloutName, final int groupSize, final String successCondition, final String errorCondition, final String rolloutName,
final String targetPrefixName) { final String targetPrefixName) {
return createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, groupSize, successCondition,
errorCondition, rolloutName, targetPrefixName, null);
}
private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
final int groupSize, final String successCondition, final String errorCondition, final String rolloutName,
final String targetPrefixName, final Integer weight) {
final DistributionSet dsForRolloutTwo = testdataFactory.createDistributionSet("dsFor" + rolloutName); final DistributionSet dsForRolloutTwo = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName); testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
return testdataFactory.createRolloutByVariables(rolloutName, rolloutName + "description", groupSize, return testdataFactory.createRolloutByVariables(rolloutName, rolloutName + "description", groupSize,
"controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition); "controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition,
Action.ActionType.FORCED, weight);
} }
private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) { private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {

View File

@@ -20,6 +20,9 @@ import static org.junit.Assert.fail;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
@@ -28,8 +31,10 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreat
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -82,13 +87,18 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetFilterQueryManagement.update(entityFactory.targetFilterQuery().update(NOT_EXIST_IDL)), () -> targetFilterQueryManagement.update(entityFactory.targetFilterQuery().update(NOT_EXIST_IDL)),
"TargetFilterQuery"); "TargetFilterQuery");
verifyThrownExceptionBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)),
"DistributionSet"); "DistributionSet");
verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS(NOT_EXIST_IDL, set.getId()),
"TargetFilterQuery");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), NOT_EXIST_IDL), () -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(NOT_EXIST_IDL).ds(set.getId())),
"TargetFilterQuery");
verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)),
"DistributionSet"); "DistributionSet");
} }
@@ -208,23 +218,26 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
@Step @Step
private void verifyAutoAssignmentWithDefaultActionType(final String filterName, private void verifyAutoAssignmentWithDefaultActionType(final String filterName,
final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) {
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()); targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()));
verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.FORCED); verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.FORCED);
} }
@Step @Step
private void verifyAutoAssignmentWithSoftActionType(final String filterName, private void verifyAutoAssignmentWithSoftActionType(final String filterName,
final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) {
targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), distributionSet.getId(), targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
ActionType.SOFT); .updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()).actionType(ActionType.SOFT));
verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.SOFT); verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.SOFT);
} }
@Step @Step
private void verifyAutoAssignmentWithDownloadOnlyActionType(final String filterName, private void verifyAutoAssignmentWithDownloadOnlyActionType(final String filterName,
final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) { final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) {
targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), distributionSet.getId(), targetFilterQueryManagement
ActionType.DOWNLOAD_ONLY); .updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId())
.ds(distributionSet.getId()).actionType(ActionType.DOWNLOAD_ONLY));
verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.DOWNLOAD_ONLY); verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.DOWNLOAD_ONLY);
} }
@@ -233,9 +246,11 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
final DistributionSet distributionSet) { final DistributionSet distributionSet) {
// assigning a distribution set with TIMEFORCED action is supposed to // assigning a distribution set with TIMEFORCED action is supposed to
// fail as only FORCED and SOFT action types are allowed // fail as only FORCED and SOFT action types are allowed
assertThatExceptionOfType(InvalidAutoAssignActionTypeException.class).isThrownBy(
() -> targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), assertThatExceptionOfType(InvalidAutoAssignActionTypeException.class)
distributionSet.getId(), ActionType.TIMEFORCED)); .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId())
.ds(distributionSet.getId()).actionType(ActionType.TIMEFORCED)));
} }
@Step @Step
@@ -245,8 +260,8 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
.type(testdataFactory.findOrCreateDefaultTestDsType())); .type(testdataFactory.findOrCreateDefaultTestDsType()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class) assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
incompleteDistributionSet.getId())); .updateAutoAssign(targetFilterQuery.getId()).ds(incompleteDistributionSet.getId())));
} }
@Step @Step
@@ -255,8 +270,9 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs")); assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs"));
distributionSetManagement.delete(softDeletedDs.getId()); distributionSetManagement.delete(softDeletedDs.getId());
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy( assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), softDeletedDs.getId())); .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(softDeletedDs.getId())));
} }
private void verifyAutoAssignDsAndActionType(final String filterName, final DistributionSet distributionSet, private void verifyAutoAssignDsAndActionType(final String filterName, final DistributionSet distributionSet,
@@ -282,8 +298,10 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
// assigning a distribution set is supposed to fail as the query // assigning a distribution set is supposed to fail as the query
// addresses too many targets // addresses too many targets
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId())); assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId())));
} }
@Test @Test
@@ -313,7 +331,8 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()); targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()));
// Check if target filter query is there // Check if target filter query is there
TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get(); TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get();
@@ -340,9 +359,11 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
// later step // later step
assignDistributionSet(distributionSet.getId(), target.getControllerId()); assignDistributionSet(distributionSet.getId(), target.getControllerId());
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement final Long filterId = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterName).query("name==PendingTargets001")) .create(entityFactory.targetFilterQuery().create().name(filterName).query("name==PendingTargets001"))
.getId(), distributionSet.getId()); .getId();
targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(distributionSet.getId()));
// Check if target filter query is there with the distribution set // Check if target filter query is there with the distribution set
TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get(); TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get();
@@ -372,20 +393,18 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
final DistributionSet distributionSet = testdataFactory.createDistributionSet(); final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet("2"); final DistributionSet distributionSet2 = testdataFactory.createDistributionSet("2");
final TargetFilterQuery tfq = targetFilterQueryManagement.updateAutoAssignDSWithActionType( final TargetFilterQuery tfq = targetFilterQueryManagement
targetFilterQueryManagement .create(entityFactory.targetFilterQuery().create().name("c").query("name==x")
.create(entityFactory.targetFilterQuery().create().name("c").query("name==x")).getId(), .autoAssignDistributionSet(distributionSet).autoAssignActionType(ActionType.SOFT));
distributionSet.getId(), ActionType.SOFT); final TargetFilterQuery tfq2 = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
final TargetFilterQuery tfq2 = targetFilterQueryManagement.updateAutoAssignDS( .name(filterName).query("name==z*").autoAssignDistributionSet(distributionSet2));
targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterName).query("name==z*")).getId(),
distributionSet2.getId());
assertEquals(4L, targetFilterQueryManagement.count()); assertEquals(4L, targetFilterQueryManagement.count());
// check if find works // check if find works
verifyFindByDistributionSetAndRsql(distributionSet, null, tfq); verifyFindByDistributionSetAndRsql(distributionSet, null, tfq);
targetFilterQueryManagement.updateAutoAssignDS(tfq2.getId(), distributionSet.getId()); targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(tfq2.getId()).ds(distributionSet.getId()));
// check if find works for two // check if find works for two
verifyFindByDistributionSetAndRsql(distributionSet, null, tfq, tfq2); verifyFindByDistributionSetAndRsql(distributionSet, null, tfq, tfq2);
@@ -421,4 +440,73 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
verifyExpectedFilterQueriesInList(tfqList, expectedFilterQueries); verifyExpectedFilterQueriesInList(tfqList, expectedFilterQueries);
} }
@Test
@Description("Creating or updating a target filter query with autoassignment and no-value weight when multi assignment in enabled.")
public void weightNotRequiredInMultiAssignmentMode() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")).getId();
targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("b").query("name==*").autoAssignDistributionSet(ds));
targetFilterQueryManagement
.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()));
}
@Test
@Description("Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled.")
public void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")).getId();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("b").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(342)));
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(343)));
}
@Test
@Description("Auto assignment can be removed from filter when multi assignment in enabled.")
public void removeDsFromFilterWhenMultiAssignmentModeNotEnabled() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a")
.query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(23)).getId();
targetFilterQueryManagement
.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(null).weight(null));
}
@Test
@Description("Weight is validated and saved to the Filter.")
public void weightValidatedAndSaved() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet();
Assertions.assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a")
.query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(Action.WEIGHT_MAX + 1)));
final Long filterId = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a")
.query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(Action.WEIGHT_MAX)).getId();
assertThat(targetFilterQueryManagement.get(filterId).get().getAutoAssignWeight().get())
.isEqualTo(Action.WEIGHT_MAX);
Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(filterId).ds(ds.getId()).weight(Action.WEIGHT_MAX + 1)));
Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(filterId).ds(ds.getId()).weight(Action.WEIGHT_MIN - 1)));
targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(Action.WEIGHT_MAX));
targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(Action.WEIGHT_MIN));
assertThat(targetFilterQueryManagement.get(filterId).get().getAutoAssignWeight().get())
.isEqualTo(Action.WEIGHT_MIN);
}
} }

View File

@@ -131,7 +131,8 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
// try to find several targets with different filter settings // try to find several targets with different filter settings
verifyThat1TargetHasNameAndId("targ-A-special", targSpecialName.getControllerId()); verifyThat1TargetHasNameAndId("targ-A-special", targSpecialName.getControllerId());
verifyThat1TargetHasAttributeValue("%c-attribute%", targAttribute.getControllerId()); verifyThat1TargetHasAttributeValue("%c-attribute%", targAttribute.getControllerId());
verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%", targAttributeId.getControllerId()); verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%",
targAttributeId.getControllerId());
verifyThatRepositoryContains400Targets(); verifyThatRepositoryContains400Targets();
verifyThat200TargetsHaveTagD(targTagW, concat(targBs, targCs)); verifyThat200TargetsHaveTagD(targTagW, concat(targBs, targCs));
verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(targTagY, targTagW, targBs); verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(targTagY, targTagW, targBs);

View File

@@ -63,8 +63,8 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches all targets // target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*"));
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), secondDistributionSet.getId()); targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(secondDistributionSet.getId()));
// Run the check // Run the check
autoAssignChecker.check(); autoAssignChecker.check();
@@ -101,7 +101,8 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches all targets // target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*"));
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), setA.getId()); targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(setA.getId()));
final String targetDsAIdPref = "targ"; final String targetDsAIdPref = "targ";
final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref, final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref,
@@ -150,18 +151,16 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches first bunch of targets, that should // target filter query that matches first bunch of targets, that should
// fail // fail
assertThatExceptionOfType( assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy(() -> {
InvalidAutoAssignDistributionSetException.class) final Long filterId = targetFilterQueryManagement.create(
.isThrownBy( entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*"))
() -> targetFilterQueryManagement.updateAutoAssignDS( .getId();
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create() targetFilterQueryManagement
.name("filterA").query("id==" + targetDsFIdPref + "*")).getId(), .updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(setF.getId()));
setF.getId())); });
// target filter query that matches failed bunch of targets // target filter query that matches failed bunch of targets
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement.create( targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterB")
entityFactory.targetFilterQuery().create().name("filterB").query("id==" + targetDsAIdPref + "*")) .query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA.getId()));
.getId(), setA.getId());
final List<Target> targetsF = testdataFactory.createTargets(10, targetDsFIdPref, final List<Target> targetsF = testdataFactory.createTargets(10, targetDsFIdPref,
targetDsFIdPref.concat(" description")); targetDsFIdPref.concat(" description"));
@@ -243,12 +242,10 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final List<Target> targets = testdataFactory.createTargets(targetCount, "target" + prefix, final List<Target> targets = testdataFactory.createTargets(targetCount, "target" + prefix,
prefix.concat(" description")); prefix.concat(" description"));
targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filter" + prefix).query("id==target" + prefix + "*")
.autoAssignDistributionSet(distributionSet).autoAssignActionType(actionType));
targetFilterQueryManagement
.updateAutoAssignDSWithActionType(
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("filter" + prefix).query("id==target" + prefix + "*")).getId(),
distributionSet.getId(), actionType);
return targets; return targets;
} }
@@ -262,4 +259,38 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
assertThat(actions).allMatch(action -> action.getActionType().equals(actionType)); assertThat(actions).allMatch(action -> action.getActionType().equals(actionType));
} }
@Test
@Description("An auto assignment target filter with weight creats actions with weights")
public void actionsWithWeightAreCreated() throws Exception {
final int amountOfTargets = 5;
final DistributionSet ds = testdataFactory.createDistributionSet();
final int weight = 32;
enableMultiAssignments();
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")
.autoAssignDistributionSet(ds).autoAssignWeight(weight));
testdataFactory.createTargets(amountOfTargets);
autoAssignChecker.check();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets);
assertThat(actions).allMatch(action -> action.getWeight().get() == weight);
}
@Test
@Description("An auto assignment target filter without weight still works after multi assignment is enabled")
public void filterWithoutWeightWorksInMultiAssignmentMode() throws Exception {
final int amountOfTargets = 5;
final DistributionSet ds = testdataFactory.createDistributionSet();
targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("a").query("name==*").autoAssignDistributionSet(ds));
enableMultiAssignments();
testdataFactory.createTargets(amountOfTargets);
autoAssignChecker.check();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions).hasSize(amountOfTargets);
assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
}
} }

View File

@@ -46,6 +46,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
action.setDistributionSet(dsA); action.setDistributionSet(dsA);
action.setTarget(target); action.setTarget(target);
action.setStatus(Status.RUNNING); action.setStatus(Status.RUNNING);
action.setWeight(45);
target.addAction(action); target.addAction(action);
actionRepository.save(action); actionRepository.save(action);
@@ -56,6 +57,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
newAction.setActive(i % 2 == 0); newAction.setActive(i % 2 == 0);
newAction.setStatus(Status.RUNNING); newAction.setStatus(Status.RUNNING);
newAction.setTarget(target); newAction.setTarget(target);
newAction.setWeight(45);
actionRepository.save(newAction); actionRepository.save(newAction);
target.addAction(newAction); target.addAction(newAction);
} }

View File

@@ -41,14 +41,10 @@ public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest
final DistributionSet ds1 = testdataFactory.createDistributionSet("AutoAssignedDs_1"); final DistributionSet ds1 = testdataFactory.createDistributionSet("AutoAssignedDs_1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("AutoAssignedDs_2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("AutoAssignedDs_2");
filter1 = targetFilterQueryManagement.updateAutoAssignDSWithActionType( filter1 = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterName1)
targetFilterQueryManagement .query("name==*").autoAssignDistributionSet(ds1).autoAssignActionType(ActionType.SOFT));
.create(entityFactory.targetFilterQuery().create().name(filterName1).query("name==*")).getId(), filter2 = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterName2)
ds1.getId(), ActionType.SOFT); .query("name==*").autoAssignDistributionSet(ds2));
filter2 = targetFilterQueryManagement.updateAutoAssignDS(
targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterName2).query("name==*")).getId(),
ds2.getId());
targetFilterQueryManagement targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterName3).query("name==*")); .create(entityFactory.targetFilterQuery().create().name(filterName3).query("name==*"));

View File

@@ -16,9 +16,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@@ -118,6 +116,8 @@ public abstract class AbstractIntegrationTest {
protected static final URI LOCALHOST = URI.create("http://127.0.0.1"); protected static final URI LOCALHOST = URI.create("http://127.0.0.1");
protected static final int DEFAULT_TEST_WEIGHT = 500;
/** /**
* Number of {@link DistributionSetType}s that exist in every test case. One * Number of {@link DistributionSetType}s that exist in every test case. One
* generated by using * generated by using
@@ -250,9 +250,14 @@ public abstract class AbstractIntegrationTest {
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds, protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds,
final ActionType actionType, final long forcedTime) { final ActionType actionType, final long forcedTime) {
return assignDistributionSet(dsID, controllerIds, actionType, forcedTime, null);
}
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds,
final ActionType actionType, final long forcedTime, final Integer weight) {
final List<DeploymentRequest> deploymentRequests = controllerIds.stream() final List<DeploymentRequest> deploymentRequests = controllerIds.stream()
.map(id -> DeploymentManagement.deploymentRequest(id, dsID).setActionType(actionType) .map(id -> DeploymentManagement.deploymentRequest(id, dsID).setActionType(actionType)
.setForceTime(forcedTime).build()) .setForceTime(forcedTime).setWeight(weight).build())
.collect(Collectors.toList()); .collect(Collectors.toList());
final List<DistributionSetAssignmentResult> results = deploymentManagement final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(deploymentRequests); .assignDistributionSets(deploymentRequests);
@@ -266,7 +271,13 @@ public abstract class AbstractIntegrationTest {
return assignDistributionSet(ds.getId(), targetIds, ActionType.FORCED); return assignDistributionSet(ds.getId(), targetIds, ActionType.FORCED);
} }
private DistributionSetAssignmentResult makeAssignment(final DeploymentRequest request) { protected DistributionSetAssignmentResult assignDistributionSet(final Long dsId, final List<String> targetIds,
final int weight) {
return assignDistributionSet(dsId, targetIds, ActionType.FORCED, RepositoryModelConstants.NO_FORCE_TIME,
weight);
}
protected DistributionSetAssignmentResult makeAssignment(final DeploymentRequest request) {
final List<DistributionSetAssignmentResult> results = deploymentManagement final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(Collections.singletonList(request)); .assignDistributionSets(Collections.singletonList(request));
assertThat(results).hasSize(1); assertThat(results).hasSize(1);
@@ -308,29 +319,15 @@ public abstract class AbstractIntegrationTest {
.build()); .build());
} }
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final long dsID,
final String controllerId, final ActionType type, final String maintenanceWindowSchedule,
final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
return makeAssignment(DeploymentManagement.deploymentRequest(controllerId, dsID).setActionType(type)
.setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)
.build());
}
protected List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
final Collection<Target> targets) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
distributionSets.forEach(ds -> targets.forEach(
target -> deploymentRequests
.add(DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).build()))
);
return deploymentRequests;
}
protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset, final Target target) { protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset, final Target target) {
return assignDistributionSet(pset, Arrays.asList(target)); return assignDistributionSet(pset, Arrays.asList(target));
} }
protected DistributionSetAssignmentResult assignDistributionSet(final long dsId, final String targetId,
final int weight) {
return assignDistributionSet(dsId, Collections.singletonList(targetId), weight);
}
protected void enableMultiAssignments() { protected void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true); tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
} }
@@ -477,12 +474,14 @@ public abstract class AbstractIntegrationTest {
return randomStringBuilder.toString(); return randomStringBuilder.toString();
} }
protected static Action getFirstAssignedAction(final DistributionSetAssignmentResult distributionSetAssignmentResult) { protected static Action getFirstAssignedAction(
final DistributionSetAssignmentResult distributionSetAssignmentResult) {
return distributionSetAssignmentResult.getAssignedEntity().stream().findFirst() return distributionSetAssignmentResult.getAssignedEntity().stream().findFirst()
.orElseThrow(() -> new IllegalStateException("expected one assigned action, found none")); .orElseThrow(() -> new IllegalStateException("expected one assigned action, found none"));
} }
protected static Long getFirstAssignedActionId(final DistributionSetAssignmentResult distributionSetAssignmentResult) { protected static Long getFirstAssignedActionId(
final DistributionSetAssignmentResult distributionSetAssignmentResult) {
return getFirstAssignedAction(distributionSetAssignmentResult).getId(); return getFirstAssignedAction(distributionSetAssignmentResult).getId();
} }

View File

@@ -19,6 +19,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@@ -117,8 +118,8 @@ public class TestdataFactory {
public static final String SM_TYPE_RT = "runtime"; public static final String SM_TYPE_RT = "runtime";
/** /**
* Key of test "application" {@link SoftwareModuleType} : optional software in * Key of test "application" {@link SoftwareModuleType} : optional software
* {@link #DS_TYPE_DEFAULT}. * in {@link #DS_TYPE_DEFAULT}.
*/ */
public static final String SM_TYPE_APP = "application"; public static final String SM_TYPE_APP = "application";
@@ -160,13 +161,14 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix) { public DistributionSet createDistributionSet(final String prefix) {
@@ -175,24 +177,25 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet() { public DistributionSet createDistributionSet() {
return createDistributionSet("", DEFAULT_VERSION, false); return createDistributionSet(UUID.randomUUID().toString(), DEFAULT_VERSION, false);
} }
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param modules * @param modules
* of {@link DistributionSet#getModules()} * of {@link DistributionSet#getModules()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules) { public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules) {
@@ -201,8 +204,8 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param modules * @param modules
@@ -210,6 +213,7 @@ public class TestdataFactory {
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules, final String prefix) { public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules, final String prefix) {
@@ -218,14 +222,15 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}. * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* @param isRequiredMigrationStep * @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final boolean isRequiredMigrationStep) { public DistributionSet createDistributionSet(final String prefix, final boolean isRequiredMigrationStep) {
@@ -234,8 +239,8 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param prefix * @param prefix
@@ -243,6 +248,7 @@ public class TestdataFactory {
* vendor and description. * vendor and description.
* @param tags * @param tags
* {@link DistributionSet#getTags()} * {@link DistributionSet#getTags()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final Collection<DistributionSetTag> tags) { public DistributionSet createDistributionSet(final String prefix, final Collection<DistributionSetTag> tags) {
@@ -251,17 +257,19 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP}. * , {@link #SM_TYPE_APP}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* @param version * @param version
* {@link DistributionSet#getVersion()} and * {@link DistributionSet#getVersion()} and
* {@link SoftwareModule#getVersion()} extended by a random number. * {@link SoftwareModule#getVersion()} extended by a random
* number.
* @param isRequiredMigrationStep * @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -319,11 +327,13 @@ public class TestdataFactory {
* vendor and description. * vendor and description.
* @param version * @param version
* {@link DistributionSet#getVersion()} and * {@link DistributionSet#getVersion()} and
* {@link SoftwareModule#getVersion()} extended by a random number. * {@link SoftwareModule#getVersion()} extended by a random
* number.
* @param isRequiredMigrationStep * @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
* @param modules * @param modules
* for {@link DistributionSet#getModules()} * for {@link DistributionSet#getModules()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -338,8 +348,8 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP}. * , {@link #SM_TYPE_APP}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
@@ -350,6 +360,7 @@ public class TestdataFactory {
* number.updat * number.updat
* @param tags * @param tags
* {@link DistributionSet#getTags()} * {@link DistributionSet#getTags()}
*
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -365,13 +376,14 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet}s in repository including three * Creates {@link DistributionSet}s in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
*
* @return {@link List} of {@link DistributionSet} entities * @return {@link List} of {@link DistributionSet} entities
*/ */
public List<DistributionSet> createDistributionSets(final int number) { public List<DistributionSet> createDistributionSets(final int number) {
@@ -380,7 +392,8 @@ public class TestdataFactory {
} }
/** /**
* Create a list of {@link DistributionSet}s without modules, i.e. incomplete. * Create a list of {@link DistributionSet}s without modules, i.e.
* incomplete.
* *
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
@@ -400,9 +413,9 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet}s in repository including three * Creates {@link DistributionSet}s in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* @param prefix * @param prefix
@@ -410,6 +423,7 @@ public class TestdataFactory {
* vendor and description. * vendor and description.
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
*
* @return {@link List} of {@link DistributionSet} entities * @return {@link List} of {@link DistributionSet} entities
*/ */
public List<DistributionSet> createDistributionSets(final String prefix, final int number) { public List<DistributionSet> createDistributionSets(final String prefix, final int number) {
@@ -431,6 +445,7 @@ public class TestdataFactory {
* {@link DistributionSet#getName()} * {@link DistributionSet#getName()}
* @param version * @param version
* {@link DistributionSet#getVersion()} * {@link DistributionSet#getVersion()}
*
* @return {@link DistributionSet} entity * @return {@link DistributionSet} entity
*/ */
public DistributionSet createDistributionSetWithNoSoftwareModules(final String name, final String version) { public DistributionSet createDistributionSetWithNoSoftwareModules(final String name, final String version) {
@@ -440,11 +455,12 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link Artifact}s for given {@link SoftwareModule} with a small text * Creates {@link Artifact}s for given {@link SoftwareModule} with a small
* payload. * text payload.
* *
* @param moduleId * @param moduleId
* the {@link Artifact}s belong to. * the {@link Artifact}s belong to.
*
* @return {@link Artifact} entity. * @return {@link Artifact} entity.
*/ */
public List<Artifact> createArtifacts(final Long moduleId) { public List<Artifact> createArtifacts(final Long moduleId) {
@@ -458,15 +474,18 @@ public class TestdataFactory {
} }
/** /**
* Create an {@link Artifact} for given {@link SoftwareModule} with a small text * Create an {@link Artifact} for given {@link SoftwareModule} with a small
* payload. * text payload.
* *
* @param artifactData * @param artifactData
* the {@link Artifact} Inputstream * the {@link Artifact} Inputstream
*
* @param moduleId * @param moduleId
* the {@link Artifact} belongs to * the {@link Artifact} belongs to
*
* @param filename * @param filename
* that was provided during upload. * that was provided during upload.
*
* @return {@link Artifact} entity. * @return {@link Artifact} entity.
*/ */
public Artifact createArtifact(final String artifactData, final Long moduleId, final String filename) { public Artifact createArtifact(final String artifactData, final Long moduleId, final String filename) {
@@ -476,17 +495,21 @@ public class TestdataFactory {
} }
/** /**
* Create an {@link Artifact} for given {@link SoftwareModule} with a small text * Create an {@link Artifact} for given {@link SoftwareModule} with a small
* payload. * text payload.
* *
* @param artifactData * @param artifactData
* the {@link Artifact} Inputstream * the {@link Artifact} Inputstream
*
* @param moduleId * @param moduleId
* the {@link Artifact} belongs to * the {@link Artifact} belongs to
*
* @param filename * @param filename
* that was provided during upload. * that was provided during upload.
*
* @param fileSize * @param fileSize
* the file size * the file size
*
* @return {@link Artifact} entity. * @return {@link Artifact} entity.
*/ */
public Artifact createArtifact(final byte[] artifactData, final Long moduleId, final String filename, public Artifact createArtifact(final byte[] artifactData, final Long moduleId, final String filename,
@@ -497,11 +520,12 @@ public class TestdataFactory {
/** /**
* Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()} * {@link #DEFAULT_VERSION} and random generated
* in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param typeKey * @param typeKey
* of the {@link SoftwareModuleType} * of the {@link SoftwareModuleType}
*
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModule(final String typeKey) { public SoftwareModule createSoftwareModule(final String typeKey) {
@@ -509,9 +533,11 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY} * Creates {@link SoftwareModule} of type
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* generated {@link Target#getDescription()} in the repository. * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
@@ -520,12 +546,15 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY} * Creates {@link SoftwareModule} of type
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* generated {@link Target#getDescription()} in the repository. * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
* *
* @param prefix * @param prefix
* added to name and version * added to name and version
*
*
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleApp(final String prefix) { public SoftwareModule createSoftwareModuleApp(final String prefix) {
@@ -533,9 +562,11 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY} * Creates {@link SoftwareModule} of type
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* generated {@link Target#getDescription()} in the repository. * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
@@ -544,12 +575,15 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY} * Creates {@link SoftwareModule} of type
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* generated {@link Target#getDescription()} in the repository. * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
* *
* @param prefix * @param prefix
* added to name and version * added to name and version
*
*
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleOs(final String prefix) { public SoftwareModule createSoftwareModuleOs(final String prefix) {
@@ -558,13 +592,14 @@ public class TestdataFactory {
/** /**
* Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()} * {@link #DEFAULT_VERSION} and random generated
* in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param typeKey * @param typeKey
* of the {@link SoftwareModuleType} * of the {@link SoftwareModuleType}
* @param prefix * @param prefix
* added to name and version * added to name and version
*
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) { public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) {
@@ -603,7 +638,7 @@ public class TestdataFactory {
return target; return target;
} }
private void assertTargetProperlyCreated(Target target) { private void assertTargetProperlyCreated(final Target target) {
assertThat(target.getCreatedBy()).isNotNull(); assertThat(target.getCreatedBy()).isNotNull();
assertThat(target.getCreatedAt()).isNotNull(); assertThat(target.getCreatedAt()).isNotNull();
assertThat(target.getLastModifiedBy()).isNotNull(); assertThat(target.getLastModifiedBy()).isNotNull();
@@ -614,14 +649,15 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet}s in repository including three * Creates {@link DistributionSet}s in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* In addition it updates the created {@link DistributionSet}s and * In addition it updates the created {@link DistributionSet}s and
* {@link SoftwareModule}s to ensure that {@link BaseEntity#getLastModifiedAt()} * {@link SoftwareModule}s to ensure that
* and {@link BaseEntity#getLastModifiedBy()} is filled. * {@link BaseEntity#getLastModifiedAt()} and
* {@link BaseEntity#getLastModifiedBy()} is filled.
* *
* @return persisted {@link DistributionSet}. * @return persisted {@link DistributionSet}.
*/ */
@@ -639,8 +675,8 @@ public class TestdataFactory {
/** /**
* @return {@link DistributionSetType} with key {@link #DS_TYPE_DEFAULT} and * @return {@link DistributionSetType} with key {@link #DS_TYPE_DEFAULT} and
* {@link SoftwareModuleType}s {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModuleType}s {@link #SM_TYPE_OS},
* , {@link #SM_TYPE_APP}. * {@link #SM_TYPE_RT} , {@link #SM_TYPE_APP}.
*/ */
public DistributionSetType findOrCreateDefaultTestDsType() { public DistributionSetType findOrCreateDefaultTestDsType() {
final List<SoftwareModuleType> mand = new ArrayList<>(); final List<SoftwareModuleType> mand = new ArrayList<>();
@@ -661,6 +697,7 @@ public class TestdataFactory {
* {@link DistributionSetType#getKey()} * {@link DistributionSetType#getKey()}
* @param dsTypeName * @param dsTypeName
* {@link DistributionSetType#getName()} * {@link DistributionSetType#getName()}
*
* @return persisted {@link DistributionSetType} * @return persisted {@link DistributionSetType}
*/ */
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName) { public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName) {
@@ -681,6 +718,7 @@ public class TestdataFactory {
* {@link DistributionSetType#getMandatoryModuleTypes()} * {@link DistributionSetType#getMandatoryModuleTypes()}
* @param optional * @param optional
* {@link DistributionSetType#getOptionalModuleTypes()} * {@link DistributionSetType#getOptionalModuleTypes()}
*
* @return persisted {@link DistributionSetType} * @return persisted {@link DistributionSetType}
*/ */
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName, public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName,
@@ -694,11 +732,12 @@ public class TestdataFactory {
/** /**
* Finds {@link SoftwareModuleType} in repository with given * Finds {@link SoftwareModuleType} in repository with given
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet with * {@link SoftwareModuleType#getKey()} or creates if it does not exist yet
* {@link SoftwareModuleType#getMaxAssignments()} = 1. * with {@link SoftwareModuleType#getMaxAssignments()} = 1.
* *
* @param key * @param key
* {@link SoftwareModuleType#getKey()} * {@link SoftwareModuleType#getKey()}
*
* @return persisted {@link SoftwareModuleType} * @return persisted {@link SoftwareModuleType}
*/ */
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key) { public SoftwareModuleType findOrCreateSoftwareModuleType(final String key) {
@@ -713,6 +752,7 @@ public class TestdataFactory {
* {@link SoftwareModuleType#getKey()} * {@link SoftwareModuleType#getKey()}
* @param maxAssignments * @param maxAssignments
* {@link SoftwareModuleType#getMaxAssignments()} * {@link SoftwareModuleType#getMaxAssignments()}
*
* @return persisted {@link SoftwareModuleType} * @return persisted {@link SoftwareModuleType}
*/ */
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key, final int maxAssignments) { public SoftwareModuleType findOrCreateSoftwareModuleType(final String key, final int maxAssignments) {
@@ -732,6 +772,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()} * {@link DistributionSet#getType()}
* @param modules * @param modules
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
*
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet createDistributionSet(final String name, final String version, public DistributionSet createDistributionSet(final String name, final String version,
@@ -754,6 +795,7 @@ public class TestdataFactory {
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
* @param requiredMigrationStep * @param requiredMigrationStep
* {@link DistributionSet#isRequiredMigrationStep()} * {@link DistributionSet#isRequiredMigrationStep()}
*
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name, final String version, public DistributionSet generateDistributionSet(final String name, final String version,
@@ -775,6 +817,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()} * {@link DistributionSet#getType()}
* @param modules * @param modules
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
*
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name, final String version, public DistributionSet generateDistributionSet(final String name, final String version,
@@ -787,6 +830,7 @@ public class TestdataFactory {
* *
* @param name * @param name
* {@link DistributionSet#getName()} * {@link DistributionSet#getName()}
*
* @return the generated {@link DistributionSet} * @return the generated {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name) { public DistributionSet generateDistributionSet(final String name) {
@@ -795,11 +839,13 @@ public class TestdataFactory {
} }
/** /**
* Creates {@link Target}s in repository and with {@link #DEFAULT_CONTROLLER_ID} * Creates {@link Target}s in repository and with
* as prefix for {@link Target#getControllerId()}. * {@link #DEFAULT_CONTROLLER_ID} as prefix for
* {@link Target#getControllerId()}.
* *
* @param number * @param number
* of {@link Target}s to create * of {@link Target}s to create
*
* @return {@link List} of {@link Target} entities * @return {@link List} of {@link Target} entities
*/ */
public List<Target> createTargets(final int number) { public List<Target> createTargets(final int number) {
@@ -835,7 +881,8 @@ public class TestdataFactory {
/** /**
* Builds {@link Target} objects with given prefix for * Builds {@link Target} objects with given prefix for
* {@link Target#getControllerId()} followed by a number suffix starting with 0. * {@link Target#getControllerId()} followed by a number suffix starting
* with 0.
* *
* @param numberOfTargets * @param numberOfTargets
* of {@link Target}s to generate * of {@link Target}s to generate
@@ -929,6 +976,7 @@ public class TestdataFactory {
* *
* @param number * @param number
* of {@link DistributionSetTag}s * of {@link DistributionSetTag}s
*
* @return the persisted {@link DistributionSetTag}s * @return the persisted {@link DistributionSetTag}s
*/ */
public List<DistributionSetTag> createDistributionSetTags(final int number) { public List<DistributionSetTag> createDistributionSetTags(final int number) {
@@ -950,7 +998,8 @@ public class TestdataFactory {
} }
/** /**
* Append {@link ActionStatus} to all {@link Action}s of given {@link Target}s. * Append {@link ActionStatus} to all {@link Action}s of given
* {@link Target}s.
* *
* @param targets * @param targets
* to add {@link ActionStatus} * to add {@link ActionStatus}
@@ -958,6 +1007,7 @@ public class TestdataFactory {
* to add * to add
* @param message * @param message
* to add * to add
*
* @return updated {@link Action}. * @return updated {@link Action}.
*/ */
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status, public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -966,7 +1016,8 @@ public class TestdataFactory {
} }
/** /**
* Append {@link ActionStatus} to all {@link Action}s of given {@link Target}s. * Append {@link ActionStatus} to all {@link Action}s of given
* {@link Target}s.
* *
* @param targets * @param targets
* to add {@link ActionStatus} * to add {@link ActionStatus}
@@ -974,6 +1025,7 @@ public class TestdataFactory {
* to add * to add
* @param msgs * @param msgs
* to add * to add
*
* @return updated {@link Action}. * @return updated {@link Action}.
*/ */
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status, public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -1012,7 +1064,7 @@ public class TestdataFactory {
final int groupSize, final String filterQuery, final DistributionSet distributionSet, final int groupSize, final String filterQuery, final DistributionSet distributionSet,
final String successCondition, final String errorCondition) { final String successCondition, final String errorCondition) {
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet, return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
successCondition, errorCondition, Action.ActionType.FORCED); successCondition, errorCondition, Action.ActionType.FORCED, null);
} }
/** /**
@@ -1034,11 +1086,14 @@ public class TestdataFactory {
* to switch to next group * to switch to next group
* @param actionType * @param actionType
* the type of the Rollout * the type of the Rollout
* @param weight
* weight of the Rollout
* @return created {@link Rollout} * @return created {@link Rollout}
*/ */
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription, public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
final int groupSize, final String filterQuery, final DistributionSet distributionSet, final int groupSize, final String filterQuery, final DistributionSet distributionSet,
final String successCondition, final String errorCondition, final Action.ActionType actionType) { final String successCondition, final String errorCondition, final Action.ActionType actionType,
final Integer weight) {
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults() final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition) .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition) .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
@@ -1046,7 +1101,7 @@ public class TestdataFactory {
final Rollout rollout = rolloutManagement.create( final Rollout rollout = rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutDescription) entityFactory.rollout().create().name(rolloutName).description(rolloutDescription)
.targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType), .targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType).weight(weight),
groupSize, conditions); groupSize, conditions);
// Run here, because Scheduler is disabled during tests // Run here, because Scheduler is disabled during tests
@@ -1060,8 +1115,8 @@ public class TestdataFactory {
* {@link Target}s. * {@link Target}s.
* *
* @param prefix * @param prefix
* for rollouts name, description, {@link Target#getControllerId()} * for rollouts name, description,
* filter * {@link Target#getControllerId()} filter
* @return created {@link Rollout} * @return created {@link Rollout}
*/ */
public Rollout createRollout(final String prefix) { public Rollout createRollout(final String prefix) {
@@ -1071,12 +1126,12 @@ public class TestdataFactory {
} }
/** /**
* Create the soft deleted {@link Rollout} with a new {@link DistributionSet} * Create the soft deleted {@link Rollout} with a new
* and {@link Target}s. * {@link DistributionSet} and {@link Target}s.
* *
* @param prefix * @param prefix
* for rollouts name, description, {@link Target#getControllerId()} * for rollouts name, description,
* filter * {@link Target#getControllerId()} filter
* @return created {@link Rollout} * @return created {@link Rollout}
*/ */
public Rollout createSoftDeletedRollout(final String prefix) { public Rollout createSoftDeletedRollout(final String prefix) {

View File

@@ -149,7 +149,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action action = controllerManagement.findOldestActiveActionByTarget(controllerId).orElse(null); final Action action = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null);
checkAndCancelExpiredAction(action); checkAndCancelExpiredAction(action);

View File

@@ -105,12 +105,12 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @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.") @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 = {CONTROLLER_ROLE, @WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = { CONTROLLER_ROLE,
SYSTEM_ROLE}, autoCreateTenant = false) SYSTEM_ROLE }, autoCreateTenant = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = DistributionSetTypeCreatedEvent.class, count = 3), @Expect(type = DistributionSetTypeCreatedEvent.class, count = 3),
@Expect(type = SoftwareModuleTypeCreatedEvent.class, count = 2)}) @Expect(type = SoftwareModuleTypeCreatedEvent.class, count = 2) })
public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception { public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception {
mvc.perform(get("/default-tenant/", tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/default-tenant/", tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
@@ -131,10 +131,10 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that target poll request does not change audit data on the entity.") @Description("Ensures that target poll request does not change audit data on the entity.")
@WithUser(principal = "knownPrincipal", authorities = {SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET, @WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET}, allSpPermissions = false) SpPermission.CREATE_TARGET }, allSpPermissions = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void targetPollDoesNotModifyAuditData() throws Exception { public void targetPollDoesNotModifyAuditData() throws Exception {
// create target first with "knownPrincipal" user and audit data // create target first with "knownPrincipal" user and audit data
final String knownTargetControllerId = "target1"; final String knownTargetControllerId = "target1";
@@ -162,15 +162,15 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that server returns a not found response in case of empty controlloer ID.") @Description("Ensures that server returns a not found response in case of empty controlloer ID.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 0)}) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void rootRsWithoutId() throws Exception { public void rootRsWithoutId() throws Exception {
mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
} }
@Test @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.") @Description("Ensures that the system creates a new target in plug and play manner, i.e. target is authenticated but does not exist yet.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlay() throws Exception { public void rootRsPlugAndPlay() throws Exception {
final long current = System.currentTimeMillis(); final long current = System.currentTimeMillis();
@@ -198,8 +198,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.") @Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.")
@WithUser(principal = "knownpricipal", allSpPermissions = false) @WithUser(principal = "knownpricipal", allSpPermissions = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetPollEvent.class, count = 1) })
public void pollWithModifiedGloablPollingTime() throws Exception { public void pollWithModifiedGloablPollingTime() throws Exception {
securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> { securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL, tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
@@ -218,14 +218,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.") @Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 6), @Expect(type = TargetPollEvent.class, count = 6),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 2), @Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6)}) @Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void rootRsNotModified() throws Exception { public void rootRsNotModified() throws Exception {
final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())) final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -289,8 +289,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that the target state machine of a precomissioned target switches from " @Description("Ensures that the target state machine of a precomissioned target switches from "
+ "UNKNOWN to REGISTERED when the target polls for the first time.") + "UNKNOWN to REGISTERED when the target polls for the first time.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPrecommissioned() throws Exception { public void rootRsPrecommissioned() throws Exception {
testdataFactory.createTarget("4711"); testdataFactory.createTarget("4711");
@@ -314,8 +314,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that the source IP address of the polling target is correctly stored in repository") @Description("Ensures that the source IP address of the polling target is correctly stored in repository")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlayIpAddress() throws Exception { public void rootRsPlugAndPlayIpAddress() throws Exception {
// test // test
final String knownControllerId1 = "0815"; final String knownControllerId1 = "0815";
@@ -341,8 +341,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Ensures that the source IP address of the polling target is not stored in repository if disabled") @Description("Ensures that the source IP address of the polling target is not stored in repository if disabled")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)}) @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsIpAddressNotStoredIfDisabled() throws Exception { public void rootRsIpAddressNotStoredIfDisabled() throws Exception {
securityProperties.getClients().setTrackRemoteIp(false); securityProperties.getClients().setTrackRemoteIp(false);
@@ -360,12 +360,12 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Controller trys to finish an update process after it has been finished by an error action status.") @Description("Controller trys to finish an update process after it has been finished by an error action status.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)}) @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception { public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911"); Target savedTarget = testdataFactory.createTarget("911");
@@ -384,13 +384,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Controller sends attribute update request after device successfully closed software update.") @Description("Controller sends attribute update request after device successfully closed software update.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 6), @Expect(type = TargetPollEvent.class, count = 4), @Expect(type = TargetUpdatedEvent.class, count = 6), @Expect(type = TargetPollEvent.class, count = 4),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1)}) @Expect(type = TargetAttributesRequestedEvent.class, count = 1) })
public void attributeUpdateRequestSendingAfterSuccessfulDeployment() throws Exception { public void attributeUpdateRequestSendingAfterSuccessfulDeployment() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("1"); final DistributionSet ds = testdataFactory.createDistributionSet("1");
final Target savedTarget = testdataFactory.createTarget("922"); final Target savedTarget = testdataFactory.createTarget("922");
@@ -463,13 +463,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.") @Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)}) @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryCount() throws Exception { public void testActionHistoryCount() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911"); Target savedTarget = testdataFactory.createTarget("911");
@@ -498,13 +498,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Test to verify that a zero input value of actionHistory results in no action history appended for getControllerDeploymentActionFeedback endpoint.") @Description("Test to verify that a zero input value of actionHistory results in no action history appended for getControllerDeploymentActionFeedback endpoint.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)}) @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryZeroInput() throws Exception { public void testActionHistoryZeroInput() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911"); Target savedTarget = testdataFactory.createTarget("911");
@@ -529,13 +529,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Test to verify that entire action history is returned if the input value for actionHistory is -1, for getControllerDeploymentActionFeedback endpoint.") @Description("Test to verify that entire action history is returned if the input value for actionHistory is -1, for getControllerDeploymentActionFeedback endpoint.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)}) @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryNegativeInput() throws Exception { public void testActionHistoryNegativeInput() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911"); Target savedTarget = testdataFactory.createTarget("911");
@@ -654,12 +654,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("Assign multiple DS in multi-assignment mode. The earliest active Action is exposed to the controller.") @Description("Assign multiple DS in multi-assignment mode. The earliest active Action is exposed to the controller.")
public void earliestActionIsExposedToControllerInMultiAssignMode() throws Exception { public void earliestActionIsExposedToControllerInMultiAssignMode() throws Exception {
setMultiAssignmentsEnabled(); enableMultiAssignments();
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Action action1 = getFirstAssignedAction(assignDistributionSet(ds1, target)); final Action action1 = getFirstAssignedAction(assignDistributionSet(ds1.getId(), target.getControllerId(), 56));
final Long action2Id = getFirstAssignedActionId(assignDistributionSet(ds2, target)); final Long action2Id = getFirstAssignedActionId(
assignDistributionSet(ds2.getId(), target.getControllerId(), 34));
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action1.getId()); assertDeploymentActionIsExposedToTarget(target.getControllerId(), action1.getId());
sendDeploymentActionFeedback(target, action1, "closed", "success"); sendDeploymentActionFeedback(target, action1, "closed", "success");
@@ -670,7 +671,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("The system should not create a new target because of a too long controller id.") @Description("The system should not create a new target because of a too long controller id.")
public void rootRsWithInvalidControllerId() throws Exception { public void rootRsWithInvalidControllerId() throws Exception {
String invalidControllerId = RandomStringUtils.randomAlphabetic(Target.CONTROLLER_ID_MAX_SIZE + 1); final String invalidControllerId = RandomStringUtils.randomAlphabetic(Target.CONTROLLER_ID_MAX_SIZE + 1);
mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), invalidControllerId)) mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), invalidControllerId))
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
} }
@@ -684,8 +685,4 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink))); .andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink)));
} }
private void setMultiAssignmentsEnabled() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
} }

View File

@@ -57,9 +57,12 @@ public class MgmtAction extends MgmtBaseEntity {
@JsonProperty @JsonProperty
private Long forceTime; private Long forceTime;
@JsonProperty(value="forceType") @JsonProperty(value = "forceType")
private MgmtActionType actionType; private MgmtActionType actionType;
@JsonProperty
private Integer weight;
@JsonProperty @JsonProperty
private MgmtMaintenanceWindow maintenanceWindow; private MgmtMaintenanceWindow maintenanceWindow;
@@ -79,6 +82,14 @@ public class MgmtAction extends MgmtBaseEntity {
this.forceTime = forceTime; this.forceTime = forceTime;
} }
public Integer getWeight() {
return weight;
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
public MgmtActionType getActionType() { public MgmtActionType getActionType() {
return actionType; return actionType;
} }
@@ -110,5 +121,4 @@ public class MgmtAction extends MgmtBaseEntity {
public void setType(final String type) { public void setType(final String type) {
this.type = type; this.type = type;
} }
} }

View File

@@ -25,6 +25,7 @@ public class MgmtTargetAssignmentRequestBody {
private long forcetime; private long forcetime;
private MgmtActionType type; private MgmtActionType type;
private MgmtMaintenanceWindowRequestBody maintenanceWindow; private MgmtMaintenanceWindowRequestBody maintenanceWindow;
private Integer weight;
/** /**
* JsonCreator Constructor * JsonCreator Constructor
@@ -61,6 +62,14 @@ public class MgmtTargetAssignmentRequestBody {
this.forcetime = forcetime; this.forcetime = forcetime;
} }
public Integer getWeight() {
return weight;
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
public MgmtMaintenanceWindowRequestBody getMaintenanceWindow() { public MgmtMaintenanceWindowRequestBody getMaintenanceWindow() {
return maintenanceWindow; return maintenanceWindow;
} }
@@ -68,5 +77,4 @@ public class MgmtTargetAssignmentRequestBody {
public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) { public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
this.maintenanceWindow = maintenanceWindow; this.maintenanceWindow = maintenanceWindow;
} }
} }

View File

@@ -12,12 +12,12 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity; import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
/** /**
* *
@@ -47,6 +47,9 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity {
@JsonProperty @JsonProperty
private MgmtActionType type; private MgmtActionType type;
@JsonProperty
private Integer weight;
public boolean isDeleted() { public boolean isDeleted() {
return deleted; return deleted;
} }
@@ -114,4 +117,12 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity {
public MgmtActionType getType() { public MgmtActionType getType() {
return type; return type;
} }
public void setWeight(final Integer weight) {
this.weight = weight;
}
public Integer getWeight() {
return weight;
}
} }

View File

@@ -16,6 +16,7 @@ import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* Model for request containing a rollout body e.g. in a POST request of * Model for request containing a rollout body e.g. in a POST request of
@@ -34,6 +35,9 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
private Long startAt; private Long startAt;
@JsonProperty(required = false)
private Integer weight;
private MgmtActionType type; private MgmtActionType type;
private List<MgmtRolloutGroup> groups; private List<MgmtRolloutGroup> groups;
@@ -124,7 +128,7 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
* @param groups * @param groups
* List of {@link MgmtRolloutGroup} * List of {@link MgmtRolloutGroup}
*/ */
public void setGroups(List<MgmtRolloutGroup> groups) { public void setGroups(final List<MgmtRolloutGroup> groups) {
this.groups = groups; this.groups = groups;
} }
@@ -139,7 +143,22 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
* @param startAt * @param startAt
* the start at timestamp in millis or null * the start at timestamp in millis or null
*/ */
public void setStartAt(Long startAt) { public void setStartAt(final Long startAt) {
this.startAt = startAt; this.startAt = startAt;
} }
/**
* @return the priority of {@link Rollout}
*/
public Integer getWeight() {
return weight;
}
/**
* @param weight
* the priority of {@link Rollout}
*/
public void setWeight(final Integer weight) {
this.weight = weight;
}
} }

View File

@@ -17,6 +17,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class MgmtDistributionSetAssignment extends MgmtId { public class MgmtDistributionSetAssignment extends MgmtId {
private long forcetime; private long forcetime;
@JsonProperty(required = false)
private Integer weight;
private MgmtActionType type; private MgmtActionType type;
private MgmtMaintenanceWindowRequestBody maintenanceWindow; private MgmtMaintenanceWindowRequestBody maintenanceWindow;
@@ -47,6 +49,14 @@ public class MgmtDistributionSetAssignment extends MgmtId {
this.forcetime = forcetime; this.forcetime = forcetime;
} }
public Integer getWeight() {
return weight;
}
public void setWeight(final int weight) {
this.weight = weight;
}
/** /**
* Returns {@link MgmtMaintenanceWindowRequestBody} for distribution set * Returns {@link MgmtMaintenanceWindowRequestBody} for distribution set
* assignment. * assignment.

View File

@@ -22,6 +22,9 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
@JsonProperty(required = false) @JsonProperty(required = false)
private MgmtActionType type; private MgmtActionType type;
@JsonProperty(required = false)
private Integer weight;
public MgmtActionType getType() { public MgmtActionType getType() {
return type; return type;
} }
@@ -29,4 +32,12 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
public void setType(final MgmtActionType type) { public void setType(final MgmtActionType type) {
this.type = type; this.type = type;
} }
public Integer getWeight() {
return weight;
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
} }

View File

@@ -40,6 +40,9 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity {
@JsonProperty @JsonProperty
private MgmtActionType autoAssignActionType; private MgmtActionType autoAssignActionType;
@JsonProperty
private Integer autoAssignWeight;
public Long getFilterId() { public Long getFilterId() {
return filterId; return filterId;
} }
@@ -80,4 +83,11 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity {
this.autoAssignActionType = actionType; this.autoAssignActionType = actionType;
} }
public Integer getAutoAssignWeight() {
return autoAssignWeight;
}
public void setAutoAssignWeight(final Integer autoAssignWeight) {
this.autoAssignWeight = autoAssignWeight;
}
} }

View File

@@ -33,8 +33,7 @@ public class MgmtTargetFilterQueryRequestBody {
return query; return query;
} }
public void setQuery(String query) { public void setQuery(final String query) {
this.query = query; this.query = query;
} }
} }

View File

@@ -38,7 +38,7 @@ public final class MgmtDeploymentRequestMapper {
final String targetId) { final String targetId) {
return createAssignmentRequest(targetId, dsAssignment.getId(), dsAssignment.getType(), return createAssignmentRequest(targetId, dsAssignment.getId(), dsAssignment.getType(),
dsAssignment.getForcetime(), dsAssignment.getMaintenanceWindow()); dsAssignment.getForcetime(), dsAssignment.getWeight(), dsAssignment.getMaintenanceWindow());
} }
/** /**
@@ -54,13 +54,14 @@ public final class MgmtDeploymentRequestMapper {
final Long dsId) { final Long dsId) {
return createAssignmentRequest(targetAssignment.getId(), dsId, targetAssignment.getType(), return createAssignmentRequest(targetAssignment.getId(), dsId, targetAssignment.getType(),
targetAssignment.getForcetime(), targetAssignment.getMaintenanceWindow()); targetAssignment.getForcetime(), targetAssignment.getWeight(), targetAssignment.getMaintenanceWindow());
} }
private static DeploymentRequest createAssignmentRequest(final String targetId, final Long dsId, private static DeploymentRequest createAssignmentRequest(final String targetId, final Long dsId,
final MgmtActionType type, final long forcetime, final MgmtMaintenanceWindowRequestBody maintenanceWindow) { final MgmtActionType type, final long forcetime, final Integer weight,
final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
final DeploymentRequestBuilder request = DeploymentManagement.deploymentRequest(targetId, dsId) final DeploymentRequestBuilder request = DeploymentManagement.deploymentRequest(targetId, dsId)
.setActionType(MgmtRestModelMapper.convertActionType(type)).setForceTime(forcetime); .setActionType(MgmtRestModelMapper.convertActionType(type)).setForceTime(forcetime).setWeight(weight);
if (maintenanceWindow != null) { if (maintenanceWindow != null) {
final String cronSchedule = maintenanceWindow.getSchedule(); final String cronSchedule = maintenanceWindow.getSchedule();
final String duration = maintenanceWindow.getDuration(); final String duration = maintenanceWindow.getDuration();
@@ -70,5 +71,4 @@ public final class MgmtDeploymentRequestMapper {
} }
return request.build(); return request.build();
} }
} }

View File

@@ -266,7 +266,6 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
final List<DistributionSetAssignmentResult> assignmentResults = deployManagament final List<DistributionSetAssignmentResult> assignmentResults = deployManagament
.assignDistributionSets(deploymentRequests); .assignDistributionSets(deploymentRequests);
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignmentResults)); return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignmentResults));
} }
@Override @Override

View File

@@ -79,6 +79,7 @@ final class MgmtRolloutMapper {
body.setTotalTargets(rollout.getTotalTargets()); body.setTotalTargets(rollout.getTotalTargets());
body.setDeleted(rollout.isDeleted()); body.setDeleted(rollout.isDeleted());
body.setType(MgmtRestModelMapper.convertActionType(rollout.getActionType())); body.setType(MgmtRestModelMapper.convertActionType(rollout.getActionType()));
rollout.getWeight().ifPresent(body::setWeight);
if (withDetails) { if (withDetails) {
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) { for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
@@ -106,7 +107,8 @@ final class MgmtRolloutMapper {
return entityFactory.rollout().create().name(restRequest.getName()).description(restRequest.getDescription()) return entityFactory.rollout().create().name(restRequest.getName()).description(restRequest.getDescription())
.set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery()) .set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery())
.actionType(MgmtRestModelMapper.convertActionType(restRequest.getType())) .actionType(MgmtRestModelMapper.convertActionType(restRequest.getType()))
.forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt()); .forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt())
.weight(restRequest.getWeight());
} }
static RolloutGroupCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) { static RolloutGroupCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
@@ -249,5 +251,4 @@ final class MgmtRolloutMapper {
private static String createIllegalArgumentLiteral(final Condition condition) { private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED; return "Condition " + condition + NOT_SUPPORTED;
} }
} }

View File

@@ -15,11 +15,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -58,6 +61,7 @@ public final class MgmtTargetFilterQueryMapper {
if (distributionSet != null) { if (distributionSet != null) {
targetRest.setAutoAssignDistributionSet(distributionSet.getId()); targetRest.setAutoAssignDistributionSet(distributionSet.getId());
targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType())); targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType()));
filter.getAutoAssignWeight().ifPresent(targetRest::setAutoAssignWeight);
} }
targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withSelfRel()); targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withSelfRel());
@@ -76,4 +80,12 @@ public final class MgmtTargetFilterQueryMapper {
return entityFactory.targetFilterQuery().create().name(filterRest.getName()).query(filterRest.getQuery()); return entityFactory.targetFilterQuery().create().name(filterRest.getName()).query(filterRest.getQuery());
} }
static AutoAssignDistributionSetUpdate fromRequest(final EntityFactory entityFactory, final long filterId,
final MgmtDistributionSetAutoAssignment assignRest) {
final ActionType type = MgmtRestModelMapper.convertActionType(assignRest.getType());
return entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(assignRest.getId()).actionType(type)
.weight(assignRest.getWeight());
}
} }

View File

@@ -108,8 +108,9 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
@RequestBody final MgmtTargetFilterQueryRequestBody targetFilterRest) { @RequestBody final MgmtTargetFilterQueryRequestBody targetFilterRest) {
LOG.debug("updating target filter query {}", filterId); LOG.debug("updating target filter query {}", filterId);
final TargetFilterQuery updateFilter = filterManagement.update(entityFactory.targetFilterQuery() final TargetFilterQuery updateFilter = filterManagement
.update(filterId).name(targetFilterRest.getName()).query(targetFilterRest.getQuery())); .update(entityFactory.targetFilterQuery().update(filterId).name(targetFilterRest.getName())
.query(targetFilterRest.getQuery()));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter); final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
MgmtTargetFilterQueryMapper.addLinks(response); MgmtTargetFilterQueryMapper.addLinks(response);
@@ -127,10 +128,10 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
@Override @Override
public ResponseEntity<MgmtTargetFilterQuery> postAssignedDistributionSet( public ResponseEntity<MgmtTargetFilterQuery> postAssignedDistributionSet(
@PathVariable("filterId") final Long filterId, @PathVariable("filterId") final Long filterId,
@RequestBody final MgmtDistributionSetAutoAssignment dsIdWithActionType) { @RequestBody final MgmtDistributionSetAutoAssignment autoAssignRequest) {
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDSWithActionType(filterId, final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(
dsIdWithActionType.getId(), MgmtRestModelMapper.convertActionType(dsIdWithActionType.getType())); MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filterId, autoAssignRequest));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter); final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
MgmtTargetFilterQueryMapper.addLinks(response); MgmtTargetFilterQueryMapper.addLinks(response);
@@ -156,7 +157,7 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
@Override @Override
public ResponseEntity<Void> deleteAssignedDistributionSet(@PathVariable("filterId") final Long filterId) { public ResponseEntity<Void> deleteAssignedDistributionSet(@PathVariable("filterId") final Long filterId) {
filterManagement.updateAutoAssignDS(filterId, null); filterManagement.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(null));
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }

View File

@@ -206,6 +206,7 @@ public final class MgmtTargetMapper {
if (ActionType.TIMEFORCED == action.getActionType()) { if (ActionType.TIMEFORCED == action.getActionType()) {
result.setForceTime(action.getForcedTime()); result.setForceTime(action.getForcedTime());
} }
action.getWeight().ifPresent(result::setWeight);
result.setActionType(MgmtRestModelMapper.convertActionType(action.getActionType())); result.setActionType(MgmtRestModelMapper.convertActionType(action.getActionType()));
if (action.isActive()) { if (action.isActive()) {
@@ -289,5 +290,4 @@ public final class MgmtTargetMapper {
static List<MgmtMetadata> toResponseTargetMetadata(final List<TargetMetadata> metadata) { static List<MgmtMetadata> toResponseTargetMetadata(final List<TargetMetadata> metadata) {
return metadata.stream().map(MgmtTargetMapper::toResponseTargetMetadata).collect(Collectors.toList()); return metadata.stream().map(MgmtTargetMapper::toResponseTargetMetadata).collect(Collectors.toList());
} }
} }

View File

@@ -12,6 +12,7 @@ import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration; import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
@@ -21,6 +22,8 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.TestConfiguration; import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
import org.eclipse.hawkbit.rest.RestConfiguration; import org.eclipse.hawkbit.rest.RestConfiguration;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration; import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@@ -197,4 +200,24 @@ public abstract class AbstractManagementApiIntegrationTest extends AbstractRestI
}; };
} }
protected static JSONObject getAssignmentObject(final Object id, final MgmtActionType type) {
final JSONObject obj = new JSONObject();
try {
obj.put("id", id);
obj.put("type", type.getName());
} catch (final JSONException e) {
e.printStackTrace();
}
return obj;
}
protected static JSONObject getAssignmentObject(final Object id, final MgmtActionType type, final int weight) {
try {
return getAssignmentObject(id, type).put("weight", weight);
} catch (final JSONException e) {
e.printStackTrace();
}
return new JSONObject();
}
} }

View File

@@ -555,9 +555,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(knownFilterName)
.create(entityFactory.targetFilterQuery().create().name(knownFilterName).query("name==y")).getId(), .query("name==y").autoAssignDistributionSet(createdDs.getId()));
createdDs.getId());
// create some dummy target filter queries // create some dummy target filter queries
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("b").query("name==y")); targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("b").query("name==y"));
@@ -615,12 +614,10 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
private void prepareTestFilters(final String filterNamePrefix, final DistributionSet createdDs) { private void prepareTestFilters(final String filterNamePrefix, final DistributionSet createdDs) {
// create target filter queries that should be found // create target filter queries that should be found
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1")
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1").query("name==y")) .query("name==y").autoAssignDistributionSet(createdDs.getId()));
.getId(), createdDs.getId()); targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2")
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement .query("name==y").autoAssignDistributionSet(createdDs.getId()));
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2").query("name==y"))
.getId(), createdDs.getId());
// create some dummy target filter queries // create some dummy target filter queries
targetFilterQueryManagement targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "b").query("name==y")); .create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "b").query("name==y"));
@@ -1247,8 +1244,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
} }
@Test @Test
@Description("Ensures that multi target assignment through API is reflected by the repository in the case of " + @Description("Ensures that multi target assignment through API is reflected by the repository in the case of "
"DOWNLOAD_ONLY.") + "DOWNLOAD_ONLY.")
public void assignMultipleTargetsToDistributionSetAsDownloadOnly() throws Exception { public void assignMultipleTargetsToDistributionSetAsDownloadOnly() throws Exception {
final DistributionSet createdDs = testdataFactory.createDistributionSet(); final DistributionSet createdDs = testdataFactory.createDistributionSet();
@@ -1263,8 +1260,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
assignDistributionSet(createdDs.getId(), knownTargetIds[0], Action.ActionType.DOWNLOAD_ONLY); assignDistributionSet(createdDs.getId(), knownTargetIds[0], Action.ActionType.DOWNLOAD_ONLY);
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", createdDs.getId()) mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", createdDs.getId())
.contentType(MediaType.APPLICATION_JSON).content(list.toString())) .contentType(MediaType.APPLICATION_JSON).content(list.toString())).andExpect(status().isOk())
.andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1))) .andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1))) .andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length))); .andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
@@ -1310,10 +1307,10 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONArray body = new JSONArray(); final JSONArray body = new JSONArray();
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED)); body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 56));
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED)); body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 78));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.FORCED)); body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.FORCED, 67));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.SOFT)); body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.SOFT, 34));
enableMultiAssignments(); enableMultiAssignments();
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(body.toString()) mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(body.toString())
@@ -1321,14 +1318,32 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
.andExpect(jsonPath("total", equalTo(body.length()))); .andExpect(jsonPath("total", equalTo(body.length())));
} }
@Test
@Description("An assignment request containing a weight is only accepted when weight is valide and multi assignment is on.")
public void weightValidation() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final int weight = 78;
final JSONArray bodyValide = new JSONArray().put(getAssignmentObject(targetId, MgmtActionType.FORCED, weight));
final JSONArray bodyInvalide = new JSONArray()
.put(getAssignmentObject(targetId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1));
public static JSONObject getAssignmentObject(final String targetId, final MgmtActionType type) mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString())
throws JSONException { .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
final JSONObject obj = new JSONObject(); .andExpect(status().isBadRequest())
obj.put("id", targetId); .andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
obj.put("type", type.getName()); enableMultiAssignments();
return obj; mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyInvalide.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().collect(Collectors.toList());
assertThat(actions).size().isEqualTo(1);
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
} }
} }

View File

@@ -916,6 +916,36 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
postRollout("rollout1", 10, dsA.getId(), "id==target*", 20, Action.ActionType.DOWNLOAD_ONLY); postRollout("rollout1", 10, dsA.getId(), "id==target*", 20, Action.ActionType.DOWNLOAD_ONLY);
} }
@Test
@Description("A rollout create request containing a weight is only accepted when weight is valide and multi assignment is on.")
public void weightValidation() throws Exception {
testdataFactory.createTargets(4, "rollout", "description");
final Long dsId = testdataFactory.createDistributionSet().getId();
final int weight = 66;
final String invalideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, Action.WEIGHT_MIN - 1);
final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight);
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
enableMultiAssignments();
mvc.perform(post("/rest/v1/rollouts").content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated());
final List<Rollout> rollouts = rolloutManagement.findAll(PAGE, false).getContent();
assertThat(rollouts).hasSize(1);
assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight);
}
protected <T> T doWithTimeout(final Callable<T> callable, final SuccessCondition<T> successCondition, protected <T> T doWithTimeout(final Callable<T> callable, final SuccessCondition<T> successCondition,
final long timeout, final long pollInterval) throws Exception // NOPMD final long timeout, final long pollInterval) throws Exception // NOPMD
{ {
@@ -955,14 +985,12 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
private void postRollout(final String name, final int groupSize, final Long distributionSetId, private void postRollout(final String name, final int groupSize, final Long distributionSetId,
final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception { final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception {
String actionType = MgmtRestModelMapper.convertActionType(type).getName(); final String actionType = MgmtRestModelMapper.convertActionType(type).getName();
String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, final String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType); new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null);
mvc.perform(post("/rest/v1/rollouts") mvc.perform(post("/rest/v1/rollouts").content(rollout).contentType(MediaType.APPLICATION_JSON)
.content(rollout) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(jsonPath("$.name", equalTo(name))).andExpect(jsonPath("$.status", equalTo("creating"))) .andExpect(jsonPath("$.name", equalTo(name))).andExpect(jsonPath("$.status", equalTo("creating")))
.andExpect(jsonPath("$.type", equalTo(actionType))) .andExpect(jsonPath("$.type", equalTo(actionType)))
.andExpect(jsonPath("$.targetFilterQuery", equalTo(targetFilterQuery))) .andExpect(jsonPath("$.targetFilterQuery", equalTo(targetFilterQuery)))

View File

@@ -20,12 +20,15 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
@@ -104,8 +107,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception {
final String notExistingId = "4395"; final String notExistingId = "4395";
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId).content("{}") mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId).content("{}")
.contentType(MediaType.APPLICATION_JSON)).andDo(print()) .contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isNotFound());
.andExpect(status().isNotFound());
} }
@Test @Test
@@ -166,8 +168,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
createSingleTargetFilterQuery(idB, testQuery); createSingleTargetFilterQuery(idB, testQuery);
createSingleTargetFilterQuery(idC, testQuery); createSingleTargetFilterQuery(idC, testQuery);
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk()) mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk()).andDo(print())
.andDo(print())
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount)))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(knownTargetAmount))) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(knownTargetAmount)))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(knownTargetAmount))) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(knownTargetAmount)))
@@ -252,9 +253,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"
+ tfq.getId(); + tfq.getId();
// test // test
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())) mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
.andDo(print()).andExpect(status().isOk()) .andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName)))
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName)))
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery))) .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery)))
.andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix))) .andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix)))
.andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS")));
@@ -402,8 +402,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
? "{\"id\":" + set.getId() + ", \"type\":\"" + actionType.getName() + "\"}" ? "{\"id\":" + set.getId() + ", \"type\":\"" + actionType.getName() + "\"}"
: "{\"id\":" + set.getId() + "}"; : "{\"id\":" + set.getId() + "}";
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(print()) .content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk());
.andExpect(status().isOk());
final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get(); final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get();
final MgmtActionType expectedActionType = actionType != null ? actionType : MgmtActionType.FORCED; final MgmtActionType expectedActionType = actionType != null ? actionType : MgmtActionType.FORCED;
@@ -412,9 +411,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
assertThat(updatedFilterQuery.getAutoAssignActionType()) assertThat(updatedFilterQuery.getAutoAssignActionType())
.isEqualTo(MgmtRestModelMapper.convertActionType(expectedActionType)); .isEqualTo(MgmtRestModelMapper.convertActionType(expectedActionType));
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())) mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
.andDo(print()).andExpect(status().isOk()) .andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName())))
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName())))
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(tfq.getQuery()))) .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(tfq.getQuery())))
.andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_DS, equalTo(set.getId().intValue()))) .andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_DS, equalTo(set.getId().intValue())))
.andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_ACTION_TYPE, equalTo(expectedActionType.getName()))) .andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_ACTION_TYPE, equalTo(expectedActionType.getName())))
@@ -427,8 +425,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
throws Exception { throws Exception {
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + ", \"type\":\"" + MgmtActionType.TIMEFORCED.getName() + "\"}") .content("{\"id\":" + set.getId() + ", \"type\":\"" + MgmtActionType.TIMEFORCED.getName() + "\"}")
.contentType(MediaType.APPLICATION_JSON)).andDo(print()) .contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isBadRequest())
.andExpect(status().isBadRequest())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS,
equalTo(InvalidAutoAssignActionTypeException.class.getName()))) equalTo(InvalidAutoAssignActionTypeException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, .andExpect(jsonPath(JSON_PATH_ERROR_CODE,
@@ -491,7 +488,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
final DistributionSet set = testdataFactory.createDistributionSet(dsName); final DistributionSet set = testdataFactory.createDistributionSet(dsName);
final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery);
targetFilterQueryManagement.updateAutoAssignDS(tfq.getId(), set.getId()); targetFilterQueryManagement
.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(tfq.getId()).ds(set.getId()));
final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get(); final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get();
@@ -514,6 +512,34 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
} }
@Test
@Description("An auto assignment containing a weight is only accepted when weight is valide and multi assignment is on.")
public void weightValidation() throws Exception {
final Long filterId = createSingleTargetFilterQuery("filter1", "name==*").getId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final String invalideWeightRequest = new JSONObject().put("id", dsId).put("weight", Action.WEIGHT_MIN - 1)
.toString();
final String valideWeightRequest = new JSONObject().put("id", dsId).put("weight", 45).toString();
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
enableMultiAssignments();
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
filterId).content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isOk());
final List<TargetFilterQuery> filters = targetFilterQueryManagement.findAll(PAGE).getContent();
assertThat(filters).hasSize(1);
assertThat(filters.get(0).getAutoAssignWeight().get()).isEqualTo(45);
}
private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) { private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) {
return targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(name).query(query)); return targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(name).query(query));
} }

View File

@@ -26,7 +26,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@@ -40,6 +39,7 @@ import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -61,7 +61,6 @@ import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
@@ -1876,7 +1875,8 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId) final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
final JSONArray body = getAssignmentBody(dsIds); final JSONArray body = new JSONArray();
dsIds.forEach(id -> body.put(getAssignmentObject(id, MgmtActionType.FORCED, 67)));
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString()) mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
@@ -1889,8 +1889,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONArray body = getAssignmentBody(Collections.singletonList(dsId)); final JSONArray body = new JSONArray().put(getAssignmentObject(dsId, MgmtActionType.FORCED));
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString()) mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
@@ -1902,7 +1901,8 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONArray body = getAssignmentBody(Arrays.asList(dsId, dsId)); final JSONObject assignment = getAssignmentObject(dsId, MgmtActionType.FORCED);
final JSONArray body = new JSONArray().put(assignment).put(assignment);
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString()) mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -1916,21 +1916,90 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId) final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
final JSONArray body = getAssignmentBody(dsIds); final JSONArray body = new JSONArray();
dsIds.forEach(id -> body.put(getAssignmentObject(id, MgmtActionType.FORCED, 76)));
enableMultiAssignments(); enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString()) mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(status().isOk()).andExpect(jsonPath("total", equalTo(2))); .andExpect(jsonPath("total", equalTo(2)));
} }
public static JSONArray getAssignmentBody(final Collection<Long> dsIds) throws JSONException { @Test
final JSONArray body = new JSONArray(); @Description("An assignment request containing a weight is only accepted when weight is valide and multi assignment is on.")
for (final Long id : dsIds) { public void weightValidation() throws Exception {
final JSONObject obj = new JSONObject(); final String targetId = testdataFactory.createTarget().getControllerId();
obj.put("id", id); final Long dsId = testdataFactory.createDistributionSet().getId();
body.put(obj); final int weight = 98;
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, weight);
final JSONObject bodyInvalid = getAssignmentObject(dsId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1);
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyInvalid.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().collect(Collectors.toList());
assertThat(actions).size().isEqualTo(1);
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
} }
return body;
@Test
@Description("An assignment request containing a valid weight when multi assignment is off.")
public void weightWithSingleAssignment() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, 98);
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
} }
@Test
@Description("An assignment request containing a valid weight when multi assignment is on.")
public void weightWithMultiAssignment() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final int weight = 98;
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, weight);
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().collect(Collectors.toList());
assertThat(actions).size().isEqualTo(1);
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
}
@Test
@Description("Get weight of action")
public void getActionWeight() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final int customWeightHigh = 800;
final int customWeightLow = 300;
assignDistributionSet(dsId, targetId);
enableMultiAssignments();
assignDistributionSet(dsId, targetId, customWeightHigh);
assignDistributionSet(dsId, targetId, customWeightLow);
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("content.[0].weight").doesNotExist())
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightLow)))
.andExpect(jsonPath("content.[2].weight", equalTo(customWeightHigh)));
}
} }

View File

@@ -83,6 +83,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
} }
private static HttpStatus getStatusOrDefault(final SpServerError error) { private static HttpStatus getStatusOrDefault(final SpServerError error) {

View File

@@ -449,25 +449,27 @@ public abstract class JsonBuilder {
public static String rollout(final String name, final String description, final int groupSize, public static String rollout(final String name, final String description, final int groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) { final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null); return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null,
null);
} }
public static String rollout(final String name, final String description, final Integer groupSize, public static String rollout(final String name, final String description, final Integer groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions, final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final List<RolloutGroup> groups) { final List<RolloutGroup> groups) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, groups, null); return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, groups, null,
null);
} }
public static String rollout(final String name, final String description, final Integer groupSize, public static String rollout(final String name, final String description, final Integer groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions, final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final String type) { final String type) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type); return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, type,
null);
} }
public static String rollout(final String name, final String description, final Integer groupSize, public static String rollout(final String name, final String description, final Integer groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions, final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final List<RolloutGroup> groups, final String type) { final List<RolloutGroup> groups, final String type, final Integer weight) {
final JSONObject json = new JSONObject(); final JSONObject json = new JSONObject();
try { try {
@@ -481,6 +483,10 @@ public abstract class JsonBuilder {
json.put("type", type); json.put("type", type);
} }
if (weight != null) {
json.put("weight", weight);
}
if (conditions != null) { if (conditions != null) {
final JSONObject successCondition = new JSONObject(); final JSONObject successCondition = new JSONObject();

View File

@@ -25,11 +25,13 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration; import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration; import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration; import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -37,6 +39,7 @@ import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
import org.eclipse.hawkbit.rest.RestConfiguration; import org.eclipse.hawkbit.rest.RestConfiguration;
import org.eclipse.hawkbit.rest.util.FilterHttpResponse; import org.eclipse.hawkbit.rest.util.FilterHttpResponse;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -98,8 +101,13 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
} }
public static MyFieldFieldDesc requestFieldWithPath(final String path, final boolean mandatory) { public static MyFieldFieldDesc requestFieldWithPath(final String path, final boolean mandatory) {
return requestFieldWithPath(path, mandatory, mandatory ? "X" : "");
}
private static MyFieldFieldDesc requestFieldWithPath(final String path, final boolean mandatory,
final String mandatoryMessage) {
final MyFieldFieldDesc myFieldDesc = new MyFieldFieldDesc(path); final MyFieldFieldDesc myFieldDesc = new MyFieldFieldDesc(path);
myFieldDesc.attributes(key("mandatory").value(mandatory ? "X" : "")); myFieldDesc.attributes(key("mandatory").value(mandatoryMessage));
// defaults // defaults
myFieldDesc.attributes(key("value").value("")); myFieldDesc.attributes(key("value").value(""));
@@ -118,6 +126,10 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
return requestFieldWithPath(path, false); return requestFieldWithPath(path, false);
} }
public static MyFieldFieldDesc requestFieldWithPathMandatoryInMultiAssignMode(final String path) {
return requestFieldWithPath(path, false, "when multi-assignment is enabled");
}
public static class MyFieldFieldDesc extends SubsectionDescriptor { public static class MyFieldFieldDesc extends SubsectionDescriptor {
/** /**
@@ -154,11 +166,17 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name) final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name)
.status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name) .status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name)
.lastTargetQuery(System.currentTimeMillis())); .lastTargetQuery(System.currentTimeMillis()));
final DeploymentRequestBuilder deploymentRequestBuilder = DeploymentManagement
final List<Target> updatedTargets = maintenanceWindowSchedule == null .deploymentRequest(savedTarget.getControllerId(), distributionSet.getId())
? assignWithoutMaintenanceWindow(distributionSet, savedTarget, timeforced) .setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
: assignWithMaintenanceWindow(distributionSet, savedTarget, timeforced, maintenanceWindowSchedule, if (timeforced) {
maintenanceWindowDuration, maintenanceWindowTimeZone); deploymentRequestBuilder.setActionType(ActionType.TIMEFORCED);
}
if (isMultiAssignmentsEnabled()) {
deploymentRequestBuilder.setWeight(600);
}
final List<Target> updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity()
.stream().map(Action::getTarget).collect(Collectors.toList());
if (inSync) { if (inSync) {
feedbackToByInSync(distributionSet); feedbackToByInSync(distributionSet);
@@ -167,28 +185,6 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
return updatedTargets.get(0); return updatedTargets.get(0);
} }
private List<Target> assignWithoutMaintenanceWindow(final DistributionSet distributionSet, final Target savedTarget,
final boolean timeforced) {
final List<Action> actions = timeforced
? assignDistributionSet(distributionSet.getId(), savedTarget.getControllerId(), ActionType.TIMEFORCED)
.getAssignedEntity()
: assignDistributionSet(distributionSet, savedTarget).getAssignedEntity();
return actions.stream().map(Action::getTarget).collect(Collectors.toList());
}
private List<Target> assignWithMaintenanceWindow(final DistributionSet distributionSet, final Target savedTarget,
final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
final List<Action> actions = timeforced
? assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), savedTarget.getControllerId(),
ActionType.TIMEFORCED, maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone).getAssignedEntity()
: assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), savedTarget.getControllerId(),
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)
.getAssignedEntity();
return actions.stream().map(Action::getTarget).collect(Collectors.toList());
}
protected DistributionSet createDistributionSet() { protected DistributionSet createDistributionSet() {
DistributionSet distributionSet = testdataFactory.createDistributionSet(""); DistributionSet distributionSet = testdataFactory.createDistributionSet("");
distributionSet = distributionSetManagement.update(entityFactory.distributionSet() distributionSet = distributionSetManagement.update(entityFactory.distributionSet()
@@ -314,4 +310,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
parameterWithName("q").description(ApiModelPropertiesGeneric.FIQL)); parameterWithName("q").description(ApiModelPropertiesGeneric.FIQL));
} }
protected boolean isMultiAssignmentsEnabled() {
return Boolean.TRUE.equals(tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
}
} }

View File

@@ -109,6 +109,8 @@ public final class MgmtApiModelProperties {
public static final String ROLLOUT_LINKS_GROUPS = "Link to retrieve the groups a rollout"; public static final String ROLLOUT_LINKS_GROUPS = "Link to retrieve the groups a rollout";
public static final String ROLLOUT_START_ASYNC = "Start the rollout asynchronous"; public static final String ROLLOUT_START_ASYNC = "Start the rollout asynchronous";
public static final String RESULTING_ACTIONS_WEIGHT = "Weight of the resulting Actions";
public static final String UPDATE_STATUS = "Current update status of the target."; public static final String UPDATE_STATUS = "Current update status of the target.";
public static final String TARGET_ATTRIBUTES = "Target attributes."; public static final String TARGET_ATTRIBUTES = "Target attributes.";
@@ -138,6 +140,8 @@ public final class MgmtApiModelProperties {
public static final String ACTION_LIST = "List of actions."; public static final String ACTION_LIST = "List of actions.";
public static final String ACTION_WEIGHT = "Weight of the action showing the importance of the update.";
public static final String IP_ADDRESS = "Last known IP address of the target. Only presented if IP address of the target itself is known (connected directly through DDI API)."; public static final String IP_ADDRESS = "Last known IP address of the target. Only presented if IP address of the target itself is known (connected directly through DDI API).";
public static final String ADDRESS = "The last known address URI of the target. Includes information of the target is connected either directly (DDI) through HTTP or indirectly (DMF) through amqp."; public static final String ADDRESS = "The last known address URI of the target. Includes information of the target is connected either directly (DDI) through HTTP or indirectly (DMF) through amqp.";
@@ -188,6 +192,7 @@ public final class MgmtApiModelProperties {
// request parameter // request parameter
public static final String FORCETIME = "Forcetime in milliseconds."; public static final String FORCETIME = "Forcetime in milliseconds.";
public static final String FORCE = "Force as boolean."; public static final String FORCE = "Force as boolean.";
public static final String ASSIGNMENT_WEIGHT = "Importance of the assignment.";
public static final String ASSIGNMENT_TYPE = "The type of the assignment."; public static final String ASSIGNMENT_TYPE = "The type of the assignment.";
public static final String TARGET_ASSIGNED = "The number of targets that have been assigned as part of this operation."; public static final String TARGET_ASSIGNED = "The number of targets that have been assigned as part of this operation.";
public static final String TARGET_ASSIGNED_ALREADY = "The number of targets which already had been the assignment."; public static final String TARGET_ASSIGNED_ALREADY = "The number of targets which already had been the assignment.";

View File

@@ -385,6 +385,9 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()), .description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields( requestFields(
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID), requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPathMandatoryInMultiAssignMode("[].weight")
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
.type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")),
optionalRequestFieldWithPath("[].forcetime") optionalRequestFieldWithPath("[].forcetime")
.description(MgmtApiModelProperties.FORCETIME), .description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("[].maintenanceWindow") optionalRequestFieldWithPath("[].maintenanceWindow")

View File

@@ -23,10 +23,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
@@ -52,6 +51,8 @@ import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.Snippet;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
import io.qameta.allure.Story; import io.qameta.allure.Story;
@@ -80,7 +81,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
@Description("Handles the GET request of retrieving all rollouts. Required Permission: " @Description("Handles the GET request of retrieving all rollouts. Required Permission: "
+ SpPermission.READ_ROLLOUT) + SpPermission.READ_ROLLOUT)
public void getRollouts() throws Exception { public void getRollouts() throws Exception {
enableMultiAssignments();
createRolloutEntity(); createRolloutEntity();
mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING).accept(MediaTypes.HAL_JSON_VALUE)) mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING).accept(MediaTypes.HAL_JSON_VALUE))
@@ -113,6 +114,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "id").description(ApiModelPropertiesGeneric.ITEM_ID)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "id").description(ApiModelPropertiesGeneric.ITEM_ID));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "name").description(ApiModelPropertiesGeneric.NAME)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "name").description(ApiModelPropertiesGeneric.NAME));
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "weight")
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT).type(JsonFieldType.NUMBER).optional());
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "deleted").description(ApiModelPropertiesGeneric.DELETED)); allFieldDescriptor.add(fieldWithPath(arrayPrefix + "deleted").description(ApiModelPropertiesGeneric.DELETED));
allFieldDescriptor allFieldDescriptor
.add(fieldWithPath(arrayPrefix + "description").description(ApiModelPropertiesGeneric.DESCRPTION)); .add(fieldWithPath(arrayPrefix + "description").description(ApiModelPropertiesGeneric.DESCRPTION));
@@ -159,7 +162,7 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
@Description("Handles the GET request of retrieving a single rollout. Required Permission: " @Description("Handles the GET request of retrieving a single rollout. Required Permission: "
+ SpPermission.READ_ROLLOUT) + SpPermission.READ_ROLLOUT)
public void getRollout() throws Exception { public void getRollout() throws Exception {
enableMultiAssignments();
final Rollout rollout = createRolloutEntity(); final Rollout rollout = createRolloutEntity();
mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}", rollout.getId()) mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}", rollout.getId())
@@ -195,19 +198,19 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.contentType(MediaTypes.HAL_JSON_UTF8).accept(MediaTypes.HAL_JSON_VALUE)) .contentType(MediaTypes.HAL_JSON_UTF8).accept(MediaTypes.HAL_JSON_VALUE))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8)) .andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8))
.andDo(this.document.document( .andDo(this.document.document(requestFields(
requestFields(requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME), requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
optionalRequestFieldWithPath("type").description( requestFieldWithPathMandatoryInMultiAssignMode("weight").type(JsonFieldType.NUMBER)
MgmtApiModelProperties.ROLLOUT_TYPE).attributes( .description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")), .attributes(key("value").value("0 - 1000")),
requestFieldWithPath("distributionSetId") requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
.description(MgmtApiModelProperties.ROLLOUT_DS_ID), optionalRequestFieldWithPath("type").description(MgmtApiModelProperties.ROLLOUT_TYPE)
.attributes(key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")),
requestFieldWithPath("distributionSetId").description(MgmtApiModelProperties.ROLLOUT_DS_ID),
requestFieldWithPath("targetFilterQuery") requestFieldWithPath("targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY), .description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
requestFieldWithPath("amountGroups") requestFieldWithPath("amountGroups").description(MgmtApiModelProperties.ROLLOUT_AMOUNT_GROUPS),
.description(MgmtApiModelProperties.ROLLOUT_AMOUNT_GROUPS), optionalRequestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition") optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION), .description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition") optionalRequestFieldWithPath("successCondition.condition")
@@ -283,12 +286,18 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.contentType(MediaType.APPLICATION_JSON).accept(MediaTypes.HAL_JSON_VALUE)) .contentType(MediaType.APPLICATION_JSON).accept(MediaTypes.HAL_JSON_VALUE))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8)) .andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8))
.andDo(this.document.document(requestFields( .andDo(this.document.document(
requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME), requestFields(requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPath("distributionSetId").description(MgmtApiModelProperties.ROLLOUT_DS_ID), requestFieldWithPathMandatoryInMultiAssignMode("weight")
.type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
.attributes(key("value").value("0 - 1000")),
requestFieldWithPath("distributionSetId")
.description(MgmtApiModelProperties.ROLLOUT_DS_ID),
requestFieldWithPath("targetFilterQuery") requestFieldWithPath("targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY), .description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
optionalRequestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION), optionalRequestFieldWithPath("description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition") optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION), .description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition") optionalRequestFieldWithPath("successCondition.condition")
@@ -317,10 +326,10 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.attributes(key("value").value("['pause']")), .attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("errorAction.expression") optionalRequestFieldWithPath("errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP), .description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP),
requestFieldWithPath("groups").description(MgmtApiModelProperties.ROLLOUT_GROUPS), requestFieldWithPath("groups").description(MgmtApiModelProperties.ROLLOUT_GROUPS),
requestFieldWithPath("groups[].name").description(ApiModelPropertiesGeneric.NAME), requestFieldWithPath("groups[].name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPath("groups[].description").description(ApiModelPropertiesGeneric.DESCRPTION), requestFieldWithPath("groups[].description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("groups[].targetFilterQuery") optionalRequestFieldWithPath("groups[].targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_GROUP_FILTER_QUERY), .description(MgmtApiModelProperties.ROLLOUT_GROUP_FILTER_QUERY),
optionalRequestFieldWithPath("groups[].targetPercentage") optionalRequestFieldWithPath("groups[].targetPercentage")
@@ -626,11 +635,13 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
private Rollout createRolloutEntity() { private Rollout createRolloutEntity() {
testdataFactory.createTargets(20, "exampleTarget"); testdataFactory.createTargets(20, "exampleTarget");
final Rollout rollout = rolloutManagement.create( final RolloutCreate rolloutCreate = entityFactory.rollout().create().name("exampleRollout")
entityFactory.rollout().create().name("exampleRollout") .targetFilterQuery("controllerId==exampleTarget*").set(testdataFactory.createDistributionSet());
.targetFilterQuery("controllerId==exampleTarget*").set(testdataFactory.createDistributionSet()), if (isMultiAssignmentsEnabled()) {
10, new RolloutGroupConditionBuilder().withDefaults() rolloutCreate.weight(400);
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build()); }
final Rollout rollout = rolloutManagement.create(rolloutCreate, 10, new RolloutGroupConditionBuilder()
.withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
// Run here, because Scheduler is disabled during tests // Run here, because Scheduler is disabled during tests
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();

View File

@@ -31,6 +31,7 @@ import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric; import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
import org.eclipse.hawkbit.rest.documentation.MgmtApiModelProperties; import org.eclipse.hawkbit.rest.documentation.MgmtApiModelProperties;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -183,15 +184,16 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
@Test @Test
@Description("Handles the POST request of setting a distribution set for auto assignment within SP. Required Permission: CREATE_TARGET.") @Description("Handles the POST request of setting a distribution set for auto assignment within SP. Required Permission: CREATE_TARGET.")
public void postAutoAssignDS() throws Exception { public void postAutoAssignDS() throws Exception {
enableMultiAssignments();
final TargetFilterQuery tfq = createTargetFilterQuery(); final TargetFilterQuery tfq = createTargetFilterQuery();
final DistributionSet distributionSet = createDistributionSet(); final DistributionSet distributionSet = createDistributionSet();
final String filterByDistSet = "{\"id\":\"" + distributionSet.getId() + "\", \"type\":\"" final String autoAssignBody = new JSONObject().put("id", distributionSet.getId())
+ MgmtActionType.SOFT.getName() + "\"}"; .put("type", MgmtActionType.SOFT.getName()).put("weight", 200).toString();
this.mockMvc this.mockMvc
.perform( .perform(
post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS", post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
tfq.getId()).contentType(MediaType.APPLICATION_JSON).content(filterByDistSet)) tfq.getId()).contentType(MediaType.APPLICATION_JSON).content(autoAssignBody.toString()))
.andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
.andDo(this.document.document( .andDo(this.document.document(
pathParameters(parameterWithName("targetFilterQueryId") pathParameters(parameterWithName("targetFilterQueryId")
@@ -199,7 +201,10 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.DS_ID), requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.DS_ID),
optionalRequestFieldWithPath("type") optionalRequestFieldWithPath("type")
.description(MgmtApiModelProperties.ACTION_FORCE_TYPE) .description(MgmtApiModelProperties.ACTION_FORCE_TYPE)
.attributes(key("value").value("['forced', 'soft', 'downloadonly']"))), .attributes(key("value").value("['forced', 'soft', 'downloadonly']")),
requestFieldWithPathMandatoryInMultiAssignMode("weight")
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
.attributes(key("value").value("0 - 1000"))),
getResponseFieldTargetFilterQuery(false))); getResponseFieldTargetFilterQuery(false)));
} }
@@ -227,6 +232,9 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
fieldWithPath(arrayPrefix + "autoAssignActionType") fieldWithPath(arrayPrefix + "autoAssignActionType")
.description(MgmtApiModelProperties.ACTION_FORCE_TYPE).type(JsonFieldType.STRING.toString()) .description(MgmtApiModelProperties.ACTION_FORCE_TYPE).type(JsonFieldType.STRING.toString())
.attributes(key("value").value("['forced', 'soft', 'downloadonly']")), .attributes(key("value").value("['forced', 'soft', 'downloadonly']")),
fieldWithPath(arrayPrefix + "autoAssignWeight")
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
.type(JsonFieldType.NUMBER.toString()),
fieldWithPath(arrayPrefix + "createdAt").description(ApiModelPropertiesGeneric.CREATED_AT), fieldWithPath(arrayPrefix + "createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath(arrayPrefix + "createdBy").description(ApiModelPropertiesGeneric.CREATED_BY), fieldWithPath(arrayPrefix + "createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath(arrayPrefix + "lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT), fieldWithPath(arrayPrefix + "lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT),
@@ -235,7 +243,8 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
.description(MgmtApiModelProperties.TARGET_FILTER_QUERY_LINK_AUTO_ASSIGN_DS)); .description(MgmtApiModelProperties.TARGET_FILTER_QUERY_LINK_AUTO_ASSIGN_DS));
} }
private String createTargetFilterQueryJson(final String name, final String query) throws JsonProcessingException { private String createTargetFilterQueryJson(final String name, final String query)
throws JsonProcessingException {
final Map<String, Object> target = new HashMap<>(); final Map<String, Object> target = new HashMap<>();
target.put("name", name); target.put("name", name);
target.put("query", query); target.put("query", query);
@@ -249,6 +258,7 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
private TargetFilterQuery createTargetFilterQueryWithDS(final DistributionSet distributionSet) { private TargetFilterQuery createTargetFilterQueryWithDS(final DistributionSet distributionSet) {
final TargetFilterQuery targetFilterQuery = createTargetFilterQuery(); final TargetFilterQuery targetFilterQuery = createTargetFilterQuery();
return targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()); return targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()));
} }
} }

View File

@@ -203,6 +203,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test @Test
@Description("Handles the GET request of retrieving the full action history of a specific target. Required Permission: READ_TARGET.") @Description("Handles the GET request of retrieving the full action history of a specific target. Required Permission: READ_TARGET.")
public void getActionsFromTarget() throws Exception { public void getActionsFromTarget() throws Exception {
enableMultiAssignments();
generateActionForTarget(targetId); generateActionForTarget(targetId);
mockMvc.perform( mockMvc.perform(
@@ -229,12 +230,14 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS) .description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
.attributes(key("value").value("['finished', 'pending']")), .attributes(key("value").value("['finished', 'pending']")),
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION), fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID)))); fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT))));
} }
@Test @Test
@Description("Handles the GET request of retrieving the full action history of a specific target with maintenance window. Required Permission: READ_TARGET.") @Description("Handles the GET request of retrieving the full action history of a specific target with maintenance window. Required Permission: READ_TARGET.")
public void getActionsFromTargetWithMaintenanceWindow() throws Exception { public void getActionsFromTargetWithMaintenanceWindow() throws Exception {
enableMultiAssignments();
generateActionForTarget(targetId, true, false, getTestSchedule(2), getTestDuration(1), getTestTimeZone()); generateActionForTarget(targetId, true, false, getTestSchedule(2), getTestDuration(1), getTestTimeZone());
mockMvc.perform( mockMvc.perform(
@@ -262,6 +265,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.attributes(key("value").value("['finished', 'pending']")), .attributes(key("value").value("['finished', 'pending']")),
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION), fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID), fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("content[].maintenanceWindow") fieldWithPath("content[].maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW), .description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
fieldWithPath("content[].maintenanceWindow.schedule") fieldWithPath("content[].maintenanceWindow.schedule")
@@ -317,6 +321,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test @Test
@Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.") @Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.")
public void getActionFromTarget() throws Exception { public void getActionFromTarget() throws Exception {
enableMultiAssignments();
final Action action = generateActionForTarget(targetId, true, true); final Action action = generateActionForTarget(targetId, true, true);
assertThat(deploymentManagement.findAction(action.getId()).get().getActionType()) assertThat(deploymentManagement.findAction(action.getId()).get().getActionType())
.isEqualTo(ActionType.TIMEFORCED); .isEqualTo(ActionType.TIMEFORCED);
@@ -330,6 +335,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY), responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT), fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID), fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY) fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"), .type("String"),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT) fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
@@ -351,6 +357,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test @Test
@Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.") @Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.")
public void getActionFromTargetWithMaintenanceWindow() throws Exception { public void getActionFromTargetWithMaintenanceWindow() throws Exception {
enableMultiAssignments();
final Action action = generateActionForTarget(targetId, true, true, getTestSchedule(2), getTestDuration(1), final Action action = generateActionForTarget(targetId, true, true, getTestSchedule(2), getTestDuration(1),
getTestTimeZone()); getTestTimeZone());
@@ -363,6 +370,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY), responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT), fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID), fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY) fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"), .type("String"),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT) fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
@@ -514,6 +522,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()), .description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields( requestFields(
requestFieldWithPath("id").description(ApiModelPropertiesGeneric.ITEM_ID), requestFieldWithPath("id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPathMandatoryInMultiAssignMode("weight")
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
.type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")),
optionalRequestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME), optionalRequestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("maintenanceWindow") optionalRequestFieldWithPath("maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW), .description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
@@ -537,12 +548,12 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
final long forceTime = System.currentTimeMillis(); final long forceTime = System.currentTimeMillis();
final JSONArray body = new JSONArray(); final JSONArray body = new JSONArray();
body.put(new JSONObject().put("id", sets.get(1).getId()).put("type", "timeforced") body.put(new JSONObject().put("id", sets.get(1).getId()).put("weight", 500).put("type", "timeforced")
.put("forcetime", forceTime) .put("forcetime", forceTime)
.put("maintenanceWindow", new JSONObject().put("schedule", getTestSchedule(100)) .put("maintenanceWindow", new JSONObject().put("schedule", getTestSchedule(100))
.put("duration", getTestDuration(10)).put("timezone", getTestTimeZone()))) .put("duration", getTestDuration(10)).put("timezone", getTestTimeZone())))
.toString(); .toString();
body.put(new JSONObject().put("id", sets.get(0).getId()).put("type", "forced")); body.put(new JSONObject().put("id", sets.get(0).getId()).put("type", "forced").put("weight", 800));
enableMultiAssignments(); enableMultiAssignments();
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/" mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
@@ -555,6 +566,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()), .description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields( requestFields(
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID), requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPathMandatoryInMultiAssignMode("[].weight")
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
.attributes(key("value").value("0 - 1000")),
optionalRequestFieldWithPath("[].forcetime") optionalRequestFieldWithPath("[].forcetime")
.description(MgmtApiModelProperties.FORCETIME), .description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("[].maintenanceWindow") optionalRequestFieldWithPath("[].maintenanceWindow")

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.filtermanagement;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -54,6 +55,7 @@ public class DistributionSetSelectWindow implements CommonDialogWindow.SaveDialo
private final EventBus.UIEventBus eventBus; private final EventBus.UIEventBus eventBus;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
private final TargetFilterQueryManagement targetFilterQueryManagement; private final TargetFilterQueryManagement targetFilterQueryManagement;
private final EntityFactory entityFactory;
private CheckBox checkBox; private CheckBox checkBox;
private ActionTypeOptionGroupAutoAssignmentLayout actionTypeOptionGroupLayout; private ActionTypeOptionGroupAutoAssignmentLayout actionTypeOptionGroupLayout;
@@ -62,12 +64,13 @@ public class DistributionSetSelectWindow implements CommonDialogWindow.SaveDialo
DistributionSetSelectWindow(final VaadinMessageSource i18n, final UIEventBus eventBus, DistributionSetSelectWindow(final VaadinMessageSource i18n, final UIEventBus eventBus,
final UINotification notification, final TargetManagement targetManagement, final UINotification notification, final TargetManagement targetManagement,
final TargetFilterQueryManagement targetFilterQueryManagement) { final TargetFilterQueryManagement targetFilterQueryManagement, final EntityFactory entityFactory) {
this.i18n = i18n; this.i18n = i18n;
this.notification = notification; this.notification = notification;
this.eventBus = eventBus; this.eventBus = eventBus;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement; this.targetFilterQueryManagement = targetFilterQueryManagement;
this.entityFactory = entityFactory;
} }
private VerticalLayout initView() { private VerticalLayout initView() {
@@ -197,7 +200,8 @@ public class DistributionSetSelectWindow implements CommonDialogWindow.SaveDialo
if (dsId != null) { if (dsId != null) {
confirmWithConsequencesDialog(tfq, dsId, actionType); confirmWithConsequencesDialog(tfq, dsId, actionType);
} else { } else {
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryId, null); targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQueryId).ds(null));
eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY);
} }
} }
@@ -206,7 +210,8 @@ public class DistributionSetSelectWindow implements CommonDialogWindow.SaveDialo
final ActionType actionType) { final ActionType actionType) {
final ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, accepted -> { final ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, accepted -> {
if (accepted) { if (accepted) {
targetFilterQueryManagement.updateAutoAssignDSWithActionType(tfq.getId(), dsId, actionType); targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(tfq.getId()).ds(dsId).actionType(actionType));
eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY);
} }
}); });

View File

@@ -70,7 +70,7 @@ public class FilterManagementView extends VerticalLayout implements View {
final AutoCompleteTextFieldComponent queryTextField, final TargetManagement targetManagement) { final AutoCompleteTextFieldComponent queryTextField, final TargetManagement targetManagement) {
this.targetFilterHeader = new TargetFilterHeader(eventBus, filterManagementUIState, permissionChecker, i18n); this.targetFilterHeader = new TargetFilterHeader(eventBus, filterManagementUIState, permissionChecker, i18n);
this.targetFilterTable = new TargetFilterTable(i18n, notification, eventBus, filterManagementUIState, this.targetFilterTable = new TargetFilterTable(i18n, notification, eventBus, filterManagementUIState,
targetFilterQueryManagement, targetManagement, permissionChecker); targetFilterQueryManagement, targetManagement, permissionChecker, entityFactory);
this.createNewFilterHeader = new CreateOrUpdateFilterHeader(i18n, eventBus, filterManagementUIState, this.createNewFilterHeader = new CreateOrUpdateFilterHeader(i18n, eventBus, filterManagementUIState,
targetFilterQueryManagement, permissionChecker, notification, uiProperties, entityFactory, targetFilterQueryManagement, permissionChecker, notification, uiProperties, entityFactory,
queryTextField); queryTextField);

View File

@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -80,7 +81,7 @@ public class TargetFilterTable extends Table {
public TargetFilterTable(final VaadinMessageSource i18n, final UINotification notification, public TargetFilterTable(final VaadinMessageSource i18n, final UINotification notification,
final UIEventBus eventBus, final FilterManagementUIState filterManagementUIState, final UIEventBus eventBus, final FilterManagementUIState filterManagementUIState,
final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement,
final SpPermissionChecker permChecker) { final SpPermissionChecker permChecker, final EntityFactory entityFactory) {
this.i18n = i18n; this.i18n = i18n;
this.notification = notification; this.notification = notification;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -89,7 +90,7 @@ public class TargetFilterTable extends Table {
this.permChecker = permChecker; this.permChecker = permChecker;
this.dsSelectWindow = new DistributionSetSelectWindow(i18n, eventBus, notification, targetManagement, this.dsSelectWindow = new DistributionSetSelectWindow(i18n, eventBus, notification, targetManagement,
targetFilterQueryManagement); targetFilterQueryManagement, entityFactory);
setStyleName("sp-table"); setStyleName("sp-table");
setSizeFull(); setSizeFull();