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

@@ -364,6 +364,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
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
------- | -------------------------------- | ----------------------------------- | -------------------------------------------------------------
@@ -381,6 +383,7 @@ Payload Template (the Java representation is [DmfMultiActionRequest](https://git
```json
[{
"topic": "String",
"weight": long,
"action": {
"actionId": long,
"targetSecurityToken": "String",
@@ -413,6 +416,7 @@ Payload Template (the Java representation is [DmfMultiActionRequest](https://git
},
{
"topic": "String",
"weight": long,
"action": {
"actionId": long,
"targetSecurityToken": "String",
@@ -454,6 +458,7 @@ type=EVENT <br /> tenant=tenant123 <br /> thingId=abc <br /> topic=MULTI\_ACTI
```json
[{
"topic": "DOWNLOAD_AND_INSTALL",
"weight": 600,
"action": {
"actionId":137,
"targetSecurityToken":"bH7XXAprK1ChnLfKSdtlsp7NOlPnZAYY",
@@ -486,6 +491,7 @@ type=EVENT <br /> tenant=tenant123 <br /> thingId=abc <br /> topic=MULTI\_ACTI
},
{
"topic": "DOWNLOAD",
"weight": 500,
"action": {
"actionId":138,
"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.
### 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
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.
* 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**
* 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:
* Pinning a target or Distribution Set in Deployment View.
* Statistics about installed or assigned Distribution Sets.

View File

@@ -256,7 +256,13 @@ public enum SpServerError {
*
*/
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 message;

View File

@@ -21,7 +21,11 @@ public enum ActionFields implements FieldNameProvider, FieldValueConverter<Actio
/**
* The id field.
*/
ID("id");
ID("id"),
/**
* The weight field.
*/
WEIGHT("weight");
private static final String ACTIVE = "pending";
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 -> {
final List<Action> activeActions = deploymentManagement
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId())
.getContent();
.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
activeActions.forEach(action -> action.getDistributionSet().getModules().forEach(
module -> softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata)));
@@ -204,7 +203,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, 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,

View File

@@ -54,7 +54,6 @@ import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -233,8 +232,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
LOG.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target);
} catch (final EntityAlreadyExistsException e) {
throw new AmqpRejectAndDontRequeueException(
"Tried to register previously registered target, message will be ignored!", e);
throw new AmqpRejectAndDontRequeueException("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) {
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)
.collect(Collectors.toSet());
@@ -267,7 +265,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private void sendOldestActionToTarget(final Target target) {
final Optional<Action> actionOptional = controllerManagement
.findOldestActiveActionByTarget(target.getControllerId());
.findActiveActionWithHighestWeight(target.getControllerId());
if (!actionOptional.isPresent()) {
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.")
public void createThing() {
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);
targetIdCaptor = ArgumentCaptor.forClass(String.class);
uriCaptor = ArgumentCaptor.forClass(URI.class);
final ArgumentCaptor<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
final ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
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)),
MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
// verify
assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
@@ -214,7 +216,7 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty());
when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
amqpMessageHandlerService.onMessage(
createMessage(targetProperties, getThingCreatedMessageProperties(knownThingId)),
@@ -488,7 +490,8 @@ public class AmqpMessageHandlerServiceTest {
when(create.status(any())).thenReturn(create);
when(entityFactoryMock.actionStatus()).thenReturn(builder);
// 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);
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()
.getMessageConverter().fromMessage(replyMessage);
Assert.assertThat(dsModules,
SoftwareModuleJsonMatcher.containsExactly(downloadAndUpdateRequest.getSoftwareModules()));
assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId);
}
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(
new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE)));
final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(controllerId));
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken());
}
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest.DmfMultiActionElement;
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.TargetAssignDistributionSetEvent;
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.TargetUpdatedEvent;
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.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
@@ -100,7 +105,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "sendDownloadStatusBeforeWindowStartTime";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet);
assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2),
getTestDuration(10), getTestTimeZone());
@@ -122,7 +127,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet);
assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5),
getTestDuration(10), getTestTimeZone());
@@ -145,7 +150,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "assignDistributionSetMultipleTimes";
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(distributionSet2);
assignDistributionSet(distributionSet2.getId(), controllerId);
assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId);
@@ -171,17 +176,74 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "assignMultipleDsInMultiAssignMode";
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);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
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);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
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
@Description("Handle cancelation process of an action in multi assignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -197,8 +259,8 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "cancelActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId);
final long actionId2 = assignNewDsToTarget(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId, 675);
final long actionId2 = assignNewDsToTarget(controllerId, 343);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(actionId1);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
@@ -229,8 +291,8 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "finishActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId);
final long actionId2 = assignNewDsToTarget(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId, 66);
final long actionId2 = assignNewDsToTarget(controllerId, 767);
final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
@@ -254,11 +316,11 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignDsMultipleTimesInMultiAssignMode";
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);
final Long actionId2 = getFirstAssignedActionId(assignDistributionSet(ds.getId(), controllerId));
final Long actionId2 = getFirstAssignedAction(assignDistributionSet(dsId, controllerId, 775)).getId();
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
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) {
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Long actionId = getFirstAssignedActionId(assignDistributionSet(ds.getId(), controllerId));
return assignNewDsToTarget(controllerId, null);
}
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);
return actionId;
}
@@ -296,15 +364,15 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "startRolloutsWithSameDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds = testdataFactory.createDistributionSet();
final Set<Long> smIds = getSoftwareModuleIds(ds);
final String filterQuery = "controllerId==" + controllerId;
createAndStartRollout(ds, filterQuery);
createAndStartRollout(ds, filterQuery, 122);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds));
createAndStartRollout(ds, filterQuery);
createAndStartRollout(ds, filterQuery, 43);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds, smIds));
}
@@ -327,15 +395,15 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
registerAndAssertTargetWithExistingTenant(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 DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds2 = testdataFactory.createDistributionSet();
final Set<Long> smIds2 = getSoftwareModuleIds(ds2);
createAndStartRollout(ds1, filterQuery);
createAndStartRollout(ds2, filterQuery);
createAndStartRollout(ds1, filterQuery, 12);
createAndStartRollout(ds2, filterQuery, 45);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
createAndStartRollout(ds1, filterQuery);
createAndStartRollout(ds1, filterQuery, 65);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
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) {
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,
filterQuery, ds, "50", "5");
filterQuery, ds, "50", "5", ActionType.FORCED, weight);
rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts();
return rollout;
@@ -483,11 +555,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
}
private List<Entry<Long, EventTopic>> getLatestMultiActionMessageActions(final String expectedControllerId) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
.isEqualTo(expectedControllerId);
final List<DmfMultiActionElement> multiActionRequest = ((DmfMultiActionRequest) getDmfClient()
.getMessageConverter().fromMessage(multiactionMessage)).getElements();
final List<DmfMultiActionElement> multiActionRequest = getLatestMultiActionMessages(expectedControllerId);
return multiActionRequest.stream()
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
.collect(Collectors.toList());

View File

@@ -52,10 +52,11 @@ public class DmfMultiActionRequest {
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();
element.setTopic(topic);
element.setAction(action);
element.setWeight(weight);
addElement(element);
}
@@ -70,6 +71,9 @@ public class DmfMultiActionRequest {
@JsonProperty
private DmfActionRequest action;
@JsonProperty
private int weight;
public DmfActionRequest getAction() {
return action;
}
@@ -89,6 +93,13 @@ public class DmfMultiActionRequest {
this.topic = actionType;
}
public void setWeight(final int weight) {
this.weight = weight;
}
public int getWeight() {
return weight;
}
}
}

View File

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

View File

@@ -14,6 +14,7 @@ import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@@ -76,7 +77,7 @@ public interface DeploymentManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets(
@NotEmpty List<DeploymentRequest> deploymentRequests);
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
/**
* Assigns {@link DistributionSet}s to {@link Target}s according to the
@@ -107,9 +108,9 @@ public interface DeploymentManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
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
* assignment
*
@@ -372,6 +373,30 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
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
* 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
* device cannot be guaranteed.
*
* Note: if this is enforced you have to make sure that the feedback
* channel from the devices is in order.
* Note: if this is enforced you have to make sure that the feedback channel
* from the devices is in order.
*/
private boolean rejectActionStatusForClosedAction;
@@ -54,6 +54,11 @@ public class RepositoryProperties {
*/
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() {
return eagerPollPersistence;
}
@@ -94,4 +99,12 @@ public class RepositoryProperties {
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.
*/
@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
@@ -166,7 +166,7 @@ public interface RolloutManagement {
* exceeded.
*/
@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);
/**
@@ -342,9 +342,9 @@ public interface RolloutManagement {
/**
* Approves or denies a created rollout being in state
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state
* {@link RolloutStatus#APPROVAL_DENIED}
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved,
* it switches state to {@link RolloutStatus#READY}, otherwise it switches
* to state {@link RolloutStatus#APPROVAL_DENIED}
*
* @param rolloutId
* the rollout to be approved or denied.
@@ -365,9 +365,9 @@ public interface RolloutManagement {
/**
* Approves or denies a created rollout being in state
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state
* {@link RolloutStatus#APPROVAL_DENIED}
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved,
* it switches state to {@link RolloutStatus#READY}, otherwise it switches
* to state {@link RolloutStatus#APPROVAL_DENIED}
*
* @param rolloutId
* the rollout to be approved or denied.

View File

@@ -15,6 +15,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull;
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.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -208,7 +209,7 @@ public interface TargetFilterQueryManagement {
* if fields are not filled as specified. Check
* {@link TargetFilterQueryUpdate} for field constraints.
*
* @throws AssignmentQuotaExceededException
* @throws QuotaExceededException
* if the update contains a new query which addresses too many
* targets (auto-assignments only)
*/
@@ -216,46 +217,10 @@ public interface TargetFilterQueryManagement {
TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update);
/**
* Updates the the auto-assign {@link DistributionSet} and sets default
* (FORCED) {@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}
* Updates the auto assign settings of an {@link TargetFilterQuery}.
*
* @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
* @param autoAssignDistributionSetUpdate
* the new auto assignment
*
* @return the updated {@link TargetFilterQuery}
*
@@ -276,5 +241,6 @@ public interface TargetFilterQueryManagement {
* valid (incomplete or soft deleted)
*/
@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 {
/**
* Set name
*
* @param name
* for {@link Rollout#getName()}
* @return updated builder instance
@@ -35,6 +37,8 @@ public interface RolloutCreate {
RolloutCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/**
* Set description
*
* @param description
* for {@link Rollout#getDescription()}
* @return updated builder instance
@@ -42,6 +46,8 @@ public interface RolloutCreate {
RolloutCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/**
* Set the {@link DistributionSet}
*
* @param set
* for {@link Rollout#getDistributionSet()}
* @return updated builder instance
@@ -51,6 +57,8 @@ public interface RolloutCreate {
}
/**
* Set the id of the {@link DistributionSet}
*
* @param setId
* for {@link Rollout#getDistributionSet()}
* @return updated builder instance
@@ -58,6 +66,8 @@ public interface RolloutCreate {
RolloutCreate set(long setId);
/**
* Set the {@link TargetFilterQuery}
*
* @param targetFilterQuery
* for {@link Rollout#getTargetFilterQuery()}
* @return updated builder instance
@@ -66,6 +76,8 @@ public interface RolloutCreate {
@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String targetFilterQuery);
/**
* Set the {@link ActionType}
*
* @param actionType
* for {@link Rollout#getActionType()}
* @return updated builder instance
@@ -73,6 +85,8 @@ public interface RolloutCreate {
RolloutCreate actionType(@NotNull ActionType actionType);
/**
* Set the forcedTime of the resulting {@link Actions}
*
* @param forcedTime
* for {@link Rollout#getForcedTime()}
* @return updated builder instance
@@ -80,6 +94,17 @@ public interface RolloutCreate {
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
* for {@link Rollout#getStartAt()}
* @return updated builder instance

View File

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

View File

@@ -16,12 +16,23 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
*/
public interface TargetFilterQueryBuilder {
/**
* Used to update a {@link TargetFilterQuery}
*
* @param id
* of the updatable entity
* @return builder instance
*/
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
*/

View File

@@ -13,6 +13,7 @@ import java.util.Optional;
import javax.validation.constraints.NotNull;
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.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -27,6 +28,8 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
*/
public interface TargetFilterQueryCreate {
/**
* Set filter name
*
* @param name
* of {@link TargetFilterQuery#getName()}
* @return updated builder instance
@@ -34,6 +37,8 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/**
* Set filter query
*
* @param query
* of {@link TargetFilterQuery#getQuery()}
* @return updated builder instance
@@ -41,6 +46,8 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate query(@Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE) @NotNull String query);
/**
* Set {@link DistributionSet} for auto assignment
*
* @param distributionSet
* for {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return updated builder instance
@@ -50,6 +57,8 @@ public interface TargetFilterQueryCreate {
}
/**
* Set ID of {@link DistributionSet} for auto assignment
*
* @param dsId
* for {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return updated builder instance
@@ -57,12 +66,23 @@ public interface TargetFilterQueryCreate {
TargetFilterQueryCreate autoAssignDistributionSet(Long dsId);
/**
* Set {@link ActionType} for auto assignment
*
* @param actionType
* for {@link TargetFilterQuery#getAutoAssignActionType()}
* @return updated builder instance
*/
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
*/

View File

@@ -32,5 +32,4 @@ public interface TargetFilterQueryUpdate {
* @return updated builder instance
*/
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

@@ -38,7 +38,16 @@ public interface Action extends TenantAwareBaseEntity {
* Maximum length of external reference.
*/
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
*/
@@ -80,6 +89,11 @@ public interface Action extends TenantAwareBaseEntity {
*/
long getForcedTime();
/**
* @return priority of the {@link Action}.
*/
Optional<Integer> getWeight();
/**
* @return rolloutGroup related to this {@link Action}.
*/
@@ -106,10 +120,11 @@ public interface Action extends TenantAwareBaseEntity {
String getMaintenanceWindowTimeZone();
/**
* @param externalRef associated with this action
* @param externalRef
* associated with this action
*/
void setExternalRef(@NotEmpty String externalRef);
/**
* @return externalRef of the action
*/

View File

@@ -10,6 +10,8 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Objects;
import javax.validation.Valid;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -19,6 +21,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
*/
public class DeploymentRequest {
private final Long distributionSetId;
@Valid
private final TargetWithActionType targetWithActionType;
/**
@@ -34,6 +37,8 @@ public class DeploymentRequest {
* specified for the action.
* @param forceTime
* at what time the type soft turns into forced.
* @param weight
* the priority of an {@link Action}.
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* 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.
*/
public DeploymentRequest(final String controllerId, final Long distributionSetId, final ActionType actionType,
final long forceTime, final String maintenanceSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
this.targetWithActionType = new TargetWithActionType(controllerId, actionType, forceTime, maintenanceSchedule,
maintenanceWindowDuration,
maintenanceWindowTimeZone);
final long forceTime, final Integer weight, final String maintenanceSchedule,
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone) {
this.targetWithActionType = new TargetWithActionType(controllerId, actionType, forceTime, weight,
maintenanceSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
this.distributionSetId = distributionSetId;
}
@@ -71,14 +75,13 @@ public class DeploymentRequest {
return targetWithActionType;
}
@Override
public String toString() {
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.getForceTime(), targetWithActionType.getMaintenanceSchedule(),
targetWithActionType.getMaintenanceWindowDuration(),
targetWithActionType.getForceTime(), targetWithActionType.getWeight(),
targetWithActionType.getMaintenanceSchedule(), targetWithActionType.getMaintenanceWindowDuration(),
targetWithActionType.getMaintenanceWindowTimeZone());
}
@@ -86,7 +89,7 @@ public class DeploymentRequest {
public int hashCode() {
return Objects.hash(distributionSetId, targetWithActionType);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {

View File

@@ -18,7 +18,7 @@ public class DeploymentRequestBuilder {
private final String controllerId;
private final Long distributionSetId;
private Integer weight;
private long forceTime = RepositoryModelConstants.NO_FORCE_TIME;
private ActionType actionType = ActionType.FORCED;
private String maintenanceSchedule;
@@ -63,6 +63,18 @@ public class DeploymentRequestBuilder {
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
*
@@ -94,8 +106,8 @@ public class DeploymentRequestBuilder {
* @return the request object
*/
public DeploymentRequest build() {
return new DeploymentRequest(controllerId, distributionSetId, actionType, forceTime, maintenanceSchedule,
maintenanceWindowDuration, maintenanceWindowTimeZone);
return new DeploymentRequest(controllerId, distributionSetId, actionType, forceTime, weight,
maintenanceSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
}
}

View File

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

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -70,4 +71,10 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity {
*/
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 javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -21,6 +24,9 @@ public class TargetWithActionType {
private final String controllerId;
private final ActionType actionType;
private final long forceTime;
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private final Integer weight;
private String maintenanceSchedule;
private String maintenanceWindowDuration;
private String maintenanceWindowTimeZone;
@@ -32,7 +38,7 @@ public class TargetWithActionType {
* ID if the controller
*/
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
* specified for the action.
* @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}
* @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.actionType = actionType != null ? actionType : ActionType.FORCED;
this.forceTime = forceTime;
this.weight = weight;
}
/**
@@ -61,8 +71,10 @@ public class TargetWithActionType {
* @param actionType
* specified for the action.
* @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}
* @param weight
* the priority of an {@link Action}
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* 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.
*/
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) {
this(controllerId, actionType, forceTime);
this(controllerId, actionType, forceTime, weight);
this.maintenanceSchedule = maintenanceSchedule;
this.maintenanceWindowDuration = maintenanceWindowDuration;
@@ -104,6 +116,11 @@ public class TargetWithActionType {
return RepositoryModelConstants.NO_FORCE_TIME;
}
public Integer getWeight() {
return weight;
}
public String getControllerId() {
return controllerId;
}
@@ -138,14 +155,14 @@ public class TargetWithActionType {
@Override
public String toString() {
return "TargetWithActionType [controllerId=" + controllerId + ", actionType=" + getActionType() + ", forceTime="
+ getForceTime() + ", maintenanceSchedule=" + getMaintenanceSchedule() + ", maintenanceWindowDuration="
+ getMaintenanceWindowDuration() + ", maintenanceWindowTimeZone=" + getMaintenanceWindowTimeZone()
+ "]";
+ getForceTime() + ", weight=" + getWeight() + ", maintenanceSchedule=" + getMaintenanceSchedule()
+ ", maintenanceWindowDuration=" + getMaintenanceWindowDuration() + ", maintenanceWindowTimeZone="
+ getMaintenanceWindowTimeZone() + "]";
}
@Override
public int hashCode() {
return Objects.hash(actionType, controllerId, forceTime, maintenanceSchedule, maintenanceWindowDuration,
return Objects.hash(actionType, controllerId, forceTime, weight, maintenanceSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone);
}
@@ -163,10 +180,9 @@ public class TargetWithActionType {
}
final TargetWithActionType other = (TargetWithActionType) obj;
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(maintenanceWindowDuration, other.maintenanceWindowDuration)
&& 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.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.support.locks.LockRegistry;
@@ -54,12 +55,18 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
protected final RolloutApprovalStrategy rolloutApprovalStrategy;
protected final TenantConfigurationManagement tenantConfigurationManagement;
protected final SystemSecurityContext systemSecurityContext;
protected AbstractRolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final LockRegistry lockRegistry,
final RolloutApprovalStrategy rolloutApprovalStrategy) {
final RolloutApprovalStrategy rolloutApprovalStrategy,
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement;
this.rolloutGroupManagement = rolloutGroupManagement;
@@ -70,6 +77,8 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
}
protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
@@ -156,5 +165,4 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
return new AsyncResult<>(validateTargetsInGroups(
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 javax.validation.constraints.Max;
import javax.validation.constraints.Min;
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.DistributionSet;
import org.springframework.util.StringUtils;
/**
@@ -30,26 +35,77 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
protected Long forcedTime;
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) {
this.set = set;
return (T) this;
}
/**
* Filter of the rollout
*
* @param targetFilterQuery
* query
* @return this builder
*/
public T targetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = StringUtils.trimWhitespace(targetFilterQuery);
return (T) this;
}
/**
* {@link ActionType} used for {@link Action}s
*
* @param actionType
* type
* @return this builder
*/
public T actionType(final ActionType actionType) {
this.actionType = actionType;
return (T) this;
}
/**
* forcedTime used for {@link Action}s
*
* @param forcedTime
* time
* @return this builder
*/
public T forcedTime(final Long forcedTime) {
this.forcedTime = forcedTime;
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) {
this.startAt = startAt;
return (T) this;
@@ -67,6 +123,10 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
return Optional.ofNullable(forcedTime);
}
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public Optional<Long> getStartAt() {
return Optional.ofNullable(startAt);
}

View File

@@ -10,8 +10,13 @@ package org.eclipse.hawkbit.repository.builder;
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.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.util.StringUtils;
/**
@@ -31,6 +36,17 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
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) {
this.distributionSetId = distributionSetId;
return (T) this;
@@ -40,15 +56,46 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
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) {
this.actionType = actionType;
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() {
return Optional.ofNullable(actionType);
}
/**
* Set name of the filter
*
* @param name
* of the {@link TargetFilterQuery}
* @return this builder
*/
public T name(final String name) {
this.name = StringUtils.trimWhitespace(name);
return (T) this;
@@ -58,6 +105,13 @@ public abstract class AbstractTargetFilterQueryUpdateCreate<T> extends AbstractB
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) {
this.query = StringUtils.trimWhitespace(query);
return (T) this;

View File

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

View File

@@ -212,6 +212,7 @@ public abstract class AbstractDsAssignmentStrategy {
final JpaAction actionForTarget = new JpaAction();
actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
actionForTarget.setWeight(targetWithActionType.getWeight());
actionForTarget.setActive(true);
actionForTarget.setTarget(target);
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.Pageable;
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.EntityGraphType;
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);
/**
* Retrieves the oldest {@link Action} that is active and referring to the
* given {@link Target}.
*
* @param sort
* order
* Retrieves the active {@link Action}s with the highest weights that refer
* to the given {@link Target}. If {@link Action}s have the same weight they
* are ordered ascending by ID (oldest ones first).
*
* @param pageable
* pageable
* @param controllerId
* the target to find assigned actions
* @param active
* the action active flag
*
* @return the found {@link Action}
* @return the found {@link Action}s
*/
@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

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

View File

@@ -8,7 +8,6 @@
*/
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 java.io.Serializable;
@@ -38,6 +37,7 @@ import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
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.utils.DeploymentHelper;
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.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -103,7 +105,7 @@ import com.google.common.collect.Lists;
*/
@Transactional(readOnly = true)
@Validated
public class JpaDeploymentManagement implements DeploymentManagement {
public class JpaDeploymentManagement extends JpaActionManagement implements DeploymentManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class);
@@ -124,7 +126,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
private final EntityManager entityManager;
private final ActionRepository actionRepository;
private final DistributionSetRepository distributionSetRepository;
private final TargetRepository targetRepository;
private final ActionStatusRepository actionStatusRepository;
@@ -146,9 +147,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
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.actionRepository = actionRepository;
this.distributionSetRepository = distributionSetRepository;
this.targetRepository = targetRepository;
this.actionStatusRepository = actionStatusRepository;
@@ -194,6 +196,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.validate(deploymentRequests);
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
}
@@ -275,8 +279,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID);
final List<String> targetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId).distinct()
.collect(Collectors.toList());
final List<String> targetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().collect(Collectors.toList());
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(targetIds,
distributionSetEntity.getId());
@@ -285,8 +289,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return allTargetsAlreadyAssignedResult(distributionSetEntity, targetsWithActionType.size());
}
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(targetsWithActionType,
actionMessage, assignmentStrategy, distributionSetEntity, targetEntities);
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(targetsWithActionType, actionMessage,
assignmentStrategy, distributionSetEntity, targetEntities);
return buildAssignmentResult(distributionSetEntity, assignedActions, targetsWithActionType.size());
}
@@ -659,6 +663,16 @@ public class JpaDeploymentManagement implements DeploymentManagement {
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
public long countActionsByTarget(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId);
@@ -823,7 +837,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
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) {

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
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.RolloutCreate;
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.exception.EntityNotFoundException;
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.jpa.configuration.Constants;
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.utils.DeploymentHelper;
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.ActionType;
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.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -165,9 +171,12 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
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,
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy);
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy,
tenantConfigurationManagement, systemSecurityContext);
this.eventPublisherHolder = eventPublisherHolder;
this.database = database;
}
@@ -226,7 +235,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}
private JpaRollout createRollout(final JpaRollout rollout) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final Long totalTargets = targetManagement.countByRsql(rollout.getTargetFilterQuery());
if (totalTargets == 0) {
throw new ValidationException("Rollout does not match any existing targets");
@@ -618,6 +627,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
action.setStatus(Status.SCHEDULED);
action.setRollout(rollout);
action.setRolloutGroup(rolloutGroup);
rollout.getWeight().ifPresent(action::setWeight);
actionRepository.save(action);
});
}
@@ -1008,6 +1018,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
update.getDescription().ifPresent(rollout::setDescription);
update.getActionType().ifPresent(rollout::setActionType);
update.getForcedTime().ifPresent(rollout::setForcedTime);
update.getWeight().ifPresent(rollout::setWeight);
update.getStartAt().ifPresent(rollout::setStartAt);
update.getSet().ifPresent(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,
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.TargetFilterQueryFields;
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.TargetFilterQueryCreate;
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.TargetFilterQuerySpecification;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -67,19 +72,24 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
private final DistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private final Database database;
JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final Database database) {
final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetRepository = targetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.database = database;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
}
@Override
@@ -92,6 +102,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
// enforce the 'max targets per auto assign' quota right here even if
// the result of the filter query can vary over time
if (create.getAutoAssignDistributionSetId().isPresent()) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(create);
create.getQuery().ifPresent(this::assertMaxTargetsQuota);
}
@@ -222,30 +233,26 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
@Override
@Transactional
public TargetFilterQuery updateAutoAssignDSWithActionType(final long queryId, final Long dsId,
final ActionType actionType) {
final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(queryId);
if (dsId == null) {
public TargetFilterQuery updateAutoAssignDS(final AutoAssignDistributionSetUpdate update) {
final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(
update.getTargetFilterId());
if (update.getDsId() == null) {
targetFilterQuery.setAutoAssignDistributionSet(null);
targetFilterQuery.setAutoAssignActionType(null);
targetFilterQuery.setAutoAssignWeight(null);
} else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
// we cannot be sure that the quota was enforced at creation time
// because the Target Filter Query REST API does not allow to
// specify an
// auto-assign distribution set when creating a target filter query
assertMaxTargetsQuota(targetFilterQuery.getQuery());
final JpaDistributionSet distributionSetToAutoAssign = findDistributionSetAndThrowExceptionIfNotFound(dsId);
// must be completed and not soft deleted
verifyDistributionSetAndThrowExceptionIfNotValid(distributionSetToAutoAssign);
targetFilterQuery.setAutoAssignDistributionSet(distributionSetToAutoAssign);
// the action type is set to FORCED per default (when not explicitly
// specified)
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(actionType));
final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId());
verifyDistributionSetAndThrowExceptionIfNotValid(ds);
targetFilterQuery.setAutoAssignDistributionSet(ds);
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
targetFilterQuery.setAutoAssignWeight(update.getWeight());
}
return targetFilterQueryRepository.save(targetFilterQuery);
}
@@ -288,5 +295,4 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetRepository.count(RSQLUtility.parse(query, TargetFields.class, virtualPropertyReplacer, database)),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
}
}

View File

@@ -541,9 +541,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetRepository targetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final JpaProperties properties) {
final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
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 EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
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,
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 TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
final JpaProperties properties) {
final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer,
txManager, tenantConfigurationManagement, quotaManagement, systemSecurityContext, tenantAware,
properties.getDatabase());
properties.getDatabase(), repositoryProperties);
}
/**
@@ -686,8 +691,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean
@ConditionalOnMissingBean
ControllerManagement controllerManagement(final ScheduledExecutorService executorService,
final RepositoryProperties repositoryProperties) {
return new JpaControllerManagement(executorService, repositoryProperties);
final RepositoryProperties repositoryProperties, final ActionRepository actionRepository) {
return new JpaControllerManagement(executorService, repositoryProperties, actionRepository);
}
@Bean

View File

@@ -97,6 +97,8 @@ public class AutoAssignChecker {
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) {
checkByTargetFilterQueryAndAssignDS(filterQuery);
}
@@ -143,7 +145,8 @@ public class AutoAssignChecker {
return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets",
Isolation.READ_COMMITTED.value(), status -> {
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();
if (count > 0) {
deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage);
@@ -168,17 +171,15 @@ public class AutoAssignChecker {
* @return list of targets with action type
*/
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,
targetFilterQuery);
// the action type is set to FORCED per default (when not explicitly
// specified)
final ActionType autoAssignActionType = type == null ? ActionType.FORCED : type;
return targets.getContent().stream()
.map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
.setActionType(autoAssignActionType).build())
.collect(Collectors.toList());
return targets.getContent().stream().map(t -> DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
.setActionType(autoAssignActionType).setWeight(weight).build()).collect(Collectors.toList());
}
}

View File

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

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa.builder;
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.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
@@ -36,4 +37,9 @@ public class JpaTargetFilterQueryBuilder implements TargetFilterQueryBuilder {
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,
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) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -104,7 +104,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(controllerManagement.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(),
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.getId(), "XXX")).isFalse();
@@ -457,7 +457,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.getContent();
assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus);
if (actionActive) {
assertThat(controllerManagement.findOldestActiveActionByTarget(controllerId).get().getId())
assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId())
.isEqualTo(actionId);
}
}
@@ -501,7 +501,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Register a controller which does not exist")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 2)})
@Expect(type = TargetPollEvent.class, count = 2) })
public void findOrRegisterTargetIfItDoesNotExist() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull();
@@ -517,7 +517,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetPollEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 1) })
public void findOrRegisterTargetIfItDoesNotExistWithName() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST, "TestName");
final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST,
final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST,
"ChangedTestName");
assertThat(target.getId()).as("Target should be the equals").isEqualTo(sameTarget.getId());
assertThat(target.getName()).as("Taget names should be different").isNotEqualTo(sameTarget.getName());
@@ -560,8 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any());
}
finally {
} finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -597,22 +596,23 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@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")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 3) ,
@Expect(type = TargetUpdatedEvent.class, count = 1)})
@Expect(type = TargetPollEvent.class, count = 3), @Expect(type = TargetUpdatedEvent.class, count = 1) })
public void findOrRegisterTargetIfItDoesNotExistDoesUpdateNameOnExistingTargetProperly() {
String controllerId = "12345";
String targetName = "UpdatedName";
final String controllerId = "12345";
final String targetName = "UpdatedName";
final Target newTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST);
final Target newTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST);
assertThat(newTarget.getName()).isEqualTo(controllerId);
Target firstTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST, targetName);
final Target firstTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId,
LOCALHOST, targetName);
assertThat(firstTimeUpdatedTarget.getName()).isEqualTo(targetName);
//Name should not change to default (name=targetId) if target is updated without new name provided
Target secondTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, LOCALHOST);
// Name should not change to default (name=targetId) if target is
// updated without new name provided
final Target secondTimeUpdatedTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId,
LOCALHOST);
assertThat(secondTimeUpdatedTarget.getName()).isEqualTo(targetName);
}
@@ -633,8 +633,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
verify(mockTargetRepository, times(1)).save(any());
}
finally {
} finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -651,13 +650,11 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class);
try {
assertThatExceptionOfType(RuntimeException.class)
.as("Expected a RuntimeException to be thrown!")
assertThatExceptionOfType(RuntimeException.class).as("Expected a RuntimeException to be thrown!")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId",
LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
}
finally {
} finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -1054,9 +1051,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
Collections.singletonMap(keyValid, valueTooLong), null));
assertThatExceptionOfType(InvalidTargetAttributeException.class)
.as("Attribute with key NULL should not be created")
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId,
Collections.singletonMap(keyNull, valueValid), null));
.as("Attribute with key NULL should not be created").isThrownBy(() -> controllerManagement
.updateControllerAttributes(controllerId, Collections.singletonMap(keyNull, valueValid), null));
}
@Test
@@ -1298,9 +1294,9 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId).isNotNull();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates")
.isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates").isThrownBy(
() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates")
@@ -1326,18 +1322,18 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId).isNotNull();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates")
.isThrownBy( ()-> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
.as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates").isThrownBy(
() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.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))));
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.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))));
}
@@ -1472,6 +1468,39 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
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) {
final Optional<Target> target = controllerManagement.getByControllerId(controllerId);
assertThat(target).isPresent();
@@ -1494,12 +1523,10 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false);
}
@Test
@Description("Delete a target on requested target deletion from client side")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TargetDeletedEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetDeletedEvent.class, count = 1) })
public void deleteTargetWithValidThingId() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull();
@@ -1512,7 +1539,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
@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() {
assertThatExceptionOfType(EntityNotFoundException.class)
.as("No EntityNotFoundException thrown when deleting a non-existing target")
@@ -1522,9 +1549,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Delete a target after it has been deleted already")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TargetDeletedEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1), @Expect(type = TargetDeletedEvent.class, count = 1) })
public void deleteTargetAfterItWasDeleted() {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
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.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -171,7 +173,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds1, Collections.singletonList(testTarget)));
.isThrownBy(() -> assignDistributionSet(ds1.getId(), testTarget.getControllerId(), 77));
}
@Test
@@ -563,16 +565,17 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public void previousAssignmentsAreNotCanceledInMultiAssignMode() {
enableMultiAssignments();
final List<Target> targets = testdataFactory.createTargets(10);
final List<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toList());
// First assignment
final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1");
assignDistributionSet(ds1, targets);
assignDistributionSet(ds1.getId(), targetIds, 77);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
// Second assignment
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, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
@@ -601,7 +604,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public void multiassignmentInOneRequest() {
final List<Target> targets = testdataFactory.createTargets(2);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement
@@ -614,7 +617,14 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.stream().map(action -> action.getDistributionSet().getId()).collect(Collectors.toList());
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
@@ -624,9 +634,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
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
.deploymentRequest(target.getControllerId(), distributionSets.get(1).getId()).build();
.deploymentRequest(target.getControllerId(), distributionSets.get(1).getId()).setWeight(565).build();
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(Arrays.asList(targetToDS0, targetToDS1)));
@@ -638,17 +649,26 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test
@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() {
final Target target = testdataFactory.createTarget();
final DistributionSet ds = testdataFactory.createDistributionSet();
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2,
DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).build());
DeploymentManagement.deploymentRequest(targetId, dsId).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments)))
.isEqualTo(1);
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);
}
@@ -668,7 +688,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> deploymentRequests = Collections.nCopies(maxActions + 1,
DeploymentManagement.deploymentRequest(controllerId, dsId).build());
DeploymentManagement.deploymentRequest(controllerId, dsId).setWeight(24).build());
enableMultiAssignments();
Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class)
@@ -676,6 +696,67 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
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
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
@@ -1143,13 +1224,15 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Tests the computation of already assigned entities returned as a result of an assignment")
public void testAlreadyAssignedAndAssignedActionsInAssignmentResult(){
// create target1, distributionSet, assign ds to target1 and finish update (close all actions)
public void testAlreadyAssignedAndAssignedActionsInAssignmentResult() {
// create target1, distributionSet, assign ds to target1 and finish
// update (close all actions)
final Action action = prepareFinishedUpdate("target1", "ds", false);
final Target target2 = testdataFactory.createTarget("target2");
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());
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(
@@ -1283,7 +1366,5 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
public List<Long> getUndeployedTargetIDs() {
return undeployedTargetIDs;
}
}
}

View File

@@ -17,12 +17,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
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.exception.EntityAlreadyExistsException;
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.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -126,7 +130,6 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Action rolloutCreatedAction = actionsByKnownTarget.stream()
.filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get();
assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.FINISHED);
}
@Test
@@ -599,7 +602,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final String successCondition = "50";
final String errorCondition = "80";
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition, ActionType.DOWNLOAD_ONLY);
amountOtherTargets, amountGroups, successCondition, errorCondition, ActionType.DOWNLOAD_ONLY, null);
// targets have not started
Map<TotalTargetCountStatus.Status, Long> validationMap = createInitStatusMap();
@@ -619,7 +622,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
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.put(TotalTargetCountStatus.Status.SCHEDULED, 4L);
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L);
@@ -628,7 +632,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
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.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L);
@@ -637,7 +642,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.DOWNLOADED);
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.put(TotalTargetCountStatus.Status.FINISHED, 6L);
validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
@@ -645,7 +651,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
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.put(TotalTargetCountStatus.Status.FINISHED, 8L);
validateRolloutActionStatus(createdRollout.getId(), validationMap);
@@ -1745,14 +1752,80 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// verify that all running actions keep running
assertThat(actionRepository.findByRolloutIdAndStatus(PAGE, deletedRollout.getId(), Status.RUNNING)
.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,
final String targetFilter) {
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")
.targetPercentage(Float.valueOf(percentage)).targetFilterQuery(targetFilter);
}
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 String errorCondition) {
return createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountOtherTargets,
groupSize, successCondition, errorCondition, ActionType.FORCED);
groupSize, successCondition, errorCondition, ActionType.FORCED, null);
}
private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
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");
testdataFactory.createTargets(amountTargetsForRollout, "rollout-", "rollout");
testdataFactory.createTargets(amountOtherTargets, "others-", "rollout");
final String filterQuery = "controllerId==rollout-*";
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,
final int groupSize, final String successCondition, final String errorCondition, final String rolloutName,
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);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
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) {

View File

@@ -20,6 +20,9 @@ import static org.junit.Assert.fail;
import java.util.Arrays;
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.event.remote.entity.DistributionSetCreatedEvent;
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.InvalidAutoAssignActionTypeException;
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.RSQLParameterUnsupportedFieldException;
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.Target;
@@ -82,13 +87,18 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
verifyThrownExceptionBy(
() -> targetFilterQueryManagement.update(entityFactory.targetFilterQuery().update(NOT_EXIST_IDL)),
"TargetFilterQuery");
verifyThrownExceptionBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), NOT_EXIST_IDL),
verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId()).ds(NOT_EXIST_IDL)),
"DistributionSet");
verifyThrownExceptionBy(() -> targetFilterQueryManagement.updateAutoAssignDS(NOT_EXIST_IDL, set.getId()),
"TargetFilterQuery");
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");
}
@@ -208,23 +218,26 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
@Step
private void verifyAutoAssignmentWithDefaultActionType(final String filterName,
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);
}
@Step
private void verifyAutoAssignmentWithSoftActionType(final String filterName,
final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) {
targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), distributionSet.getId(),
ActionType.SOFT);
targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()).actionType(ActionType.SOFT));
verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.SOFT);
}
@Step
private void verifyAutoAssignmentWithDownloadOnlyActionType(final String filterName,
final TargetFilterQuery targetFilterQuery, final DistributionSet distributionSet) {
targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(), distributionSet.getId(),
ActionType.DOWNLOAD_ONLY);
targetFilterQueryManagement
.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId())
.ds(distributionSet.getId()).actionType(ActionType.DOWNLOAD_ONLY));
verifyAutoAssignDsAndActionType(filterName, distributionSet, ActionType.DOWNLOAD_ONLY);
}
@@ -233,9 +246,11 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
final DistributionSet distributionSet) {
// assigning a distribution set with TIMEFORCED action is supposed to
// fail as only FORCED and SOFT action types are allowed
assertThatExceptionOfType(InvalidAutoAssignActionTypeException.class).isThrownBy(
() -> targetFilterQueryManagement.updateAutoAssignDSWithActionType(targetFilterQuery.getId(),
distributionSet.getId(), ActionType.TIMEFORCED));
assertThatExceptionOfType(InvalidAutoAssignActionTypeException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
entityFactory.targetFilterQuery().updateAutoAssign(targetFilterQuery.getId())
.ds(distributionSet.getId()).actionType(ActionType.TIMEFORCED)));
}
@Step
@@ -245,8 +260,8 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
.type(testdataFactory.findOrCreateDefaultTestDsType()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(),
incompleteDistributionSet.getId()));
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(incompleteDistributionSet.getId())));
}
@Step
@@ -255,8 +270,9 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs"));
distributionSetManagement.delete(softDeletedDs.getId());
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery.getId(), softDeletedDs.getId()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(softDeletedDs.getId())));
}
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
// 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
@@ -313,7 +331,8 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
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
TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get();
@@ -340,9 +359,11 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
// later step
assignDistributionSet(distributionSet.getId(), target.getControllerId());
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
final Long filterId = targetFilterQueryManagement
.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
TargetFilterQuery tfq = targetFilterQueryManagement.getByName(filterName).get();
@@ -372,20 +393,18 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet("2");
final TargetFilterQuery tfq = targetFilterQueryManagement.updateAutoAssignDSWithActionType(
targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("c").query("name==x")).getId(),
distributionSet.getId(), ActionType.SOFT);
final TargetFilterQuery tfq2 = targetFilterQueryManagement.updateAutoAssignDS(
targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterName).query("name==z*")).getId(),
distributionSet2.getId());
final TargetFilterQuery tfq = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("c").query("name==x")
.autoAssignDistributionSet(distributionSet).autoAssignActionType(ActionType.SOFT));
final TargetFilterQuery tfq2 = targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name(filterName).query("name==z*").autoAssignDistributionSet(distributionSet2));
assertEquals(4L, targetFilterQueryManagement.count());
// check if find works
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
verifyFindByDistributionSetAndRsql(distributionSet, null, tfq, tfq2);
@@ -421,4 +440,73 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
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
verifyThat1TargetHasNameAndId("targ-A-special", targSpecialName.getControllerId());
verifyThat1TargetHasAttributeValue("%c-attribute%", targAttribute.getControllerId());
verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%", targAttributeId.getControllerId());
verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%",
targAttributeId.getControllerId());
verifyThatRepositoryContains400Targets();
verifyThat200TargetsHaveTagD(targTagW, concat(targBs, targCs));
verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(targTagY, targTagW, targBs);

View File

@@ -63,8 +63,8 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.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
autoAssignChecker.check();
@@ -101,7 +101,8 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.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 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
// fail
assertThatExceptionOfType(
InvalidAutoAssignDistributionSetException.class)
.isThrownBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("filterA").query("id==" + targetDsFIdPref + "*")).getId(),
setF.getId()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy(() -> {
final Long filterId = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*"))
.getId();
targetFilterQueryManagement
.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(setF.getId()));
});
// target filter query that matches failed bunch of targets
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filterB").query("id==" + targetDsAIdPref + "*"))
.getId(), setA.getId());
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterB")
.query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA.getId()));
final List<Target> targetsF = testdataFactory.createTargets(10, targetDsFIdPref,
targetDsFIdPref.concat(" description"));
@@ -243,12 +242,10 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final List<Target> targets = testdataFactory.createTargets(targetCount, "target" + prefix,
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;
}
@@ -262,4 +259,38 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
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.setTarget(target);
action.setStatus(Status.RUNNING);
action.setWeight(45);
target.addAction(action);
actionRepository.save(action);
@@ -56,6 +57,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
newAction.setActive(i % 2 == 0);
newAction.setStatus(Status.RUNNING);
newAction.setTarget(target);
newAction.setWeight(45);
actionRepository.save(newAction);
target.addAction(newAction);
}

View File

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

View File

@@ -16,9 +16,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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 int DEFAULT_TEST_WEIGHT = 500;
/**
* Number of {@link DistributionSetType}s that exist in every test case. One
* generated by using
@@ -250,9 +250,14 @@ public abstract class AbstractIntegrationTest {
protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final List<String> controllerIds,
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()
.map(id -> DeploymentManagement.deploymentRequest(id, dsID).setActionType(actionType)
.setForceTime(forcedTime).build())
.setForceTime(forcedTime).setWeight(weight).build())
.collect(Collectors.toList());
final List<DistributionSetAssignmentResult> results = deploymentManagement
.assignDistributionSets(deploymentRequests);
@@ -266,7 +271,13 @@ public abstract class AbstractIntegrationTest {
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
.assignDistributionSets(Collections.singletonList(request));
assertThat(results).hasSize(1);
@@ -302,35 +313,21 @@ public abstract class AbstractIntegrationTest {
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final long dsID,
final String controllerId, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
return makeAssignment(DeploymentManagement.deploymentRequest(controllerId, dsID)
.setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)
.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) {
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() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
@@ -477,12 +474,14 @@ public abstract class AbstractIntegrationTest {
return randomStringBuilder.toString();
}
protected static Action getFirstAssignedAction(final DistributionSetAssignmentResult distributionSetAssignmentResult) {
protected static Action getFirstAssignedAction(
final DistributionSetAssignmentResult distributionSetAssignmentResult) {
return distributionSetAssignmentResult.getAssignedEntity().stream().findFirst()
.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();
}

View File

@@ -1,6 +1,6 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -19,6 +19,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -117,8 +118,8 @@ public class TestdataFactory {
public static final String SM_TYPE_RT = "runtime";
/**
* Key of test "application" {@link SoftwareModuleType} : optional software in
* {@link #DS_TYPE_DEFAULT}.
* Key of test "application" {@link SoftwareModuleType} : optional software
* in {@link #DS_TYPE_DEFAULT}.
*/
public static final String SM_TYPE_APP = "application";
@@ -160,13 +161,14 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix) {
@@ -175,24 +177,25 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet() {
return createDistributionSet("", DEFAULT_VERSION, false);
return createDistributionSet(UUID.randomUUID().toString(), DEFAULT_VERSION, false);
}
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @param modules
* of {@link DistributionSet#getModules()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules) {
@@ -201,15 +204,16 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @param modules
* of {@link DistributionSet#getModules()}
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules, final String prefix) {
@@ -218,14 +222,15 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}.
*
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}.
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
* @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix, final boolean isRequiredMigrationStep) {
@@ -234,15 +239,16 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
* @param tags
* {@link DistributionSet#getTags()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix, final Collection<DistributionSetTag> tags) {
@@ -251,17 +257,19 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP}.
*
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP}.
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
* @param version
* {@link DistributionSet#getVersion()} and
* {@link SoftwareModule#getVersion()} extended by a random number.
* {@link SoftwareModule#getVersion()} extended by a random
* number.
* @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -295,7 +303,7 @@ public class TestdataFactory {
* {@link SoftwareModuleMetadata#isTargetVisible()} and
* {@link #INVISIBLE_SM_MD_KEY}, {@link #INVISIBLE_SM_MD_VALUE} without
* {@link SoftwareModuleMetadata#isTargetVisible()}
*
*
* @param set
* to add metadata to
*/
@@ -313,17 +321,19 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository.
*
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
* @param version
* {@link DistributionSet#getVersion()} and
* {@link SoftwareModule#getVersion()} extended by a random number.
* {@link SoftwareModule#getVersion()} extended by a random
* number.
* @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()}
* @param modules
* for {@link DistributionSet#getModules()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -338,9 +348,9 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
* {@link #SM_TYPE_APP}.
*
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP}.
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
@@ -350,6 +360,7 @@ public class TestdataFactory {
* number.updat
* @param tags
* {@link DistributionSet#getTags()}
*
* @return {@link DistributionSet} entity.
*/
public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -365,13 +376,14 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet}s in repository including three
* {@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
* number and {@link DistributionSet#isRequiredMigrationStep()}
* {@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 number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>.
*
*
* @param number
* of {@link DistributionSet}s to create
*
* @return {@link List} of {@link DistributionSet} entities
*/
public List<DistributionSet> createDistributionSets(final int number) {
@@ -380,8 +392,9 @@ 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
* of {@link DistributionSet}s to create
* @return {@link List} of {@link DistributionSet} entities
@@ -400,16 +413,17 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet}s in repository including three
* {@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
* number and {@link DistributionSet#isRequiredMigrationStep()}
* {@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 number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>.
*
*
* @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description.
* @param number
* of {@link DistributionSet}s to create
*
* @return {@link List} of {@link DistributionSet} entities
*/
public List<DistributionSet> createDistributionSets(final String prefix, final int number) {
@@ -426,11 +440,12 @@ public class TestdataFactory {
* Creates {@link DistributionSet}s in repository with
* {@link #DEFAULT_DESCRIPTION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
*
*
* @param name
* {@link DistributionSet#getName()}
* @param version
* {@link DistributionSet#getVersion()}
*
* @return {@link DistributionSet} entity
*/
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
* payload.
*
* Creates {@link Artifact}s for given {@link SoftwareModule} with a small
* text payload.
*
* @param moduleId
* the {@link Artifact}s belong to.
*
* @return {@link Artifact} entity.
*/
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
* payload.
*
* Create an {@link Artifact} for given {@link SoftwareModule} with a small
* text payload.
*
* @param artifactData
* the {@link Artifact} Inputstream
*
* @param moduleId
* the {@link Artifact} belongs to
*
* @param filename
* that was provided during upload.
*
* @return {@link Artifact} entity.
*/
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
* payload.
* Create an {@link Artifact} for given {@link SoftwareModule} with a small
* text payload.
*
* @param artifactData
* the {@link Artifact} Inputstream
*
* @param moduleId
* the {@link Artifact} belongs to
*
* @param filename
* that was provided during upload.
*
* @param fileSize
* the file size
*
* @return {@link Artifact} entity.
*/
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
* {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()}
* in the repository.
*
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* @param typeKey
* of the {@link SoftwareModuleType}
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModule(final String typeKey) {
@@ -509,10 +533,12 @@ public class TestdataFactory {
}
/**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY}
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random
* generated {@link Target#getDescription()} in the repository.
*
* Creates {@link SoftwareModule} of type
* {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModuleApp() {
@@ -520,12 +546,15 @@ public class TestdataFactory {
}
/**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY}
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random
* generated {@link Target#getDescription()} in the repository.
*
* Creates {@link SoftwareModule} of type
* {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* @param prefix
* added to name and version
*
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModuleApp(final String prefix) {
@@ -533,10 +562,12 @@ public class TestdataFactory {
}
/**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY}
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random
* generated {@link Target#getDescription()} in the repository.
*
* Creates {@link SoftwareModule} of type
* {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModuleOs() {
@@ -544,12 +575,15 @@ public class TestdataFactory {
}
/**
* Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY}
* with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random
* generated {@link Target#getDescription()} in the repository.
*
* Creates {@link SoftwareModule} of type
* {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* @param prefix
* added to name and version
*
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModuleOs(final String prefix) {
@@ -558,13 +592,14 @@ public class TestdataFactory {
/**
* Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()}
* in the repository.
*
* {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository.
*
* @param typeKey
* of the {@link SoftwareModuleType}
* @param prefix
* added to name and version
*
* @return persisted {@link SoftwareModule}.
*/
public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) {
@@ -603,7 +638,7 @@ public class TestdataFactory {
return target;
}
private void assertTargetProperlyCreated(Target target) {
private void assertTargetProperlyCreated(final Target target) {
assertThat(target.getCreatedBy()).isNotNull();
assertThat(target.getCreatedAt()).isNotNull();
assertThat(target.getLastModifiedBy()).isNotNull();
@@ -614,15 +649,16 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSet}s in repository including three
* {@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
* number and {@link DistributionSet#isRequiredMigrationStep()}
* {@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 number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>.
*
* In addition it updates the created {@link DistributionSet}s and
* {@link SoftwareModule}s to ensure that {@link BaseEntity#getLastModifiedAt()}
* and {@link BaseEntity#getLastModifiedBy()} is filled.
*
* {@link SoftwareModule}s to ensure that
* {@link BaseEntity#getLastModifiedAt()} and
* {@link BaseEntity#getLastModifiedBy()} is filled.
*
* @return persisted {@link DistributionSet}.
*/
public DistributionSet createUpdatedDistributionSet() {
@@ -639,8 +675,8 @@ public class TestdataFactory {
/**
* @return {@link DistributionSetType} with key {@link #DS_TYPE_DEFAULT} and
* {@link SoftwareModuleType}s {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP}.
* {@link SoftwareModuleType}s {@link #SM_TYPE_OS},
* {@link #SM_TYPE_RT} , {@link #SM_TYPE_APP}.
*/
public DistributionSetType findOrCreateDefaultTestDsType() {
final List<SoftwareModuleType> mand = new ArrayList<>();
@@ -656,11 +692,12 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSetType} in repository.
*
*
* @param dsTypeKey
* {@link DistributionSetType#getKey()}
* @param dsTypeName
* {@link DistributionSetType#getName()}
*
* @return persisted {@link DistributionSetType}
*/
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName) {
@@ -672,7 +709,7 @@ public class TestdataFactory {
/**
* Finds {@link DistributionSetType} in repository with given
* {@link DistributionSetType#getKey()} or creates if it does not exist yet.
*
*
* @param dsTypeKey
* {@link DistributionSetType#getKey()}
* @param dsTypeName
@@ -681,6 +718,7 @@ public class TestdataFactory {
* {@link DistributionSetType#getMandatoryModuleTypes()}
* @param optional
* {@link DistributionSetType#getOptionalModuleTypes()}
*
* @return persisted {@link DistributionSetType}
*/
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName,
@@ -694,11 +732,12 @@ public class TestdataFactory {
/**
* Finds {@link SoftwareModuleType} in repository with given
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet with
* {@link SoftwareModuleType#getMaxAssignments()} = 1.
*
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet
* with {@link SoftwareModuleType#getMaxAssignments()} = 1.
*
* @param key
* {@link SoftwareModuleType#getKey()}
*
* @return persisted {@link SoftwareModuleType}
*/
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key) {
@@ -708,11 +747,12 @@ public class TestdataFactory {
/**
* Finds {@link SoftwareModuleType} in repository with given
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet.
*
*
* @param key
* {@link SoftwareModuleType#getKey()}
* @param maxAssignments
* {@link SoftwareModuleType#getMaxAssignments()}
*
* @return persisted {@link SoftwareModuleType}
*/
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key, final int maxAssignments) {
@@ -732,6 +772,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()}
* @param modules
* {@link DistributionSet#getModules()}
*
* @return the created {@link DistributionSet}
*/
public DistributionSet createDistributionSet(final String name, final String version,
@@ -754,6 +795,7 @@ public class TestdataFactory {
* {@link DistributionSet#getModules()}
* @param requiredMigrationStep
* {@link DistributionSet#isRequiredMigrationStep()}
*
* @return the created {@link DistributionSet}
*/
public DistributionSet generateDistributionSet(final String name, final String version,
@@ -775,6 +817,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()}
* @param modules
* {@link DistributionSet#getModules()}
*
* @return the created {@link DistributionSet}
*/
public DistributionSet generateDistributionSet(final String name, final String version,
@@ -787,6 +830,7 @@ public class TestdataFactory {
*
* @param name
* {@link DistributionSet#getName()}
*
* @return the generated {@link DistributionSet}
*/
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}
* as prefix for {@link Target#getControllerId()}.
*
* Creates {@link Target}s in repository and with
* {@link #DEFAULT_CONTROLLER_ID} as prefix for
* {@link Target#getControllerId()}.
*
* @param number
* of {@link Target}s to create
*
* @return {@link List} of {@link Target} entities
*/
public List<Target> createTargets(final int number) {
@@ -815,7 +861,7 @@ public class TestdataFactory {
/**
* Builds {@link Target} objects with given prefix for
* {@link Target#getControllerId()} followed by a number suffix.
*
*
* @param start
* value for the controllerId suffix
* @param numberOfTargets
@@ -835,8 +881,9 @@ public class TestdataFactory {
/**
* 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
* of {@link Target}s to generate
* @param controllerIdPrefix
@@ -926,9 +973,10 @@ public class TestdataFactory {
/**
* Creates {@link DistributionSetTag}s in repository.
*
*
* @param number
* of {@link DistributionSetTag}s
*
* @return the persisted {@link DistributionSetTag}s
*/
public List<DistributionSetTag> createDistributionSetTags(final int number) {
@@ -950,14 +998,16 @@ 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
* to add {@link ActionStatus}
* @param status
* to add
* @param message
* to add
*
* @return updated {@link Action}.
*/
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -966,14 +1016,16 @@ 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
* to add {@link ActionStatus}
* @param status
* to add
* @param msgs
* to add
*
* @return updated {@link Action}.
*/
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -991,7 +1043,7 @@ public class TestdataFactory {
/**
* Creates rollout based on given parameters.
*
*
* @param rolloutName
* of the {@link Rollout}
* @param rolloutDescription
@@ -1012,7 +1064,7 @@ public class TestdataFactory {
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
final String successCondition, final String errorCondition) {
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
* @param actionType
* the type of the Rollout
* @param weight
* weight of the Rollout
* @return created {@link Rollout}
*/
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
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()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
@@ -1046,7 +1101,7 @@ public class TestdataFactory {
final Rollout rollout = rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutDescription)
.targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType),
.targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType).weight(weight),
groupSize, conditions);
// Run here, because Scheduler is disabled during tests
@@ -1058,10 +1113,10 @@ public class TestdataFactory {
/**
* Create {@link Rollout} with a new {@link DistributionSet} and
* {@link Target}s.
*
*
* @param prefix
* for rollouts name, description, {@link Target#getControllerId()}
* filter
* for rollouts name, description,
* {@link Target#getControllerId()} filter
* @return created {@link Rollout}
*/
public Rollout createRollout(final String prefix) {
@@ -1071,12 +1126,12 @@ public class TestdataFactory {
}
/**
* Create the soft deleted {@link Rollout} with a new {@link DistributionSet}
* and {@link Target}s.
*
* Create the soft deleted {@link Rollout} with a new
* {@link DistributionSet} and {@link Target}s.
*
* @param prefix
* for rollouts name, description, {@link Target#getControllerId()}
* filter
* for rollouts name, description,
* {@link Target#getControllerId()} filter
* @return created {@link Rollout}
*/
public Rollout createSoftDeletedRollout(final String prefix) {

View File

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

View File

@@ -105,12 +105,12 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that targets cannot be created e.g. in plug'n play scenarios when tenant does not exists but can be created if the tenant exists.")
@WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = {CONTROLLER_ROLE,
SYSTEM_ROLE}, autoCreateTenant = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = { CONTROLLER_ROLE,
SYSTEM_ROLE }, autoCreateTenant = false)
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = DistributionSetTypeCreatedEvent.class, count = 3),
@Expect(type = SoftwareModuleTypeCreatedEvent.class, count = 2)})
@Expect(type = SoftwareModuleTypeCreatedEvent.class, count = 2) })
public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception {
mvc.perform(get("/default-tenant/", tenantAware.getCurrentTenant())).andDo(MockMvcResultPrinter.print())
@@ -131,10 +131,10 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that target poll request does not change audit data on the entity.")
@WithUser(principal = "knownPrincipal", authorities = {SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET}, allSpPermissions = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1)})
@WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET,
SpPermission.CREATE_TARGET }, allSpPermissions = false)
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void targetPollDoesNotModifyAuditData() throws Exception {
// create target first with "knownPrincipal" user and audit data
final String knownTargetControllerId = "target1";
@@ -162,15 +162,15 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 {
mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test
@Description("Ensures that the system creates a new target in plug and play manner, i.e. target is authenticated but does not exist yet.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlay() throws Exception {
final long current = System.currentTimeMillis();
@@ -198,8 +198,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.")
@WithUser(principal = "knownpricipal", allSpPermissions = false)
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void pollWithModifiedGloablPollingTime() throws Exception {
securityRule.runAs(WithSpringAuthorityRule.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION), () -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
@@ -218,14 +218,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void rootRsNotModified() throws Exception {
final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -289,8 +289,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that the target state machine of a precomissioned target switches from "
+ "UNKNOWN to REGISTERED when the target polls for the first time.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPrecommissioned() throws Exception {
testdataFactory.createTarget("4711");
@@ -314,8 +314,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Ensures that the source IP address of the polling target is correctly stored in repository")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsPlugAndPlayIpAddress() throws Exception {
// test
final String knownControllerId1 = "0815";
@@ -341,8 +341,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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),
@Expect(type = TargetPollEvent.class, count = 1)})
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) })
public void rootRsIpAddressNotStoredIfDisabled() throws Exception {
securityProperties.getClients().setTrackRemoteIp(false);
@@ -360,12 +360,12 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911");
@@ -384,13 +384,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.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 = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1)})
@Expect(type = TargetAttributesRequestedEvent.class, count = 1) })
public void attributeUpdateRequestSendingAfterSuccessfulDeployment() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("1");
final Target savedTarget = testdataFactory.createTarget("922");
@@ -463,13 +463,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryCount() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911");
@@ -498,13 +498,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryZeroInput() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911");
@@ -529,13 +529,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@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 = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void testActionHistoryNegativeInput() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
Target savedTarget = testdataFactory.createTarget("911");
@@ -654,12 +654,13 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("Assign multiple DS in multi-assignment mode. The earliest active Action is exposed to the controller.")
public void earliestActionIsExposedToControllerInMultiAssignMode() throws Exception {
setMultiAssignmentsEnabled();
enableMultiAssignments();
final Target target = testdataFactory.createTarget();
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Action action1 = getFirstAssignedAction(assignDistributionSet(ds1, target));
final Long action2Id = getFirstAssignedActionId(assignDistributionSet(ds2, target));
final Action action1 = getFirstAssignedAction(assignDistributionSet(ds1.getId(), target.getControllerId(), 56));
final Long action2Id = getFirstAssignedActionId(
assignDistributionSet(ds2.getId(), target.getControllerId(), 34));
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action1.getId());
sendDeploymentActionFeedback(target, action1, "closed", "success");
@@ -670,7 +671,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Test
@Description("The system should not create a new target because of a too long controller id.")
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))
.andExpect(status().isBadRequest());
}
@@ -684,8 +685,4 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.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
private Long forceTime;
@JsonProperty(value="forceType")
@JsonProperty(value = "forceType")
private MgmtActionType actionType;
@JsonProperty
private Integer weight;
@JsonProperty
private MgmtMaintenanceWindow maintenanceWindow;
@@ -79,6 +82,14 @@ public class MgmtAction extends MgmtBaseEntity {
this.forceTime = forceTime;
}
public Integer getWeight() {
return weight;
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
public MgmtActionType getActionType() {
return actionType;
}
@@ -110,5 +121,4 @@ public class MgmtAction extends MgmtBaseEntity {
public void setType(final String type) {
this.type = type;
}
}

View File

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

View File

@@ -12,12 +12,12 @@ import java.util.HashMap;
import java.util.Map;
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.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
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
private MgmtActionType type;
@JsonProperty
private Integer weight;
public boolean isDeleted() {
return deleted;
}
@@ -114,4 +117,12 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity {
public MgmtActionType getType() {
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.JsonInclude;
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
@@ -34,6 +35,9 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
private Long startAt;
@JsonProperty(required = false)
private Integer weight;
private MgmtActionType type;
private List<MgmtRolloutGroup> groups;
@@ -124,7 +128,7 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
* @param groups
* List of {@link MgmtRolloutGroup}
*/
public void setGroups(List<MgmtRolloutGroup> groups) {
public void setGroups(final List<MgmtRolloutGroup> groups) {
this.groups = groups;
}
@@ -139,7 +143,22 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
* @param startAt
* the start at timestamp in millis or null
*/
public void setStartAt(Long startAt) {
public void setStartAt(final Long 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 {
private long forcetime;
@JsonProperty(required = false)
private Integer weight;
private MgmtActionType type;
private MgmtMaintenanceWindowRequestBody maintenanceWindow;
@@ -47,6 +49,14 @@ public class MgmtDistributionSetAssignment extends MgmtId {
this.forcetime = forcetime;
}
public Integer getWeight() {
return weight;
}
public void setWeight(final int weight) {
this.weight = weight;
}
/**
* Returns {@link MgmtMaintenanceWindowRequestBody} for distribution set
* assignment.

View File

@@ -22,6 +22,9 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
@JsonProperty(required = false)
private MgmtActionType type;
@JsonProperty(required = false)
private Integer weight;
public MgmtActionType getType() {
return type;
}
@@ -29,4 +32,12 @@ public class MgmtDistributionSetAutoAssignment extends MgmtId {
public void setType(final MgmtActionType 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
private MgmtActionType autoAssignActionType;
@JsonProperty
private Integer autoAssignWeight;
public Long getFilterId() {
return filterId;
}
@@ -80,4 +83,11 @@ public class MgmtTargetFilterQuery extends MgmtBaseEntity {
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;
}
public void setQuery(String query) {
public void setQuery(final String query) {
this.query = query;
}
}

View File

@@ -38,7 +38,7 @@ public final class MgmtDeploymentRequestMapper {
final String targetId) {
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) {
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,
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)
.setActionType(MgmtRestModelMapper.convertActionType(type)).setForceTime(forcetime);
.setActionType(MgmtRestModelMapper.convertActionType(type)).setForceTime(forcetime).setWeight(weight);
if (maintenanceWindow != null) {
final String cronSchedule = maintenanceWindow.getSchedule();
final String duration = maintenanceWindow.getDuration();
@@ -70,5 +71,4 @@ public final class MgmtDeploymentRequestMapper {
}
return request.build();
}
}

View File

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

View File

@@ -79,6 +79,7 @@ final class MgmtRolloutMapper {
body.setTotalTargets(rollout.getTotalTargets());
body.setDeleted(rollout.isDeleted());
body.setType(MgmtRestModelMapper.convertActionType(rollout.getActionType()));
rollout.getWeight().ifPresent(body::setWeight);
if (withDetails) {
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())
.set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery())
.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) {
@@ -249,5 +251,4 @@ final class MgmtRolloutMapper {
private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED;
}
}

View File

@@ -15,11 +15,14 @@ import java.util.Collections;
import java.util.List;
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.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi;
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.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.util.CollectionUtils;
@@ -58,6 +61,7 @@ public final class MgmtTargetFilterQueryMapper {
if (distributionSet != null) {
targetRest.setAutoAssignDistributionSet(distributionSet.getId());
targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType()));
filter.getAutoAssignWeight().ifPresent(targetRest::setAutoAssignWeight);
}
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());
}
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) {
LOG.debug("updating target filter query {}", filterId);
final TargetFilterQuery updateFilter = filterManagement.update(entityFactory.targetFilterQuery()
.update(filterId).name(targetFilterRest.getName()).query(targetFilterRest.getQuery()));
final TargetFilterQuery updateFilter = filterManagement
.update(entityFactory.targetFilterQuery().update(filterId).name(targetFilterRest.getName())
.query(targetFilterRest.getQuery()));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
MgmtTargetFilterQueryMapper.addLinks(response);
@@ -127,10 +128,10 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
@Override
public ResponseEntity<MgmtTargetFilterQuery> postAssignedDistributionSet(
@PathVariable("filterId") final Long filterId,
@RequestBody final MgmtDistributionSetAutoAssignment dsIdWithActionType) {
@RequestBody final MgmtDistributionSetAutoAssignment autoAssignRequest) {
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDSWithActionType(filterId,
dsIdWithActionType.getId(), MgmtRestModelMapper.convertActionType(dsIdWithActionType.getType()));
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(
MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filterId, autoAssignRequest));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter);
MgmtTargetFilterQueryMapper.addLinks(response);
@@ -156,7 +157,7 @@ public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestA
@Override
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();
}

View File

@@ -206,6 +206,7 @@ public final class MgmtTargetMapper {
if (ActionType.TIMEFORCED == action.getActionType()) {
result.setForceTime(action.getForcedTime());
}
action.getWeight().ifPresent(result::setWeight);
result.setActionType(MgmtRestModelMapper.convertActionType(action.getActionType()));
if (action.isActive()) {
@@ -289,5 +290,4 @@ public final class MgmtTargetMapper {
static List<MgmtMetadata> toResponseTargetMetadata(final List<TargetMetadata> metadata) {
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.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.model.BaseEntity;
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.rest.AbstractRestIntegrationTest;
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.test.context.ContextConfiguration;
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 DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(knownFilterName).query("name==y")).getId(),
createdDs.getId());
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(knownFilterName)
.query("name==y").autoAssignDistributionSet(createdDs.getId()));
// create some dummy target filter queries
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) {
// create target filter queries that should be found
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1").query("name==y"))
.getId(), createdDs.getId());
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2").query("name==y"))
.getId(), createdDs.getId());
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1")
.query("name==y").autoAssignDistributionSet(createdDs.getId()));
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2")
.query("name==y").autoAssignDistributionSet(createdDs.getId()));
// create some dummy target filter queries
targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "b").query("name==y"));
@@ -1247,8 +1244,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
}
@Test
@Description("Ensures that multi target assignment through API is reflected by the repository in the case of " +
"DOWNLOAD_ONLY.")
@Description("Ensures that multi target assignment through API is reflected by the repository in the case of "
+ "DOWNLOAD_ONLY.")
public void assignMultipleTargetsToDistributionSetAsDownloadOnly() throws Exception {
final DistributionSet createdDs = testdataFactory.createDistributionSet();
@@ -1263,8 +1260,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
assignDistributionSet(createdDs.getId(), knownTargetIds[0], Action.ActionType.DOWNLOAD_ONLY);
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", createdDs.getId())
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
.andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.contentType(MediaType.APPLICATION_JSON).content(list.toString())).andExpect(status().isOk())
.andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
@@ -1310,10 +1307,10 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONArray body = new JSONArray();
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED));
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.FORCED));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.SOFT));
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 56));
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 78));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.FORCED, 67));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.SOFT, 34));
enableMultiAssignments();
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())));
}
@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)
throws JSONException {
final JSONObject obj = new JSONObject();
obj.put("id", targetId);
obj.put("type", type.getName());
return obj;
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
enableMultiAssignments();
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);
}
@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,
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,
final String targetFilterQuery, final int targets, final Action.ActionType type) throws Exception {
String actionType = MgmtRestModelMapper.convertActionType(type).getName();
String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType);
final String actionType = MgmtRestModelMapper.convertActionType(type).getName();
final String rollout = JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
new RolloutGroupConditionBuilder().withDefaults().build(), null, actionType, null);
mvc.perform(post("/rest/v1/rollouts")
.content(rollout)
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
mvc.perform(post("/rest/v1/rollouts").content(rollout).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("$.type", equalTo(actionType)))
.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.status;
import java.util.List;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
@@ -104,8 +107,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception {
final String notExistingId = "4395";
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId).content("{}")
.contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isNotFound());
.contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isNotFound());
}
@Test
@@ -166,8 +168,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
createSingleTargetFilterQuery(idB, testQuery);
createSingleTargetFilterQuery(idC, testQuery);
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk())
.andDo(print())
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk()).andDo(print())
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount)))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(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 + "/"
+ tfq.getId();
// test
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()))
.andDo(print()).andExpect(status().isOk())
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName)))
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
.andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName)))
.andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery)))
.andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix)))
.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() + "}";
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isOk());
.content(payload).contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk());
final TargetFilterQuery updatedFilterQuery = targetFilterQueryManagement.get(tfq.getId()).get();
final MgmtActionType expectedActionType = actionType != null ? actionType : MgmtActionType.FORCED;
@@ -412,9 +411,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
assertThat(updatedFilterQuery.getAutoAssignActionType())
.isEqualTo(MgmtRestModelMapper.convertActionType(expectedActionType));
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()))
.andDo(print()).andExpect(status().isOk())
.andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName())))
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())).andDo(print())
.andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(tfq.getName())))
.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_ACTION_TYPE, equalTo(expectedActionType.getName())))
@@ -427,8 +425,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
throws Exception {
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + ", \"type\":\"" + MgmtActionType.TIMEFORCED.getName() + "\"}")
.contentType(MediaType.APPLICATION_JSON)).andDo(print())
.andExpect(status().isBadRequest())
.contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isBadRequest())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS,
equalTo(InvalidAutoAssignActionTypeException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE,
@@ -491,7 +488,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
final DistributionSet set = testdataFactory.createDistributionSet(dsName);
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();
@@ -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) {
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.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -40,6 +39,7 @@ import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.exception.SpServerError;
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.repository.ActionFields;
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.util.IpUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
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)
.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())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
@@ -1889,8 +1889,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final String targetId = testdataFactory.createTarget().getControllerId();
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())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
@@ -1902,7 +1901,8 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final String targetId = testdataFactory.createTarget().getControllerId();
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())
.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)
.collect(Collectors.toList());
final JSONArray body = getAssignmentBody(dsIds);
final JSONArray body = new JSONArray();
dsIds.forEach(id -> body.put(getAssignmentObject(id, MgmtActionType.FORCED, 76)));
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("total", equalTo(2)));
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("total", equalTo(2)));
}
public static JSONArray getAssignmentBody(final Collection<Long> dsIds) throws JSONException {
final JSONArray body = new JSONArray();
for (final Long id : dsIds) {
final JSONObject obj = new JSONObject();
obj.put("id", id);
body.put(obj);
}
return body;
@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 = 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);
}
@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_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_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
}
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,
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,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
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,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
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,
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();
try {
@@ -481,6 +483,10 @@ public abstract class JsonBuilder {
json.put("type", type);
}
if (weight != null) {
json.put("weight", weight);
}
if (conditions != null) {
final JSONObject successCondition = new JSONObject();

View File

@@ -25,11 +25,13 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
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.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
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.Target;
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.RestConfiguration;
import org.eclipse.hawkbit.rest.util.FilterHttpResponse;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Rule;
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) {
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);
myFieldDesc.attributes(key("mandatory").value(mandatory ? "X" : ""));
myFieldDesc.attributes(key("mandatory").value(mandatoryMessage));
// defaults
myFieldDesc.attributes(key("value").value(""));
@@ -118,6 +126,10 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
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 {
/**
@@ -154,11 +166,17 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name)
.status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name)
.lastTargetQuery(System.currentTimeMillis()));
final List<Target> updatedTargets = maintenanceWindowSchedule == null
? assignWithoutMaintenanceWindow(distributionSet, savedTarget, timeforced)
: assignWithMaintenanceWindow(distributionSet, savedTarget, timeforced, maintenanceWindowSchedule,
maintenanceWindowDuration, maintenanceWindowTimeZone);
final DeploymentRequestBuilder deploymentRequestBuilder = DeploymentManagement
.deploymentRequest(savedTarget.getControllerId(), distributionSet.getId())
.setMaintenance(maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
if (timeforced) {
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) {
feedbackToByInSync(distributionSet);
@@ -167,28 +185,6 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
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() {
DistributionSet distributionSet = testdataFactory.createDistributionSet("");
distributionSet = distributionSetManagement.update(entityFactory.distributionSet()
@@ -314,4 +310,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
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_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 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_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 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
public static final String FORCETIME = "Forcetime in milliseconds.";
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 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.";

View File

@@ -385,6 +385,9 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(
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("[].maintenanceWindow")

View File

@@ -23,10 +23,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.eclipse.hawkbit.im.authentication.SpPermission;
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.RolloutGroup;
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.snippet.Snippet;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
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: "
+ SpPermission.READ_ROLLOUT)
public void getRollouts() throws Exception {
enableMultiAssignments();
createRolloutEntity();
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 + "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 + "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: "
+ SpPermission.READ_ROLLOUT)
public void getRollout() throws Exception {
enableMultiAssignments();
final Rollout rollout = createRolloutEntity();
mockMvc.perform(get(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}", rollout.getId())
@@ -195,47 +198,47 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.contentType(MediaTypes.HAL_JSON_UTF8).accept(MediaTypes.HAL_JSON_VALUE))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8))
.andDo(this.document.document(
requestFields(requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
optionalRequestFieldWithPath("type").description(
MgmtApiModelProperties.ROLLOUT_TYPE).attributes(
key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")),
requestFieldWithPath("distributionSetId")
.description(MgmtApiModelProperties.ROLLOUT_DS_ID),
requestFieldWithPath("targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
requestFieldWithPath("amountGroups")
.description(MgmtApiModelProperties.ROLLOUT_AMOUNT_GROUPS),
optionalRequestFieldWithPath("description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP)),
.andDo(this.document.document(requestFields(
requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPathMandatoryInMultiAssignMode("weight").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.RESULTING_ACTIONS_WEIGHT)
.attributes(key("value").value("0 - 1000")),
requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
optionalRequestFieldWithPath("type").description(MgmtApiModelProperties.ROLLOUT_TYPE)
.attributes(key("value").value("['soft', 'forced', 'timeforced', 'downloadonly']")),
requestFieldWithPath("distributionSetId").description(MgmtApiModelProperties.ROLLOUT_DS_ID),
requestFieldWithPath("targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
requestFieldWithPath("amountGroups").description(MgmtApiModelProperties.ROLLOUT_AMOUNT_GROUPS),
optionalRequestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP)),
getRolloutResponseFields(false, true)));
@@ -283,77 +286,83 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
.contentType(MediaType.APPLICATION_JSON).accept(MediaTypes.HAL_JSON_VALUE))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaTypes.HAL_JSON_UTF8))
.andDo(this.document.document(requestFields(
requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPath("distributionSetId").description(MgmtApiModelProperties.ROLLOUT_DS_ID),
requestFieldWithPath("targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
optionalRequestFieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP),
requestFieldWithPath("groups").description(MgmtApiModelProperties.ROLLOUT_GROUPS),
requestFieldWithPath("groups[].name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPath("groups[].description").description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("groups[].targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_GROUP_FILTER_QUERY),
optionalRequestFieldWithPath("groups[].targetPercentage")
.description(MgmtApiModelProperties.ROLLOUT_GROUP_TARGET_PERCENTAGE)
.attributes(key("value").value("0..100")),
optionalRequestFieldWithPath("groups[].successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("groups[].successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("groups[].successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("groups[].successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("groups[].successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("groups[].successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("groups[].errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("groups[].errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("groups[].errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("groups[].errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("groups[].errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("groups[].errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP)),
.andDo(this.document.document(
requestFields(requestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
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")
.description(MgmtApiModelProperties.ROLLOUT_FILTER_QUERY),
optionalRequestFieldWithPath("description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP),
requestFieldWithPath("groups").description(MgmtApiModelProperties.ROLLOUT_GROUPS),
requestFieldWithPath("groups[].name").description(ApiModelPropertiesGeneric.NAME),
requestFieldWithPath("groups[].description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("groups[].targetFilterQuery")
.description(MgmtApiModelProperties.ROLLOUT_GROUP_FILTER_QUERY),
optionalRequestFieldWithPath("groups[].targetPercentage")
.description(MgmtApiModelProperties.ROLLOUT_GROUP_TARGET_PERCENTAGE)
.attributes(key("value").value("0..100")),
optionalRequestFieldWithPath("groups[].successCondition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION),
optionalRequestFieldWithPath("groups[].successCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("groups[].successCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_CONDITION_EXP),
optionalRequestFieldWithPath("groups[].successAction")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION),
optionalRequestFieldWithPath("groups[].successAction.action")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_ACTION)
.attributes(key("value").value("['nextgroup']")),
optionalRequestFieldWithPath("groups[].successAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_SUCCESS_ACTION_EXP),
optionalRequestFieldWithPath("groups[].errorCondition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION),
optionalRequestFieldWithPath("groups[].errorCondition.condition")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_CONDITION)
.attributes(key("value").value("['threshold']")),
optionalRequestFieldWithPath("groups[].errorCondition.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_CONDITION_EXP),
optionalRequestFieldWithPath("groups[].errorAction")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION),
optionalRequestFieldWithPath("groups[].errorAction.action")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_ACTION)
.attributes(key("value").value("['pause']")),
optionalRequestFieldWithPath("groups[].errorAction.expression")
.description(MgmtApiModelProperties.ROLLOUT_ERROR_ACTION_EXP)),
getRolloutResponseFields(false, true)));
}
@@ -626,11 +635,13 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
private Rollout createRolloutEntity() {
testdataFactory.createTargets(20, "exampleTarget");
final Rollout rollout = rolloutManagement.create(
entityFactory.rollout().create().name("exampleRollout")
.targetFilterQuery("controllerId==exampleTarget*").set(testdataFactory.createDistributionSet()),
10, new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
final RolloutCreate rolloutCreate = entityFactory.rollout().create().name("exampleRollout")
.targetFilterQuery("controllerId==exampleTarget*").set(testdataFactory.createDistributionSet());
if (isMultiAssignmentsEnabled()) {
rolloutCreate.weight(400);
}
final Rollout rollout = rolloutManagement.create(rolloutCreate, 10, new RolloutGroupConditionBuilder()
.withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
// Run here, because Scheduler is disabled during tests
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.MgmtApiModelProperties;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
@@ -183,15 +184,16 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
@Test
@Description("Handles the POST request of setting a distribution set for auto assignment within SP. Required Permission: CREATE_TARGET.")
public void postAutoAssignDS() throws Exception {
enableMultiAssignments();
final TargetFilterQuery tfq = createTargetFilterQuery();
final DistributionSet distributionSet = createDistributionSet();
final String filterByDistSet = "{\"id\":\"" + distributionSet.getId() + "\", \"type\":\""
+ MgmtActionType.SOFT.getName() + "\"}";
final String autoAssignBody = new JSONObject().put("id", distributionSet.getId())
.put("type", MgmtActionType.SOFT.getName()).put("weight", 200).toString();
this.mockMvc
.perform(
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())
.andDo(this.document.document(
pathParameters(parameterWithName("targetFilterQueryId")
@@ -199,7 +201,10 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
requestFields(requestFieldWithPath("id").description(MgmtApiModelProperties.DS_ID),
optionalRequestFieldWithPath("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)));
}
@@ -227,6 +232,9 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
fieldWithPath(arrayPrefix + "autoAssignActionType")
.description(MgmtApiModelProperties.ACTION_FORCE_TYPE).type(JsonFieldType.STRING.toString())
.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 + "createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
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));
}
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<>();
target.put("name", name);
target.put("query", query);
@@ -249,6 +258,7 @@ public class TargetFilterQueriesResourceDocumentationTest extends AbstractApiRes
private TargetFilterQuery createTargetFilterQueryWithDS(final DistributionSet distributionSet) {
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
@Description("Handles the GET request of retrieving the full action history of a specific target. Required Permission: READ_TARGET.")
public void getActionsFromTarget() throws Exception {
enableMultiAssignments();
generateActionForTarget(targetId);
mockMvc.perform(
@@ -229,12 +230,14 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
.attributes(key("value").value("['finished', 'pending']")),
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
@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 {
enableMultiAssignments();
generateActionForTarget(targetId, true, false, getTestSchedule(2), getTestDuration(1), getTestTimeZone());
mockMvc.perform(
@@ -262,6 +265,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.attributes(key("value").value("['finished', 'pending']")),
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("content[].maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
fieldWithPath("content[].maintenanceWindow.schedule")
@@ -317,6 +321,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test
@Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.")
public void getActionFromTarget() throws Exception {
enableMultiAssignments();
final Action action = generateActionForTarget(targetId, true, true);
assertThat(deploymentManagement.findAction(action.getId()).get().getActionType())
.isEqualTo(ActionType.TIMEFORCED);
@@ -330,6 +335,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
@@ -351,6 +357,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
@Test
@Description("Handles the GET request of retrieving a specific action on a specific target. Required Permission: READ_TARGET.")
public void getActionFromTargetWithMaintenanceWindow() throws Exception {
enableMultiAssignments();
final Action action = generateActionForTarget(targetId, true, true, getTestSchedule(2), getTestDuration(1),
getTestTimeZone());
@@ -363,6 +370,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
@@ -514,6 +522,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(
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("maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
@@ -537,12 +548,12 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
final long forceTime = System.currentTimeMillis();
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("maintenanceWindow", new JSONObject().put("schedule", getTestSchedule(100))
.put("duration", getTestDuration(10)).put("timezone", getTestTimeZone())))
.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();
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
@@ -555,6 +566,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPathMandatoryInMultiAssignMode("[].weight")
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
.attributes(key("value").value("0 - 1000")),
optionalRequestFieldWithPath("[].forcetime")
.description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("[].maintenanceWindow")

View File

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

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ public final class TargetAssignmentOperations {
final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceTimeZone();
final Set<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toSet());
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
dsIds.forEach(dsId -> targets.forEach(t -> {
final DeploymentRequestBuilder request = DeploymentManagement.deploymentRequest(t.getControllerId(), dsId)
.setActionType(actionType).setForceTime(forcedTimeStamp);