Target poll DB performance improvements (#613)

* Async update on last target query.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* 10 seconds

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Completed batch and optimzed schema.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Cleanup and test.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* readibility.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Batch update.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Sonar issue fixed.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2017-12-18 13:50:25 +01:00
committed by GitHub
parent 80d9f1b8fc
commit 7d0bf3a162
22 changed files with 513 additions and 75 deletions

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.repository; package org.eclipse.hawkbit.repository;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -34,6 +36,45 @@ public class RepositoryProperties {
*/ */
private boolean publishTargetPollEvent = true; private boolean publishTargetPollEvent = true;
/**
* Maximum number of poll operations queued before flush.
*/
private int pollPersistenceQueueSize = 10_000;
/**
* Maximum time before queue is flushed in {@link TimeUnit#MILLISECONDS}.
*/
private long pollPersistenceFlushTime = TimeUnit.SECONDS.toMillis(10);
/**
* Set to true to persist polls immediately.
*/
private boolean eagerPollPersistence;
public boolean isEagerPollPersistence() {
return eagerPollPersistence;
}
public void setEagerPollPersistence(final boolean eagerPollPersistence) {
this.eagerPollPersistence = eagerPollPersistence;
}
public long getPollPersistenceFlushTime() {
return pollPersistenceFlushTime;
}
public void setPollPersistenceFlushTime(final long pollPersistenceFlushTime) {
this.pollPersistenceFlushTime = pollPersistenceFlushTime;
}
public int getPollPersistenceQueueSize() {
return pollPersistenceQueueSize;
}
public void setPollPersistenceQueueSize(final int pollPersistenceQueueSize) {
this.pollPersistenceQueueSize = pollPersistenceQueueSize;
}
public boolean isRejectActionStatusForClosedAction() { public boolean isRejectActionStatusForClosedAction() {
return rejectActionStatusForClosedAction; return rejectActionStatusForClosedAction;
} }

View File

@@ -26,6 +26,11 @@ public class TargetPollEvent extends RemoteTenantAwareEvent {
// for serialization libs like jackson // for serialization libs like jackson
} }
public TargetPollEvent(final String controllerId, final String tenant, final String applicationId) {
super(controllerId, tenant, applicationId);
this.controllerId = controllerId;
}
public TargetPollEvent(final Target target, final String applicationId) { public TargetPollEvent(final Target target, final String applicationId) {
super(target.getControllerId(), target.getTenant(), applicationId); super(target.getControllerId(), target.getTenant(), applicationId);
this.controllerId = target.getControllerId(); this.controllerId = target.getControllerId();

View File

@@ -25,7 +25,7 @@ public interface ActionStatus extends TenantAwareBaseEntity {
* @return time in {@link TimeUnit#MILLISECONDS} when the status was * @return time in {@link TimeUnit#MILLISECONDS} when the status was
* reported. * reported.
*/ */
Long getOccurredAt(); long getOccurredAt();
/** /**
* @return {@link Action} this {@link ActionStatus} belongs to. * @return {@link Action} this {@link ActionStatus} belongs to.

View File

@@ -14,9 +14,15 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
@@ -51,6 +57,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -66,10 +73,20 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Retryable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/** /**
* JPA based {@link ControllerManagement} implementation. * JPA based {@link ControllerManagement} implementation.
* *
@@ -79,6 +96,8 @@ import org.springframework.validation.annotation.Validated;
public class JpaControllerManagement implements ControllerManagement { public class JpaControllerManagement implements ControllerManagement {
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class); private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
private final BlockingDeque<TargetPoll> queue;
@Autowired @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@@ -97,9 +116,6 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired @Autowired
private QuotaManagement quotaManagement; private QuotaManagement quotaManagement;
@Autowired
private RepositoryProperties repositoryProperties;
@Autowired @Autowired
private TenantConfigurationManagement tenantConfigurationManagement; private TenantConfigurationManagement tenantConfigurationManagement;
@@ -121,6 +137,38 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired @Autowired
private SoftwareModuleMetadataRepository softwareModuleMetadataRepository; private SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private TenantAware tenantAware;
private final RepositoryProperties repositoryProperties;
JpaControllerManagement(final ScheduledExecutorService executorService,
final RepositoryProperties repositoryProperties) {
if (!repositoryProperties.isEagerPollPersistence()) {
executorService.scheduleWithFixedDelay(this::flushUpdateQueue,
repositoryProperties.getPollPersistenceFlushTime(),
repositoryProperties.getPollPersistenceFlushTime(), TimeUnit.MILLISECONDS);
queue = new LinkedBlockingDeque<>(repositoryProperties.getPollPersistenceQueueSize());
} else {
queue = null;
}
this.repositoryProperties = repositoryProperties;
}
private <T> T runInNewTransaction(final String transactionName, final TransactionCallback<T> action) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(transactionName);
def.setReadOnly(false);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(action);
}
@Override @Override
public String getPollingTime() { public String getPollingTime() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
@@ -214,19 +262,121 @@ public class JpaControllerManagement implements ControllerManagement {
return updateTargetStatus(target, address); return updateTargetStatus(target, address);
} }
/**
* Flush the update queue by means to persisting
* {@link Target#getLastTargetQuery()}.
*/
private void flushUpdateQueue() {
LOG.debug("Run flushUpdateQueue.");
final int size = queue.size();
if (size <= 0) {
return;
}
LOG.debug("{} events in flushUpdateQueue.", size);
final Set<TargetPoll> events = Sets.newHashSetWithExpectedSize(queue.size());
final int drained = queue.drainTo(events);
if (drained <= 0) {
return;
}
try {
events.stream().collect(Collectors.groupingBy(TargetPoll::getTenant)).forEach((tenant, polls) -> {
final TransactionCallback<Void> createTransaction = status -> updateLastTargetQueries(tenant, polls);
tenantAware.runAsTenant(tenant, () -> runInNewTransaction("flushUpdateQueue", createTransaction));
});
} catch (final RuntimeException ex) {
LOG.error("Failed to persist UpdateQueue content.", ex);
return;
}
LOG.debug("{} events persisted.", drained);
}
private Void updateLastTargetQueries(final String tenant, final List<TargetPoll> polls) {
LOG.debug("Persist {} targetqueries.", polls.size());
final List<List<String>> pollChunks = Lists.partition(
polls.stream().map(TargetPoll::getControllerId).collect(Collectors.toList()),
Constants.MAX_ENTRIES_IN_STATEMENT);
pollChunks.forEach(chunk -> {
setLastTargetQuery(tenant, System.currentTimeMillis(), chunk);
chunk.forEach(controllerId -> afterCommit.afterCommit(() -> eventPublisher
.publishEvent(new TargetPollEvent(controllerId, tenant, applicationContext.getId()))));
});
return null;
}
/**
* Sets {@link Target#getLastTargetQuery()} by native SQL in order to avoid
* raising opt lock revision as this update is not mission critical and in
* fact only written by {@link ControllerManagement}, i.e. the target
* itself.
*/
private void setLastTargetQuery(final String tenant, final long currentTimeMillis, final List<String> chunk) {
final Map<String, String> paramMapping = Maps.newHashMapWithExpectedSize(chunk.size());
for (int i = 0; i < chunk.size(); i++) {
paramMapping.put("cid" + i, chunk.get(i));
}
final Query updateQuery = entityManager.createNativeQuery(
"UPDATE sp_target t SET t.last_target_query = #last_target_query WHERE t.controller_id IN ("
+ formatQueryInStatementParams(paramMapping.keySet()) + ") AND t.tenant = #tenant");
paramMapping.entrySet().forEach(entry -> updateQuery.setParameter(entry.getKey(), entry.getValue()));
updateQuery.setParameter("last_target_query", currentTimeMillis);
updateQuery.setParameter("tenant", tenant);
final int updated = updateQuery.executeUpdate();
if (updated < chunk.size()) {
LOG.error("Targets polls could not be applied completely ({} instead of {}).", updated, chunk.size());
}
}
private static String formatQueryInStatementParams(final Collection<String> paramNames) {
return "#" + Joiner.on(",#").join(paramNames);
}
/**
* Stores target directly to DB in case either {@link Target#getAddress()}
* or {@link Target#getUpdateStatus()} changes or the buffer queue is full.
*
*/
private Target updateTargetStatus(final JpaTarget toUpdate, final URI address) { private Target updateTargetStatus(final JpaTarget toUpdate, final URI address) {
boolean storeEager = isStoreEager(toUpdate, address);
if (TargetUpdateStatus.UNKNOWN.equals(toUpdate.getUpdateStatus())) { if (TargetUpdateStatus.UNKNOWN.equals(toUpdate.getUpdateStatus())) {
toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED); toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED);
storeEager = true;
} }
toUpdate.setAddress(address.toString()); if (storeEager || !queue.offer(new TargetPoll(toUpdate))) {
toUpdate.setLastTargetQuery(System.currentTimeMillis()); toUpdate.setAddress(address.toString());
toUpdate.setLastTargetQuery(System.currentTimeMillis());
afterCommit.afterCommit( afterCommit.afterCommit(
() -> eventPublisher.publishEvent(new TargetPollEvent(toUpdate, applicationContext.getId()))); () -> eventPublisher.publishEvent(new TargetPollEvent(toUpdate, applicationContext.getId())));
return targetRepository.save(toUpdate); return targetRepository.save(toUpdate);
}
return toUpdate;
}
private boolean isStoreEager(final JpaTarget toUpdate, final URI address) {
if (repositoryProperties.isEagerPollPersistence()) {
return true;
} else if (toUpdate.getAddress() == null) {
return true;
} else {
return !toUpdate.getAddress().equals(address);
}
} }
@Override @Override
@@ -540,4 +690,62 @@ public class JpaControllerManagement implements ControllerManagement {
.getContent().stream().collect(Collectors.groupingBy(o -> (Long) o[0], .getContent().stream().collect(Collectors.groupingBy(o -> (Long) o[0],
Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList()))); Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList())));
} }
private static class TargetPoll {
private final String tenant;
private final String controllerId;
TargetPoll(final Target target) {
this.tenant = target.getTenant();
this.controllerId = target.getControllerId();
}
public String getTenant() {
return tenant;
}
public String getControllerId() {
return controllerId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((controllerId == null) ? 0 : controllerId.hashCode());
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TargetPoll other = (TargetPoll) obj;
if (controllerId == null) {
if (other.controllerId != null) {
return false;
}
} else if (!controllerId.equals(other.controllerId)) {
return false;
}
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
return true;
}
}
} }

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -554,8 +555,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
ControllerManagement controllerManagement() { ControllerManagement controllerManagement(final ScheduledExecutorService executorService,
return new JpaControllerManagement(); final RepositoryProperties repositoryProperties) {
return new JpaControllerManagement(executorService, repositoryProperties);
} }
@Bean @Bean

View File

@@ -36,7 +36,6 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>, JpaSpecificationExecutor<JpaTarget> { public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>, JpaSpecificationExecutor<JpaTarget> {
/** /**
* Sets {@link JpaTarget#getAssignedDistributionSet()}. * Sets {@link JpaTarget#getAssignedDistributionSet()}.
* *

View File

@@ -14,8 +14,6 @@ import java.util.List;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ConstraintMode; import javax.persistence.ConstraintMode;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.ForeignKey; import javax.persistence.ForeignKey;
import javax.persistence.Index; import javax.persistence.Index;
@@ -40,6 +38,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
/** /**
@@ -71,8 +72,12 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "active") @Column(name = "active")
private boolean active; private boolean active;
@Column(name = "action_type", nullable = false, length = 16) @Column(name = "action_type", nullable = false)
@Enumerated(EnumType.STRING) @ObjectTypeConverter(name = "actionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FORCED", dataValue = "0"),
@ConversionValue(objectValue = "SOFT", dataValue = "1"),
@ConversionValue(objectValue = "TIMEFORCED", dataValue = "2") })
@Convert("actionType")
@NotNull @NotNull
private ActionType actionType; private ActionType actionType;
@@ -80,6 +85,18 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
private long forcedTime; private long forcedTime;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@ObjectTypeConverter(name = "status", objectType = Action.Status.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FINISHED", dataValue = "0"),
@ConversionValue(objectValue = "ERROR", dataValue = "1"),
@ConversionValue(objectValue = "WARNING", dataValue = "2"),
@ConversionValue(objectValue = "RUNNING", dataValue = "3"),
@ConversionValue(objectValue = "CANCELED", dataValue = "4"),
@ConversionValue(objectValue = "CANCELING", dataValue = "5"),
@ConversionValue(objectValue = "RETRIEVED", dataValue = "6"),
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9") })
@Convert("status")
@NotNull @NotNull
private Status status; private Status status;

View File

@@ -31,6 +31,9 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
@@ -50,8 +53,8 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "target_occurred_at") @Column(name = "target_occurred_at", nullable = false, updatable = false)
private Long occurredAt; private long occurredAt;
@ManyToOne(fetch = FetchType.LAZY, optional = false) @ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "action", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action")) @JoinColumn(name = "action", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
@@ -59,6 +62,18 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
private JpaAction action; private JpaAction action;
@Column(name = "status", nullable = false, updatable = false) @Column(name = "status", nullable = false, updatable = false)
@ObjectTypeConverter(name = "status", objectType = Action.Status.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FINISHED", dataValue = "0"),
@ConversionValue(objectValue = "ERROR", dataValue = "1"),
@ConversionValue(objectValue = "WARNING", dataValue = "2"),
@ConversionValue(objectValue = "RUNNING", dataValue = "3"),
@ConversionValue(objectValue = "CANCELED", dataValue = "4"),
@ConversionValue(objectValue = "CANCELING", dataValue = "5"),
@ConversionValue(objectValue = "RETRIEVED", dataValue = "6"),
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9") })
@Convert("status")
@NotNull @NotNull
private Status status; private Status status;
@@ -66,7 +81,7 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
@ElementCollection(fetch = FetchType.LAZY, targetClass = String.class) @ElementCollection(fetch = FetchType.LAZY, targetClass = String.class)
@CollectionTable(name = "sp_action_status_messages", joinColumns = @JoinColumn(name = "action_status_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")), indexes = { @CollectionTable(name = "sp_action_status_messages", joinColumns = @JoinColumn(name = "action_status_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")), indexes = {
@Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") }) @Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") })
@Column(name = "detail_message", length = 512) @Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false, updatable = false)
private List<String> messages; private List<String> messages;
/** /**
@@ -97,7 +112,7 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
* @param message * @param message
* the message which should be added to this action status * the message which should be added to this action status
*/ */
public JpaActionStatus(final JpaAction action, final Status status, final Long occurredAt, final String message) { public JpaActionStatus(final JpaAction action, final Status status, final long occurredAt, final String message) {
this.action = action; this.action = action;
this.status = status; this.status = status;
this.occurredAt = occurredAt; this.occurredAt = occurredAt;
@@ -125,11 +140,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
} }
@Override @Override
public Long getOccurredAt() { public long getOccurredAt() {
return occurredAt; return occurredAt;
} }
public void setOccurredAt(final Long occurredAt) { public void setOccurredAt(final long occurredAt) {
this.occurredAt = occurredAt; this.occurredAt = occurredAt;
} }

View File

@@ -41,13 +41,13 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "sha1_hash", length = 40, nullable = false, updatable = false) @Column(name = "sha1_hash", length = 40, nullable = false, updatable = false)
@Size(max = 40) @Size(min = 1, max = 40)
@NotEmpty @NotNull
private String sha1Hash; private String sha1Hash;
@Column(name = "provided_file_name", length = 256) @Column(name = "provided_file_name", length = 256)
@Size(max = 256) @Size(min = 1, max = 256)
@NotEmpty @NotNull
private String filename; private String filename;
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY) @ManyToOne(optional = false, cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY)

View File

@@ -15,8 +15,6 @@ import java.util.stream.Collectors;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ConstraintMode; import javax.persistence.ConstraintMode;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.ForeignKey; import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
@@ -30,10 +28,12 @@ import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.CascadeOnDelete;
@@ -44,7 +44,6 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.queries.UpdateObjectQuery; import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet; import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* JPA implementation of a {@link Rollout}. * JPA implementation of a {@link Rollout}.
@@ -56,18 +55,6 @@ import org.hibernate.validator.constraints.NotEmpty;
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
@ObjectTypeConverter(name = "rolloutstatus", objectType = Rollout.RolloutStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "CREATING", dataValue = "0"),
@ConversionValue(objectValue = "READY", dataValue = "1"),
@ConversionValue(objectValue = "PAUSED", dataValue = "2"),
@ConversionValue(objectValue = "STARTING", dataValue = "3"),
@ConversionValue(objectValue = "STOPPED", dataValue = "4"),
@ConversionValue(objectValue = "RUNNING", dataValue = "5"),
@ConversionValue(objectValue = "FINISHED", dataValue = "6"),
@ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"),
@ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"),
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10") })
public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity { public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -78,9 +65,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@OneToMany(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, mappedBy = "rollout") @OneToMany(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, mappedBy = "rollout")
private List<JpaRolloutGroup> rolloutGroups; private List<JpaRolloutGroup> rolloutGroups;
@Column(name = "target_filter", length = 1024, nullable = false) @Column(name = "target_filter", length = TargetFilterQuery.QUERY_MAX_SIZE, nullable = false)
@Size(max = 1024) @Size(min = 1, max = TargetFilterQuery.QUERY_MAX_SIZE)
@NotEmpty @NotNull
private String targetFilterQuery; private String targetFilterQuery;
@ManyToOne(fetch = FetchType.LAZY, optional = false) @ManyToOne(fetch = FetchType.LAZY, optional = false)
@@ -89,6 +76,18 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
private JpaDistributionSet distributionSet; private JpaDistributionSet distributionSet;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@ObjectTypeConverter(name = "rolloutstatus", objectType = Rollout.RolloutStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "CREATING", dataValue = "0"),
@ConversionValue(objectValue = "READY", dataValue = "1"),
@ConversionValue(objectValue = "PAUSED", dataValue = "2"),
@ConversionValue(objectValue = "STARTING", dataValue = "3"),
@ConversionValue(objectValue = "STOPPED", dataValue = "4"),
@ConversionValue(objectValue = "RUNNING", dataValue = "5"),
@ConversionValue(objectValue = "FINISHED", dataValue = "6"),
@ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"),
@ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"),
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10") })
@Convert("rolloutstatus") @Convert("rolloutstatus")
@NotNull @NotNull
private RolloutStatus status = RolloutStatus.CREATING; private RolloutStatus status = RolloutStatus.CREATING;
@@ -96,8 +95,12 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Column(name = "last_check") @Column(name = "last_check")
private long lastCheck; private long lastCheck;
@Column(name = "action_type", nullable = false, length = 16) @Column(name = "action_type", nullable = false)
@Enumerated(EnumType.STRING) @ObjectTypeConverter(name = "actionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "FORCED", dataValue = "0"),
@ConversionValue(objectValue = "SOFT", dataValue = "1"),
@ConversionValue(objectValue = "TIMEFORCED", dataValue = "2") })
@Convert("actionType")
@NotNull @NotNull
private ActionType actionType = ActionType.FORCED; private ActionType actionType = ActionType.FORCED;

View File

@@ -48,13 +48,6 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
@ObjectTypeConverter(name = "rolloutgroupstatus", objectType = RolloutGroup.RolloutGroupStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "READY", dataValue = "0"),
@ConversionValue(objectValue = "SCHEDULED", dataValue = "1"),
@ConversionValue(objectValue = "FINISHED", dataValue = "2"),
@ConversionValue(objectValue = "ERROR", dataValue = "3"),
@ConversionValue(objectValue = "RUNNING", dataValue = "4"),
@ConversionValue(objectValue = "CREATING", dataValue = "5") })
public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGroup, EventAwareEntity { public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGroup, EventAwareEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -64,6 +57,13 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
private JpaRollout rollout; private JpaRollout rollout;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@ObjectTypeConverter(name = "rolloutgroupstatus", objectType = RolloutGroup.RolloutGroupStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "READY", dataValue = "0"),
@ConversionValue(objectValue = "SCHEDULED", dataValue = "1"),
@ConversionValue(objectValue = "FINISHED", dataValue = "2"),
@ConversionValue(objectValue = "ERROR", dataValue = "3"),
@ConversionValue(objectValue = "RUNNING", dataValue = "4"),
@ConversionValue(objectValue = "CREATING", dataValue = "5") })
@Convert("rolloutgroupstatus") @Convert("rolloutgroupstatus")
private RolloutGroupStatus status = RolloutGroupStatus.CREATING; private RolloutGroupStatus status = RolloutGroupStatus.CREATING;

View File

@@ -28,8 +28,6 @@ import javax.persistence.Column;
import javax.persistence.ConstraintMode; import javax.persistence.ConstraintMode;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.persistence.ForeignKey; import javax.persistence.ForeignKey;
import javax.persistence.Index; import javax.persistence.Index;
@@ -63,6 +61,9 @@ import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagement
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -87,10 +88,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
private static final Logger LOG = LoggerFactory.getLogger(JpaTarget.class); private static final Logger LOG = LoggerFactory.getLogger(JpaTarget.class);
private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = Arrays.asList("lastTargetQuery", private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = Arrays.asList("lastTargetQuery", "address",
"lastTargetQuery", "address", "optLockRevision", "lastModifiedAt", "lastModifiedBy"); "optLockRevision", "lastModifiedAt", "lastModifiedBy");
@Column(name = "controller_id", length = Target.CONTROLLER_ID_MAX_SIZE) @Column(name = "controller_id", length = Target.CONTROLLER_ID_MAX_SIZE, updatable = false, nullable = false)
@Size(min = 1, max = Target.CONTROLLER_ID_MAX_SIZE) @Size(min = 1, max = Target.CONTROLLER_ID_MAX_SIZE)
@NotNull @NotNull
@Pattern(regexp = "[.\\S]*", message = "has whitespaces which are not allowed") @Pattern(regexp = "[.\\S]*", message = "has whitespaces which are not allowed")
@@ -130,8 +131,14 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Column(name = "install_date") @Column(name = "install_date")
private Long installationDate; private Long installationDate;
@Column(name = "update_status", nullable = false, length = 16) @Column(name = "update_status", nullable = false)
@Enumerated(EnumType.STRING) @ObjectTypeConverter(name = "updateStatus", objectType = TargetUpdateStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "UNKNOWN", dataValue = "0"),
@ConversionValue(objectValue = "IN_SYNC", dataValue = "1"),
@ConversionValue(objectValue = "PENDING", dataValue = "2"),
@ConversionValue(objectValue = "ERROR", dataValue = "3"),
@ConversionValue(objectValue = "REGISTERED", dataValue = "4") })
@Convert("updateStatus")
@NotNull @NotNull
private TargetUpdateStatus updateStatus = TargetUpdateStatus.UNKNOWN; private TargetUpdateStatus updateStatus = TargetUpdateStatus.UNKNOWN;

View File

@@ -19,12 +19,12 @@ import javax.persistence.JoinColumn;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* Tenant entity with meta data that is configured globally for the entire * Tenant entity with meta data that is configured globally for the entire
@@ -45,8 +45,8 @@ public class JpaTenantMetaData extends AbstractJpaBaseEntity implements TenantMe
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, updatable = false, length = 40) @Column(name = "tenant", nullable = false, updatable = false, length = 40)
@Size(max = 40) @Size(min = 1, max = 40)
@NotEmpty @NotNull
private String tenant; private String tenant;
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)

View File

@@ -0,0 +1,25 @@
ALTER TABLE sp_action ADD COLUMN action_type_new integer not null;
UPDATE sp_action SET action_type_new =
CASE WHEN (action_type = 'SOFT') THEN 1
WHEN (action_type = 'TIMEFORCED') THEN 2
ELSE 0 END;
ALTER TABLE sp_action DROP COLUMN action_type;
ALTER TABLE sp_action CHANGE COLUMN action_type_new action_type integer;
ALTER TABLE sp_rollout ADD COLUMN action_type_new integer not null;
UPDATE sp_rollout SET action_type_new =
CASE WHEN (action_type = 'SOFT') THEN 1
WHEN (action_type = 'TIMEFORCED') THEN 2
ELSE 0 END;
ALTER TABLE sp_rollout DROP COLUMN action_type;
ALTER TABLE sp_rollout CHANGE COLUMN action_type_new action_type integer;
ALTER TABLE sp_target ADD COLUMN update_status_new integer not null;
UPDATE sp_target SET update_status_new =
CASE WHEN (update_status = 'IN_SYNC') THEN 1
WHEN (update_status = 'PENDING') THEN 2
WHEN (update_status = 'ERROR') THEN 3
WHEN (update_status = 'REGISTERED') THEN 4
ELSE 0 END;
ALTER TABLE sp_target DROP COLUMN update_status;
ALTER TABLE sp_target CHANGE COLUMN update_status_new update_status integer;

View File

@@ -0,0 +1,12 @@
ALTER TABLE sp_action_status_messages CHANGE COLUMN detail_message detail_message varchar(512) not null;
ALTER TABLE sp_action CHANGE COLUMN distribution_set distribution_set bigint not null;
ALTER TABLE sp_action CHANGE COLUMN target target bigint not null;
ALTER TABLE sp_action CHANGE COLUMN status status integer not null;
ALTER TABLE sp_action_status CHANGE COLUMN target_occurred_at target_occurred_at bigint not null;
ALTER TABLE sp_action_status CHANGE COLUMN status status integer not null;
ALTER TABLE sp_rollout CHANGE COLUMN distribution_set distribution_set bigint not null;
ALTER TABLE sp_rollout CHANGE COLUMN status status integer not null;
ALTER TABLE sp_rolloutgroup CHANGE COLUMN rollout rollout bigint not null;
ALTER TABLE sp_rolloutgroup CHANGE COLUMN status status integer not null;
ALTER TABLE sp_artifact CHANGE COLUMN sha1_hash sha1_hash varchar(40) not null;
ALTER TABLE sp_target CHANGE COLUMN controller_id controller_id varchar(64) not null;

View File

@@ -0,0 +1,25 @@
ALTER TABLE sp_action ADD COLUMN action_type_new integer not null;
UPDATE sp_action SET action_type_new =
CASE WHEN (action_type = 'SOFT') THEN 1
WHEN (action_type = 'TIMEFORCED') THEN 2
ELSE 0 END;
ALTER TABLE sp_action DROP COLUMN action_type;
ALTER TABLE sp_action CHANGE COLUMN action_type_new action_type integer;
ALTER TABLE sp_rollout ADD COLUMN action_type_new integer not null;
UPDATE sp_rollout SET action_type_new =
CASE WHEN (action_type = 'SOFT') THEN 1
WHEN (action_type = 'TIMEFORCED') THEN 2
ELSE 0 END;
ALTER TABLE sp_rollout DROP COLUMN action_type;
ALTER TABLE sp_rollout CHANGE COLUMN action_type_new action_type integer;
ALTER TABLE sp_target ADD COLUMN update_status_new integer not null;
UPDATE sp_target SET update_status_new =
CASE WHEN (update_status = 'IN_SYNC') THEN 1
WHEN (update_status = 'PENDING') THEN 2
WHEN (update_status = 'ERROR') THEN 3
WHEN (update_status = 'REGISTERED') THEN 4
ELSE 0 END;
ALTER TABLE sp_target DROP COLUMN update_status;
ALTER TABLE sp_target CHANGE COLUMN update_status_new update_status integer;

View File

@@ -0,0 +1,12 @@
ALTER TABLE sp_action_status_messages CHANGE COLUMN detail_message detail_message varchar(512) not null;
ALTER TABLE sp_action CHANGE COLUMN distribution_set distribution_set bigint not null;
ALTER TABLE sp_action CHANGE COLUMN target target bigint not null;
ALTER TABLE sp_action CHANGE COLUMN status status integer not null;
ALTER TABLE sp_action_status CHANGE COLUMN target_occurred_at target_occurred_at bigint not null;
ALTER TABLE sp_action_status CHANGE COLUMN status status integer not null;
ALTER TABLE sp_rollout CHANGE COLUMN distribution_set distribution_set bigint not null;
ALTER TABLE sp_rollout CHANGE COLUMN status status integer not null;
ALTER TABLE sp_rolloutgroup CHANGE COLUMN rollout rollout bigint not null;
ALTER TABLE sp_rolloutgroup CHANGE COLUMN status status integer not null;
ALTER TABLE sp_artifact CHANGE COLUMN sha1_hash sha1_hash varchar(40) not null;
ALTER TABLE sp_target CHANGE COLUMN controller_id controller_id varchar(64) not null;

View File

@@ -0,0 +1,56 @@
/**
* 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
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features("Component Tests - Repository")
@Stories("Controller Management")
@TestPropertySource(locations = "classpath:/jpa-test.properties", properties = {
"hawkbit.server.repository.eagerPollPersistence=false",
"hawkbit.server.repository.pollPersistenceFlushTime=1000" })
public class LazyControllerManagementTest extends AbstractJpaIntegrationTest {
@Autowired
private RepositoryProperties repositoryProperties;
@Test
@Description("Verfies that lazy target poll update is executed as specified.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 2) })
public void lazyFindOrRegisterTargetIfItDoesNotexist() throws InterruptedException {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", LOCALHOST);
assertThat(target).as("target should not be null").isNotNull();
TimeUnit.MILLISECONDS.sleep(10);
controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", LOCALHOST);
TimeUnit.MILLISECONDS.sleep(repositoryProperties.getPollPersistenceFlushTime() + 1);
final Target updated = targetManagement.get(target.getId()).get();
assertThat(updated.getOptLockRevision()).isEqualTo(target.getOptLockRevision());
assertThat(updated.getLastTargetQuery()).isGreaterThan(target.getLastTargetQuery());
}
}

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.test;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.ControllerPollProperties; import org.eclipse.hawkbit.ControllerPollProperties;
import org.eclipse.hawkbit.HawkbitServerProperties; import org.eclipse.hawkbit.HawkbitServerProperties;
@@ -60,9 +61,12 @@ import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.MessageConverter;
import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/** /**
* Spring context configuration required for Dev.Environment. * Spring context configuration required for Dev.Environment.
*/ */
@@ -186,6 +190,12 @@ public class TestConfiguration implements AsyncConfigurer {
return new SimpleAsyncUncaughtExceptionHandler(); return new SimpleAsyncUncaughtExceptionHandler();
} }
@Bean
public ScheduledExecutorService scheduledExecutorService() {
return new DelegatingSecurityContextScheduledExecutorService(Executors.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("central-scheduled-executor-pool-%d").build()));
}
/** /**
* *
* @return returns a VirtualPropertyReplacer * @return returns a VirtualPropertyReplacer

View File

@@ -21,7 +21,6 @@ import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemProperties;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager; import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
@@ -176,9 +175,6 @@ public abstract class AbstractIntegrationTest {
@Autowired @Autowired
protected SystemSecurityContext systemSecurityContext; protected SystemSecurityContext systemSecurityContext;
@Autowired
private ArtifactFilesystemProperties artifactFilesystemProperties;
@Autowired @Autowired
protected ArtifactRepository binaryArtifactRepository; protected ArtifactRepository binaryArtifactRepository;
@@ -271,18 +267,18 @@ public abstract class AbstractIntegrationTest {
osType = securityRule osType = securityRule
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS)); .runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
osType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement.update( osType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement
entityFactory.softwareModuleType().update(osType.getId()).description(description))); .update(entityFactory.softwareModuleType().update(osType.getId()).description(description)));
appType = securityRule.runAsPrivileged( appType = securityRule.runAsPrivileged(
() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE)); () -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE));
appType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement.update( appType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement
entityFactory.softwareModuleType().update(appType.getId()).description(description))); .update(entityFactory.softwareModuleType().update(appType.getId()).description(description)));
runtimeType = securityRule runtimeType = securityRule
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT)); .runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
runtimeType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement.update( runtimeType = securityRule.runAsPrivileged(() -> softwareModuleTypeManagement
entityFactory.softwareModuleType().update(runtimeType.getId()).description(description))); .update(entityFactory.softwareModuleType().update(runtimeType.getId()).description(description)));
standardDsType = securityRule.runAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType()); standardDsType = securityRule.runAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
} }

View File

@@ -23,6 +23,10 @@ spring.jpa.properties.eclipselink.logging.parameters=true
## JPA Repository - START ## JPA Repository - START
spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE
## JPA Repository - END ## JPA Repository - END
# Enforce persistence of targetpolls for test predictability.
hawkbit.server.repository.eagerPollPersistence=true
# Default properties for test that can be overridden during test run - END # Default properties for test that can be overridden during test run - END
# Properties that are managed by autoconfigure module at runtime and not available during test - START # Properties that are managed by autoconfigure module at runtime and not available during test - START

View File

@@ -238,7 +238,8 @@ public abstract class AbstractMetadataPopupLayout<E extends NamedVersionedEntity
private TextField createKeyTextField() { private TextField createKeyTextField() {
final TextField keyField = new TextFieldBuilder().caption(i18n.getMessage("textfield.key")).required(true) final TextField keyField = new TextFieldBuilder().caption(i18n.getMessage("textfield.key")).required(true)
.prompt(i18n.getMessage("textfield.key")).immediate(true) .prompt(i18n.getMessage("textfield.key")).immediate(true)
.id(UIComponentIdProvider.METADATA_KEY_FIELD_ID).maxLengthAllowed(128).buildTextComponent(); .id(UIComponentIdProvider.METADATA_KEY_FIELD_ID).maxLengthAllowed(MetaData.KEY_MAX_SIZE)
.buildTextComponent();
keyField.addTextChangeListener(this::onKeyChange); keyField.addTextChangeListener(this::onKeyChange);
keyField.setTextChangeEventMode(TextChangeEventMode.EAGER); keyField.setTextChangeEventMode(TextChangeEventMode.EAGER);
keyField.setWidth("100%"); keyField.setWidth("100%");
@@ -248,7 +249,7 @@ public abstract class AbstractMetadataPopupLayout<E extends NamedVersionedEntity
private TextArea createValueTextField() { private TextArea createValueTextField() {
valueTextArea = new TextAreaBuilder().caption(i18n.getMessage("textfield.value")).required(true) valueTextArea = new TextAreaBuilder().caption(i18n.getMessage("textfield.value")).required(true)
.prompt(i18n.getMessage("textfield.value")).immediate(true).id(UIComponentIdProvider.METADATA_VALUE_ID) .prompt(i18n.getMessage("textfield.value")).immediate(true).id(UIComponentIdProvider.METADATA_VALUE_ID)
.maxLengthAllowed(4000).buildTextComponent(); .maxLengthAllowed(MetaData.VALUE_MAX_SIZE).buildTextComponent();
valueTextArea.setNullRepresentation(""); valueTextArea.setNullRepresentation("");
valueTextArea.setSizeFull(); valueTextArea.setSizeFull();
valueTextArea.setHeight(100, Unit.PERCENTAGE); valueTextArea.setHeight(100, Unit.PERCENTAGE);