External ref for actions and status-update events (#830)
* 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>
This commit is contained in:
committed by
Stefan Behl
parent
e80399bc79
commit
fba6cf9787
@@ -429,4 +429,26 @@ public interface ControllerManagement {
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* Update operations to be executed by the target.
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
@@ -98,6 +105,16 @@ public interface Action extends TenantAwareBaseEntity {
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -240,6 +240,18 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
@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
|
||||
@@ -504,4 +516,16 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import javax.persistence.Query;
|
||||
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;
|
||||
@@ -372,6 +374,11 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
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))
|
||||
@@ -1042,6 +1049,11 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
}
|
||||
}
|
||||
|
||||
@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())));
|
||||
|
||||
@@ -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() {
|
||||
@@ -332,4 +335,14 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExternalRef(String externalRef) {
|
||||
this.externalRef = externalRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExternalRef() {
|
||||
return externalRef;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -54,6 +55,7 @@ import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
|
||||
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;
|
||||
@@ -1315,6 +1317,44 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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.")
|
||||
|
||||
@@ -11,12 +11,18 @@ package org.eclipse.hawkbit.security;
|
||||
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;
|
||||
@@ -111,6 +117,42 @@ public class SystemSecurityContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -119,6 +161,16 @@ public class SystemSecurityContext {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user