Skip to content

Commit

Permalink
External ref for actions and status-update events (eclipse-hawkbit#830)
Browse files Browse the repository at this point in the history
* Add feature to listen to rollout status update
* With this feature, extensions can update back the status of a given rollout using an event(containing distributionSetId and targetId).
* In future, AmqpMessageHandlerService can make use of this feature and de-couple its own implementation from performing status update of an action.
* Implement ActionStatusUpdateHandlerService using actionId
* Extend actions to support externalRef
* Update the action status using externalRef.
* Update securityContext to support running a callable under specific authorities.
* Fixing the review comments
* Increase length of externalRef to 128 chars
* Remove actionStatusUpdateEvent and the handler service
* Use 256 chars for externalRef
* Increment the version for migration script
* Another feature had use v1_12_12 in a recent PR. So incrementing the version.
* Create length limit for externalRef and add it to index
* Externalref will be much longer than 256 chars if controllerId is as long as 256 chars
* Adding tests for verifying externalRef in controllerManagement
* Improve test to consider multiple externalRefs
* Fix issue in migration script for mssql server
* Fix documentation

Signed-off-by: Ravindranath Sandeep (INST-IOT/ESW-Imb) <Sandeep.Ravindranath@bosch-si.com>
  • Loading branch information
smy4kor authored and stefbehl committed Jul 25, 2019
1 parent 01d0216 commit e3bbee5
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -429,4 +429,26 @@ Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<St
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Action cancelAction(long actionId);

/**
* Updates given {@link Action} with its external id.
*
* @param actionId
* to be updated
* @param externalRef
* of the action
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
void updateActionExternalRef(long actionId, @NotEmpty String externalRef);

/**
* 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.
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
List<Action> getActiveActionsByExternalRef(@NotNull List<String> externalRefs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import javax.validation.constraints.NotEmpty;

/**
* Update operations to be executed by the target.
*/
Expand All @@ -32,6 +34,11 @@ public interface Action extends TenantAwareBaseEntity {
*/
int MAINTENANCE_WINDOW_TIMEZONE_LENGTH = 8;

/**
* Maximum length of external reference.
*/
int EXTERNAL_REF_MAX_LENGTH = 512;

/**
* @return the distributionSet
*/
Expand Down Expand Up @@ -98,6 +105,16 @@ default boolean isCancelingOrCanceled() {
*/
String getMaintenanceWindowTimeZone();

/**
* @param externalRef associated with this action
*/
void setExternalRef(@NotEmpty String externalRef);

/**
* @return externalRef of the action
*/
String getExternalRef();

/**
* checks if the {@link #getForcedTime()} is hit by the given
* {@code hitTimeMillis}, by means if the given milliseconds are greater
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@ List<Long> findByTargetIdInAndIsActiveAndActionStatusAndDistributionSetNotRequir
@Param("targetsIds") List<Long> targetIds, @Param("active") boolean active,
@Param("currentStatus") Action.Status currentStatus);

/**
* Retrieves all {@link Action}s that matches the queried externalRefs.
*
* @param externalRefs
* for which the actions need to be found
* @param active
* flag to indicate active/inactive actions
* @return list of actions
*/
List<Action> findByExternalRefInAndActive(@Param("externalRefs") List<String> externalRefs,
@Param("active") boolean active);

/**
* Switches the status of actions from one specific status into another for
* given actions IDs, active flag and current status
Expand Down Expand Up @@ -504,4 +516,16 @@ Page<Action> findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageab
@Query("DELETE FROM JpaAction a WHERE a.id IN ?1")
void deleteByIdIn(Collection<Long> actionIDs);

/**
* Updates the externalRef of an action by its actionId.
*
* @param actionId
* for which the externalRef is being updated.
* @param externalRef
* value of the external reference for the given action id.
*/
@Modifying
@Transactional
@Query("UPDATE JpaAction a SET a.externalRef = :externalRef WHERE a.id = :actionId")
void updateExternalRef(@Param("actionId") Long actionId, @Param("externalRef") String externalRef);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
Expand Down Expand Up @@ -372,6 +374,11 @@ public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository.getById(actionId);
}

@Override
public List<Action> getActiveActionsByExternalRef(@NotNull final List<String> externalRefs) {
return actionRepository.findByExternalRefInAndActive(externalRefs, true);
}

@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = ConcurrencyFailureException.class, exclude = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
Expand Down Expand Up @@ -1042,6 +1049,11 @@ public Action cancelAction(final long actionId) {
}
}

@Override
public void updateActionExternalRef(final long actionId, @NotEmpty final String externalRef) {
actionRepository.updateExternalRef(actionId, externalRef);
}

private void cancelAssignDistributionSetEvent(final JpaTarget target, final Long actionId) {
afterCommit.afterCommit(
() -> eventPublisher.publishEvent(new CancelTargetAssignmentEvent(target, actionId, bus.getId())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ 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;

@Override
public DistributionSet getDistributionSet() {
Expand Down Expand Up @@ -332,4 +335,14 @@ public boolean isMaintenanceWindowAvailable() {
}
}
}

@Override
public void setExternalRef(String externalRef) {
this.externalRef = externalRef;
}

@Override
public String getExternalRef() {
return externalRef;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE sp_action ADD COLUMN external_ref VARCHAR(512);
CREATE INDEX sp_idx_action_external_ref ON sp_action (external_ref);


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE sp_action ADD COLUMN external_ref VARCHAR(512);
CREATE INDEX sp_idx_action_external_ref ON sp_action (external_ref);


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE sp_action ADD COLUMN external_ref VARCHAR(512);
CREATE INDEX sp_idx_action_external_ref ON sp_action (external_ref);


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE sp_action ADD external_ref VARCHAR(512);
CREATE INDEX sp_idx_action_external_ref ON sp_action (external_ref);


Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import java.io.ByteArrayInputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -54,6 +55,7 @@
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
Expand Down Expand Up @@ -1315,6 +1317,44 @@ public void quotaEceededExceptionWhenControllerReportsTooManyUpdateActionStatusM
}
}

@Test
@Description("Verify that the attaching externalRef to an action is propery stored")
public void updatedExternalRefOnActionIsReallyUpdated() {
final List<String> allExternalRef = new ArrayList<>();
final List<Long> allActionId = new ArrayList<>();
final int numberOfActions = 3;
final DistributionSet knownDistributionSet = testdataFactory.createDistributionSet();
for (int i = 0; i < numberOfActions; i++) {
final String knownControllerId = "controllerId" + i;
final String knownExternalref = "externalRefId" + i;

testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
knownDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final Long actionId = assignmentResult.getActionIds().get(0);
controllerManagement.updateActionExternalRef(actionId, knownExternalref);

allExternalRef.add(knownExternalref);
allActionId.add(actionId);
}

final List<Action> foundAction = controllerManagement.getActiveActionsByExternalRef(allExternalRef);
assertThat(foundAction).isNotNull();
for (int i = 0; i < numberOfActions; i++) {
assertThat(foundAction.get(i).getId()).isEqualTo(allActionId.get(i));
}
}

@Test
@Description("Verify that a null externalRef cannot be assigned to an action")
public void externalRefCannotBeNull() {
try {
controllerManagement.updateActionExternalRef(1L, null);
fail("No ConstraintViolationException thrown when a null externalRef was set on an action");
} catch (final ConstraintViolationException e) {
}
}

@Test
@Description("Verifies that a target can report FINISHED/ERROR updates for DOWNLOAD_ONLY assignments regardless of "
+ "repositoryProperties.rejectActionStatusForClosedAction value.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
Expand Down Expand Up @@ -111,6 +117,42 @@ public <T> T runAsSystemAsTenant(final Callable<T> callable, final String tenant
}
}

/**
* Runs a given {@link Callable} within a system security context, which has
* the provided {@link GrantedAuthority}s to successfully run the
* {@link Callable}.
*
* The security context will be switched to the a new
* {@link SecurityContext} and back after the callable is called.
*
* @param tenant
* under which the {@link Callable#call()} must be executed.
* @param callable
* to call within the security context
* @return the return value of the {@link Callable#call()} method.
*/
// The callable API throws a Exception and not a specific one
@SuppressWarnings({ "squid:S2221", "squid:S00112" })
public <T> T runAsControllerAsTenant(@NotEmpty final String tenant, @NotNull final Callable<T> callable) {
final SecurityContext oldContext = SecurityContextHolder.getContext();
List<SimpleGrantedAuthority> authorities = Collections
.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS));
try {
return tenantAware.runAsTenant(tenant, () -> {
try {
setCustomSecurityContext(tenant, oldContext.getAuthentication().getPrincipal(), authorities);
return callable.call();

} catch (final Exception e) {
throw new RuntimeException(e);
}
});

} finally {
SecurityContextHolder.setContext(oldContext);
}
}

/**
* @return {@code true} if the current running code is running as system
* code block.
Expand All @@ -119,6 +161,16 @@ public boolean isCurrentThreadSystemCode() {
return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication;
}

private void setCustomSecurityContext(final String tenantId, final Object principal,
final Collection<? extends GrantedAuthority> authorities) {
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
UUID.randomUUID().toString(), principal, authorities);
authenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenantId, true));
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(authenticationToken);
SecurityContextHolder.setContext(securityContextImpl);
}

private static void setSystemContext(final SecurityContext oldContext) {
final Authentication oldAuthentication = oldContext.getAuthentication();
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
Expand Down

0 comments on commit e3bbee5

Please sign in to comment.