Started with #100 by means of splitting the management services into API
and Jpa implementation. Signed-off-by: Kai Zimmermann <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -12,8 +12,8 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder;
|
||||
import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder;
|
||||
import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder;
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.eclipse.hawkbit.eventbus.event.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetInfoUpdateEvent;
|
||||
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
|
||||
@@ -66,7 +66,7 @@ public class EntityChangeEventListener {
|
||||
* in case exception happens in the
|
||||
* {@link ProceedingJoinPoint#proceed()}
|
||||
*/
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.TargetInfoRepository.save(..))")
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetInfoRepository.save(..))")
|
||||
// Exception squid:S00112 - Is aspectJ proxy
|
||||
@SuppressWarnings({ "squid:S00112" })
|
||||
public Object targetCreated(final ProceedingJoinPoint joinpoint) throws Throwable {
|
||||
@@ -93,7 +93,7 @@ public class EntityChangeEventListener {
|
||||
* in case exception happens in the
|
||||
* {@link ProceedingJoinPoint#proceed()}
|
||||
*/
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.TargetRepository.deleteByIdIn(..))")
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.deleteByIdIn(..))")
|
||||
// Exception squid:S00112 - Is aspectJ proxy
|
||||
@SuppressWarnings({ "squid:S00112" })
|
||||
public Object targetDeletedById(final ProceedingJoinPoint joinpoint) throws Throwable {
|
||||
@@ -115,8 +115,8 @@ public class EntityChangeEventListener {
|
||||
* in case exception happens in the
|
||||
* {@link ProceedingJoinPoint#proceed()}
|
||||
*/
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.TargetRepository.delete(..))")
|
||||
// Exception squid:S00112 - Is aspectJ proxy
|
||||
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.delete(..))")
|
||||
// Exception squid:S00112 - Is aspectJ proxy
|
||||
@SuppressWarnings({ "squid:S00112", "unchecked" })
|
||||
public Object targetDeleted(final ProceedingJoinPoint joinpoint) throws Throwable {
|
||||
final String currentTenant = tenantAware.getCurrentTenant();
|
||||
@@ -146,7 +146,7 @@ public class EntityChangeEventListener {
|
||||
afterCommit.afterCommit(() -> eventBus.post(new TargetDeletedEvent(tenant, targetId)));
|
||||
}
|
||||
|
||||
private boolean isTargetInfoNew(final Object targetInfo) {
|
||||
private static boolean isTargetInfoNew(final Object targetInfo) {
|
||||
return ((TargetInfo) targetInfo).isNew();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,7 @@ import java.util.List;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
|
||||
@@ -31,47 +27,110 @@ import org.eclipse.hawkbit.repository.model.ExternalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.specifications.SoftwareModuleSpecification;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Service for {@link Artifact} management operations.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class ArtifactManagement {
|
||||
public interface ArtifactManagement {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ArtifactManagement.class);
|
||||
/**
|
||||
* Creates {@link ExternalArtifact} based on given provider.
|
||||
*
|
||||
* @param externalRepository
|
||||
* the artifact is located in
|
||||
* @param urlSuffix
|
||||
* of the artifact
|
||||
* {@link ExternalArtifactProvider#getDefaultSuffix()} is used if
|
||||
* empty.
|
||||
* @param moduleId
|
||||
* to assign the artifact to
|
||||
*
|
||||
* @return created {@link ExternalArtifact}
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if {@link SoftwareModule} with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
|
||||
ExternalArtifact createExternalArtifact(@NotNull ExternalArtifactProvider externalRepository, String urlSuffix,
|
||||
@NotNull Long moduleId);
|
||||
|
||||
@Autowired
|
||||
private LocalArtifactRepository localArtifactRepository;
|
||||
/**
|
||||
* Persists {@link ExternalArtifactProvider} based on given properties.
|
||||
*
|
||||
* @param name
|
||||
* of the provided
|
||||
* @param description
|
||||
* which is optional
|
||||
* @param basePath
|
||||
* of all {@link ExternalArtifact}s of the provider
|
||||
* @param defaultUrlSuffix
|
||||
* that is used if {@link ExternalArtifact#getUrlSuffix()} is
|
||||
* empty.
|
||||
* @return created {@link ExternalArtifactProvider}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
|
||||
ExternalArtifactProvider createExternalArtifactProvider(@NotEmpty String name, String description,
|
||||
@NotNull String basePath, String defaultUrlSuffix);
|
||||
|
||||
@Autowired
|
||||
private ExternalArtifactRepository externalArtifactRepository;
|
||||
/**
|
||||
* Persists artifact binary as provided by given InputStream. assign the
|
||||
* artifact in addition to given {@link SoftwareModule}.
|
||||
*
|
||||
* @param inputStream
|
||||
* to read from for artifact binary
|
||||
* @param moduleId
|
||||
* to assign the new artifact to
|
||||
* @param filename
|
||||
* of the artifact
|
||||
* @param overrideExisting
|
||||
* to <code>true</code> if the artifact binary can be overridden
|
||||
* if it already exists
|
||||
*
|
||||
* @return uploaded {@link LocalArtifact}
|
||||
*
|
||||
* @throw ArtifactUploadFailedException if upload fails
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
default LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
|
||||
final boolean overrideExisting) {
|
||||
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private ExternalArtifactProviderRepository externalArtifactProviderRepository;
|
||||
|
||||
@Autowired
|
||||
private ArtifactRepository artifactRepository;
|
||||
/**
|
||||
* Persists artifact binary as provided by given InputStream. assign the
|
||||
* artifact in addition to given {@link SoftwareModule}.
|
||||
*
|
||||
* @param inputStream
|
||||
* to read from for artifact binary
|
||||
* @param moduleId
|
||||
* to assign the new artifact to
|
||||
* @param filename
|
||||
* of the artifact
|
||||
* @param overrideExisting
|
||||
* to <code>true</code> if the artifact binary can be overridden
|
||||
* if it already exists
|
||||
* @param contentType
|
||||
* the contentType of the file
|
||||
*
|
||||
* @return uploaded {@link LocalArtifact}
|
||||
*
|
||||
* @throw ArtifactUploadFailedException if upload fails
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
default LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
|
||||
final boolean overrideExisting, final String contentType) {
|
||||
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists artifact binary as provided by given InputStream. assign the
|
||||
@@ -105,59 +164,112 @@ public class ArtifactManagement {
|
||||
* @throws InvalidSHA1HashException
|
||||
* if check against provided SHA1 checksum failed
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
|
||||
public LocalArtifact createLocalArtifact(@NotNull final InputStream stream, @NotNull final Long moduleId,
|
||||
@NotEmpty final String filename, final String providedMd5Sum, final String providedSha1Sum,
|
||||
final boolean overrideExisting, final String contentType) {
|
||||
DbArtifact result = null;
|
||||
LocalArtifact createLocalArtifact(@NotNull InputStream stream, @NotNull Long moduleId, @NotEmpty String filename,
|
||||
String providedMd5Sum, String providedSha1Sum, boolean overrideExisting, String contentType);
|
||||
|
||||
final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId);
|
||||
/**
|
||||
* Deletes {@link Artifact} based on given id.
|
||||
*
|
||||
* @param id
|
||||
* of the {@link Artifact} that has to be deleted.
|
||||
* @throws ArtifactDeleteFailedException
|
||||
* if deletion failed (MongoDB is not available)
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
void deleteExternalArtifact(@NotNull Long id);
|
||||
|
||||
final LocalArtifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
|
||||
/**
|
||||
* Deletes a local artifact.
|
||||
*
|
||||
* @param existing
|
||||
* the related local artifact
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
void deleteLocalArtifact(@NotNull LocalArtifact existing);
|
||||
|
||||
try {
|
||||
result = artifactRepository.store(stream, filename, contentType,
|
||||
new DbArtifactHash(providedSha1Sum, providedMd5Sum));
|
||||
} catch (final ArtifactStoreException e) {
|
||||
throw new ArtifactUploadFailedException(e);
|
||||
} catch (final HashNotMatchException e) {
|
||||
if (e.getHashFunction().equals(HashNotMatchException.SHA1)) {
|
||||
throw new InvalidSHA1HashException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new InvalidMD5HashException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Deletes {@link Artifact} based on given id.
|
||||
*
|
||||
* @param id
|
||||
* of the {@link Artifact} that has to be deleted.
|
||||
* @throws ArtifactDeleteFailedException
|
||||
* if deletion failed (MongoDB is not available)
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
void deleteLocalArtifact(@NotNull Long id);
|
||||
|
||||
return storeArtifactMetadata(softwareModule, filename, result, existing);
|
||||
}
|
||||
/**
|
||||
* Searches for {@link Artifact} with given {@link Identifiable}.
|
||||
*
|
||||
* @param id
|
||||
* to search for
|
||||
* @return found {@link Artifact} or <code>null</code> is it could not be
|
||||
* found.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
Artifact findArtifact(@NotNull Long id);
|
||||
|
||||
private static LocalArtifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
|
||||
final SoftwareModule softwareModule) {
|
||||
if (softwareModule.getLocalArtifactByFilename(filename).isPresent()) {
|
||||
if (overrideExisting) {
|
||||
LOG.debug("overriding existing artifact with new filename {}", filename);
|
||||
return softwareModule.getLocalArtifactByFilename(filename).get();
|
||||
} else {
|
||||
throw new EntityAlreadyExistsException("File with that name already exists in the Software Module");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Find by artifact by software module id and filename.
|
||||
*
|
||||
* @param filename
|
||||
* file name
|
||||
* @param softwareModuleId
|
||||
* software module id.
|
||||
* @return LocalArtifact if artifact present
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
List<LocalArtifact> findByFilenameAndSoftwareModule(@NotNull String filename, @NotNull Long softwareModuleId);
|
||||
|
||||
private SoftwareModule getModuleAndThrowExceptionIfThatFails(final Long moduleId) {
|
||||
final SoftwareModule softwareModule = findSoftwareModuleWithDetails(moduleId);
|
||||
/**
|
||||
* Find all local artifact by sha1 and return the first artifact.
|
||||
*
|
||||
* @param sha1
|
||||
* the sha1
|
||||
* @return the first local artifact
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
LocalArtifact findFirstLocalArtifactsBySHA1(@NotNull String sha1);
|
||||
|
||||
if (softwareModule == null) {
|
||||
LOG.debug("no software module with ID {} exists", moduleId);
|
||||
throw new EntityNotFoundException("Software Module: " + moduleId);
|
||||
}
|
||||
return softwareModule;
|
||||
}
|
||||
/**
|
||||
* Searches for {@link Artifact} with given file name.
|
||||
*
|
||||
* @param filename
|
||||
* to search for
|
||||
* @return found List of {@link LocalArtifact}s.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
List<LocalArtifact> findLocalArtifactByFilename(@NotNull String filename);
|
||||
|
||||
/**
|
||||
* Get local artifact for a base software module.
|
||||
*
|
||||
* @param pageReq
|
||||
* Pageable
|
||||
* @param swId
|
||||
* software module id
|
||||
* @return Page<LocalArtifact>
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
Page<LocalArtifact> findLocalArtifactBySoftwareModule(@NotNull Pageable pageReq, @NotNull Long swId);
|
||||
|
||||
/**
|
||||
* Finds {@link SoftwareModule} by given id.
|
||||
*
|
||||
* @param id
|
||||
* to search for
|
||||
* @return the found {@link SoftwareModule}s or <code>null</code> if not
|
||||
* found.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
SoftwareModule findSoftwareModuleById(@NotNull Long id);
|
||||
|
||||
/**
|
||||
* Retrieves software module including details (
|
||||
@@ -170,7 +282,7 @@ public class ArtifactManagement {
|
||||
* @return the found {@link SoftwareModule}s
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
protected SoftwareModule findSoftwareModuleWithDetails(@NotNull final Long id) {
|
||||
default SoftwareModule findSoftwareModuleWithDetails(@NotNull final Long id) {
|
||||
final SoftwareModule result = findSoftwareModuleById(id);
|
||||
if (result != null) {
|
||||
result.getArtifacts().size();
|
||||
@@ -179,211 +291,6 @@ public class ArtifactManagement {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all local artifact by sha1 and return the first artifact.
|
||||
*
|
||||
* @param sha1
|
||||
* the sha1
|
||||
* @return the first local artifact
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
public LocalArtifact findFirstLocalArtifactsBySHA1(final String sha1) {
|
||||
return localArtifactRepository.findFirstByGridFsFileName(sha1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds {@link SoftwareModule} by given id.
|
||||
*
|
||||
* @param id
|
||||
* to search for
|
||||
* @return the found {@link SoftwareModule}s or <code>null</code> if not
|
||||
* found.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
protected SoftwareModule findSoftwareModuleById(@NotNull final Long id) {
|
||||
|
||||
final Specification<SoftwareModule> spec = SoftwareModuleSpecification.byId(id);
|
||||
|
||||
return softwareModuleRepository.findOne(spec);
|
||||
}
|
||||
|
||||
private LocalArtifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,
|
||||
final DbArtifact result, final LocalArtifact existing) {
|
||||
LocalArtifact artifact = existing;
|
||||
if (existing == null) {
|
||||
artifact = new LocalArtifact(result.getHashes().getSha1(), providedFilename, softwareModule);
|
||||
}
|
||||
artifact.setMd5Hash(result.getHashes().getMd5());
|
||||
artifact.setSha1Hash(result.getHashes().getSha1());
|
||||
artifact.setSize(result.getSize());
|
||||
|
||||
LOG.debug("storing new artifact into repository {}", artifact);
|
||||
return localArtifactRepository.save(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists {@link ExternalArtifactProvider} based on given properties.
|
||||
*
|
||||
* @param name
|
||||
* of the provided
|
||||
* @param description
|
||||
* which is optional
|
||||
* @param basePath
|
||||
* of all {@link ExternalArtifact}s of the provider
|
||||
* @param defaultUrlSuffix
|
||||
* that is used if {@link ExternalArtifact#getUrlSuffix()} is
|
||||
* empty.
|
||||
* @return created {@link ExternalArtifactProvider}
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
|
||||
public ExternalArtifactProvider createExternalArtifactProvider(@NotEmpty final String name,
|
||||
final String description, @NotNull final String basePath, final String defaultUrlSuffix) {
|
||||
return externalArtifactProviderRepository
|
||||
.save(new ExternalArtifactProvider(name, description, basePath, defaultUrlSuffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link ExternalArtifact} based on given provider.
|
||||
*
|
||||
* @param externalRepository
|
||||
* the artifact is located in
|
||||
* @param urlSuffix
|
||||
* of the artifact
|
||||
* {@link ExternalArtifactProvider#getDefaultSuffix()} is used if
|
||||
* empty.
|
||||
* @param moduleId
|
||||
* to assign the artifact to
|
||||
*
|
||||
* @return created {@link ExternalArtifact}
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if {@link SoftwareModule} with given ID does not exist
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
|
||||
public ExternalArtifact createExternalArtifact(@NotNull final ExternalArtifactProvider externalRepository,
|
||||
final String urlSuffix, @NotNull final Long moduleId) {
|
||||
|
||||
final SoftwareModule module = getModuleAndThrowExceptionIfThatFails(moduleId);
|
||||
return externalArtifactRepository.save(new ExternalArtifact(externalRepository, urlSuffix, module));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes {@link Artifact} based on given id.
|
||||
*
|
||||
* @param id
|
||||
* of the {@link Artifact} that has to be deleted.
|
||||
* @throws ArtifactDeleteFailedException
|
||||
* if deletion failed (MongoDB is not available)
|
||||
*
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
public void deleteLocalArtifact(@NotNull final Long id) {
|
||||
final LocalArtifact existing = localArtifactRepository.findOne(id);
|
||||
|
||||
if (null == existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteGridFsArtifact(existing);
|
||||
|
||||
existing.getSoftwareModule().removeArtifact(existing);
|
||||
softwareModuleRepository.save(existing.getSoftwareModule());
|
||||
localArtifactRepository.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a grid fs file.
|
||||
*
|
||||
* @param existing
|
||||
* the related local artifact
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
public void deleteGridFsArtifact(@NotNull final LocalArtifact existing) {
|
||||
if (existing == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean artifactIsOnlyUsedByOneSoftwareModule = true;
|
||||
for (final LocalArtifact lArtifact : localArtifactRepository
|
||||
.findByGridFsFileName(existing.getGridFsFileName())) {
|
||||
if (!lArtifact.getSoftwareModule().isDeleted()
|
||||
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
|
||||
artifactIsOnlyUsedByOneSoftwareModule = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (artifactIsOnlyUsedByOneSoftwareModule) {
|
||||
try {
|
||||
LOG.debug("deleting artifact from repository {}", existing.getGridFsFileName());
|
||||
artifactRepository.deleteBySha1(existing.getGridFsFileName());
|
||||
} catch (final ArtifactStoreException e) {
|
||||
throw new ArtifactDeleteFailedException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes {@link Artifact} based on given id.
|
||||
*
|
||||
* @param id
|
||||
* of the {@link Artifact} that has to be deleted.
|
||||
* @throws ArtifactDeleteFailedException
|
||||
* if deletion failed (MongoDB is not available)
|
||||
*
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
|
||||
public void deleteExternalArtifact(@NotNull final Long id) {
|
||||
final ExternalArtifact existing = externalArtifactRepository.findOne(id);
|
||||
|
||||
if (null == existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
existing.getSoftwareModule().removeArtifact(existing);
|
||||
softwareModuleRepository.save(existing.getSoftwareModule());
|
||||
externalArtifactRepository.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for {@link Artifact} with given file name.
|
||||
*
|
||||
* @param filename
|
||||
* to search for
|
||||
* @return found List of {@link LocalArtifact}s.
|
||||
*/
|
||||
@NotNull
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
public List<LocalArtifact> findLocalArtifactByFilename(@NotNull final String filename) {
|
||||
return localArtifactRepository.findByFilename(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for {@link Artifact} with given {@link Identifiable}.
|
||||
*
|
||||
* @param id
|
||||
* to search for
|
||||
* @return found {@link Artifact} or <code>null</code> is it could not be
|
||||
* found.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
public Artifact findArtifact(@NotNull final Long id) {
|
||||
return localArtifactRepository.findOne(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads {@link org.eclipse.hawkbit.artifact.server.json.model.Artifact}
|
||||
* from store for given {@link LocalArtifact}.
|
||||
@@ -398,98 +305,6 @@ public class ArtifactManagement {
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DOWNLOAD_ARTIFACT + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.HAS_CONTROLLER_DOWNLOAD)
|
||||
public DbArtifact loadLocalArtifactBinary(@NotNull final LocalArtifact artifact) {
|
||||
final DbArtifact result = artifactRepository.getArtifactBySha1(artifact.getGridFsFileName());
|
||||
if (result == null) {
|
||||
throw new GridFSDBFileNotFoundException(artifact.getGridFsFileName());
|
||||
}
|
||||
DbArtifact loadLocalArtifactBinary(@NotNull LocalArtifact artifact);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists artifact binary as provided by given InputStream. assign the
|
||||
* artifact in addition to given {@link SoftwareModule}.
|
||||
*
|
||||
* @param inputStream
|
||||
* to read from for artifact binary
|
||||
* @param moduleId
|
||||
* to assign the new artifact to
|
||||
* @param filename
|
||||
* of the artifact
|
||||
* @param overrideExisting
|
||||
* to <code>true</code> if the artifact binary can be overridden
|
||||
* if it already exists
|
||||
* @param contentType
|
||||
* the contentType of the file
|
||||
*
|
||||
* @return uploaded {@link LocalArtifact}
|
||||
*
|
||||
* @throw ArtifactUploadFailedException if upload fails
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
|
||||
final boolean overrideExisting, final String contentType) {
|
||||
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists artifact binary as provided by given InputStream. assign the
|
||||
* artifact in addition to given {@link SoftwareModule}.
|
||||
*
|
||||
* @param inputStream
|
||||
* to read from for artifact binary
|
||||
* @param moduleId
|
||||
* to assign the new artifact to
|
||||
* @param filename
|
||||
* of the artifact
|
||||
* @param overrideExisting
|
||||
* to <code>true</code> if the artifact binary can be overdiden
|
||||
* if it already exists
|
||||
*
|
||||
* @return uploaded {@link LocalArtifact}
|
||||
*
|
||||
* @throw ArtifactUploadFailedException if upload failes
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
|
||||
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
|
||||
final boolean overrideExisting) {
|
||||
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local artifact for a base software module.
|
||||
*
|
||||
* @param pageReq
|
||||
* Pageable
|
||||
* @param swId
|
||||
* software module id
|
||||
* @return Page<LocalArtifact>
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
public Page<LocalArtifact> findLocalArtifactBySoftwareModule(@NotNull final Pageable pageReq,
|
||||
@NotNull final Long swId) {
|
||||
return localArtifactRepository.findBySoftwareModuleId(pageReq, swId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find by artifact by software module id and filename.
|
||||
*
|
||||
* @param filename
|
||||
* file name
|
||||
* @param softwareModuleId
|
||||
* software module id.
|
||||
* @return LocalArtifact if artifact present
|
||||
*/
|
||||
@NotNull
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_CONTROLLER)
|
||||
public List<LocalArtifact> findByFilenameAndSoftwareModule(@NotNull final String filename,
|
||||
@NotNull final Long softwareModuleId) {
|
||||
return localArtifactRepository.findByFilenameAndSoftwareModuleId(filename, softwareModuleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,6 @@ import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
@@ -26,78 +22,102 @@ import org.eclipse.hawkbit.repository.exception.ToManyStatusEntriesException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
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.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.specifications.ActionSpecifications;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Service layer for all operations of the controller API (with access
|
||||
* permissions only for the controller).
|
||||
*
|
||||
*
|
||||
* Service layer for all operations of the DDI API (with access permissions only
|
||||
* for the controller).
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class ControllerManagement {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
|
||||
private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos");
|
||||
public interface ControllerManagement {
|
||||
|
||||
public static final String SERVER_MESSAGE_PREFIX = "Update Server: ";
|
||||
String SERVER_MESSAGE_PREFIX = "Update Server: ";
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
/**
|
||||
* Simple addition of a new {@link ActionStatus} entry to the {@link Action}
|
||||
* . No state changes.
|
||||
*
|
||||
* @param statusMessage
|
||||
* to add to the action
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
void addInformationalActionStatus(@NotNull ActionStatus statusMessage);
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
/**
|
||||
* Adds an {@link ActionStatus} for a cancel {@link Action} including
|
||||
* potential state changes for the target and the {@link Action} itself.
|
||||
*
|
||||
* @param actionStatus
|
||||
* to be added
|
||||
* @return the persisted {@link Action}
|
||||
*
|
||||
* @throws EntityAlreadyExistsException
|
||||
* if a given entity already exists
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Action addCancelActionStatus(@NotNull ActionStatus actionStatus);
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
/**
|
||||
* Adds an {@link ActionStatus} entry for an update {@link Action} including
|
||||
* potential state changes for the target and the {@link Action} itself.
|
||||
*
|
||||
* @param actionStatus
|
||||
* to be added
|
||||
* @return the updated {@link Action}
|
||||
*
|
||||
* @throws EntityAlreadyExistsException
|
||||
* if a given entity already exists
|
||||
* @throws ToManyStatusEntriesException
|
||||
* if more than the allowed number of status entries are
|
||||
* inserted
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Action addUpdateActionStatus(@NotNull ActionStatus actionStatus);
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
/**
|
||||
* Retrieves all {@link Action}s which are active and assigned to a
|
||||
* {@link Target}.
|
||||
*
|
||||
* @param target
|
||||
* the target to retrieve the actions from
|
||||
* @return a list of actions assigned to given target which are active
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
List<Action> findActionByTargetAndActive(@NotNull Target target);
|
||||
|
||||
@Autowired
|
||||
private DeploymentManagement deploymentManagement;
|
||||
/**
|
||||
* Get the {@link Action} entity for given actionId with all lazy
|
||||
* attributes.
|
||||
*
|
||||
* @param actionId
|
||||
* to be id of the action
|
||||
* @return the corresponding {@link Action}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Action findActionWithDetails(@NotNull Long actionId);
|
||||
|
||||
@Autowired
|
||||
private TargetInfoRepository targetInfoRepository;
|
||||
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionStatusRepository actionStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private HawkbitSecurityProperties securityProperties;
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationRepository tenantConfigurationRepository;
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationManagement tenantConfigurationManagement;
|
||||
/**
|
||||
* register new target in the repository (plug-and-play).
|
||||
*
|
||||
* @param controllerId
|
||||
* reference
|
||||
* @param address
|
||||
* the client IP address of the target, might be {@code null}
|
||||
* @return target reference
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Target findOrRegisterTargetIfItDoesNotexist(@NotEmpty String controllerId, URI address);
|
||||
|
||||
/**
|
||||
* Retrieves all {@link SoftwareModule}s which are assigned to the given
|
||||
@@ -110,46 +130,13 @@ public class ControllerManagement {
|
||||
* {@code distributionSet}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public String findPollingTime() {
|
||||
final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final Class<String> propertyType = String.class;
|
||||
tenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
return tenantConfigurationManagement
|
||||
.buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the time of the last time the controller has been connected to
|
||||
* the server.
|
||||
*
|
||||
* @param targetid
|
||||
* of the target to to update
|
||||
* @param address
|
||||
* the client address of the target, might be {@code null}
|
||||
* @return the updated target
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target with given ID could not be found
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Target updateLastTargetQuery(@NotEmpty final String targetid, final URI address) {
|
||||
final Target target = targetRepository.findByControllerId(targetid);
|
||||
if (target == null) {
|
||||
throw new EntityNotFoundException(targetid);
|
||||
}
|
||||
|
||||
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
|
||||
}
|
||||
List<SoftwareModule> findSoftwareModulesByDistributionSet(@NotNull DistributionSet distributionSet);
|
||||
|
||||
/**
|
||||
* Retrieves last {@link UpdateAction} for a download of an artifact of
|
||||
* given module and target.
|
||||
*
|
||||
* @param targetId
|
||||
* @param controllerId
|
||||
* to look for
|
||||
* @param module
|
||||
* that should be assigned to the target
|
||||
@@ -159,17 +146,28 @@ public class ControllerManagement {
|
||||
* if action for given combination could not be found
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Action getActionForDownloadByTargetAndSoftwareModule(@NotNull final String targetId,
|
||||
@NotNull final SoftwareModule module) {
|
||||
final List<Action> action = actionRepository.findActionByTargetAndSoftwareModule(targetId, module);
|
||||
Action getActionForDownloadByTargetAndSoftwareModule(@NotEmpty String controllerId, @NotNull SoftwareModule module);
|
||||
|
||||
if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) {
|
||||
throw new EntityNotFoundException(
|
||||
"No assigment found for module " + module.getId() + " to target " + targetId);
|
||||
}
|
||||
/**
|
||||
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
String getPollingTime();
|
||||
|
||||
return action.get(0);
|
||||
}
|
||||
/**
|
||||
* An direct access to the security token of an
|
||||
* {@link Target#getSecurityToken()} without authorization. This is
|
||||
* necessary to be able to access the security-token without any
|
||||
* security-context information because the security-token is used for
|
||||
* authentication.
|
||||
*
|
||||
* @param controllerId
|
||||
* the ID of the controller to retrieve the security token for
|
||||
* @return the security context of the target, in case no target exists for
|
||||
* the given controllerId {@code null} is returned
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
String getSecurityTokenByControllerId(@NotEmpty String controllerId);
|
||||
|
||||
/**
|
||||
* Checks if a given target has currently or has even been assigned to the
|
||||
@@ -178,7 +176,7 @@ public class ControllerManagement {
|
||||
* assigned or had ever been assigned to the target and so it's visible to a
|
||||
* specific target e.g. for downloading.
|
||||
*
|
||||
* @param targetId
|
||||
* @param controllerId
|
||||
* the ID of the target to check
|
||||
* @param localArtifact
|
||||
* the artifact to verify if the given target had even been
|
||||
@@ -187,14 +185,56 @@ public class ControllerManagement {
|
||||
* relation to the given artifact through the action history,
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
public boolean hasTargetArtifactAssigned(@NotNull final String targetId,
|
||||
@NotNull final LocalArtifact localArtifact) {
|
||||
final Target target = targetRepository.findByControllerId(targetId);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
boolean hasTargetArtifactAssigned(@NotNull String controllerId, @NotNull LocalArtifact localArtifact);
|
||||
|
||||
/**
|
||||
* Registers retrieved status for given {@link Target} and {@link Action} if
|
||||
* it does not exist yet.
|
||||
*
|
||||
* @param action
|
||||
* to the handle status for
|
||||
* @param message
|
||||
* for the status
|
||||
* @return the update action in case the status has been changed to
|
||||
* {@link Status#RETRIEVED}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Action registerRetrieved(@NotNull Action action, String message);
|
||||
|
||||
/**
|
||||
* Updates attributes of the controller.
|
||||
*
|
||||
* @param controllerId
|
||||
* to update
|
||||
* @param attributes
|
||||
* to insert
|
||||
*
|
||||
* @return updated {@link Target}
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target that has to be updated could not be found
|
||||
* @throws ToManyAttributeEntriesException
|
||||
* if maximum
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<String, String> attributes);
|
||||
|
||||
/**
|
||||
* Refreshes the time of the last time the controller has been connected to
|
||||
* the server.
|
||||
*
|
||||
* @param controllerId
|
||||
* of the target to to update
|
||||
* @param address
|
||||
* the client address of the target, might be {@code null}
|
||||
* @return the updated target
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target with given ID could not be found
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Target updateLastTargetQuery(@NotEmpty String controllerId, URI address);
|
||||
|
||||
/**
|
||||
* Refreshes the time of the last time the controller has been connected to
|
||||
@@ -207,83 +247,12 @@ public class ControllerManagement {
|
||||
* @return the updated target
|
||||
*
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public TargetInfo updateLastTargetQuery(@NotNull final TargetInfo target, final URI address) {
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
default TargetInfo updateLastTargetQuery(@NotNull final TargetInfo target, @NotNull final URI address) {
|
||||
return updateTargetStatus(target, null, System.currentTimeMillis(), address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all {@link Action}s which are active and assigned to a
|
||||
* {@link Target}.
|
||||
*
|
||||
* @param target
|
||||
* the target to retrieve the actions from
|
||||
* @return a list of actions assigned to given target which are active
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public List<Action> findActionByTargetAndActive(final Target target) {
|
||||
return actionRepository.findByTargetAndActiveOrderByIdAsc(target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all {@link SoftwareModule}s which are assigned to the given
|
||||
* {@link DistributionSet}.
|
||||
*
|
||||
* @param distributionSet
|
||||
* the distribution set which should be assigned to the returned
|
||||
* {@link SoftwareModule}s
|
||||
* @return a list of {@link SoftwareModule}s assigned to given
|
||||
* {@code distributionSet}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public List<SoftwareModule> findSoftwareModulesByDistributionSet(final DistributionSet distributionSet) {
|
||||
return softwareModuleRepository.findByAssignedTo(distributionSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Action} entity for given actionId with all lazy
|
||||
* attributes.
|
||||
*
|
||||
* @param actionId
|
||||
* to be id of the action
|
||||
* @return the corresponding {@link Action}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Action findActionWithDetails(@NotNull final Long actionId) {
|
||||
return actionRepository.findById(actionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* register new target in the repository (plug-and-play).
|
||||
*
|
||||
* @param targetid
|
||||
* reference
|
||||
* @param address
|
||||
* the client IP address of the target, might be {@code null}
|
||||
* @return target reference
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Target findOrRegisterTargetIfItDoesNotexist(@NotEmpty final String targetid, final URI address) {
|
||||
final Specification<Target> spec = (targetRoot, query, cb) -> cb.equal(targetRoot.get(Target_.controllerId),
|
||||
targetid);
|
||||
|
||||
Target target = targetRepository.findOne(spec);
|
||||
|
||||
if (target == null) {
|
||||
target = new Target(targetid);
|
||||
target.setDescription("Plug and Play target: " + targetid);
|
||||
target.setName(targetid);
|
||||
return targetManagement.createTarget(target, TargetUpdateStatus.REGISTERED, System.currentTimeMillis(),
|
||||
address);
|
||||
}
|
||||
|
||||
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selective the target status of a given {@code target}.
|
||||
*
|
||||
@@ -300,313 +269,8 @@ public class ControllerManagement {
|
||||
* the client address of the target, might be {@code null}
|
||||
* @return the updated TargetInfo
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public TargetInfo updateTargetStatus(@NotNull final TargetInfo targetInfo, final TargetUpdateStatus status,
|
||||
final Long lastTargetQuery, final URI address) {
|
||||
final TargetInfo mtargetInfo = entityManager.merge(targetInfo);
|
||||
if (status != null) {
|
||||
mtargetInfo.setUpdateStatus(status);
|
||||
}
|
||||
if (lastTargetQuery != null) {
|
||||
mtargetInfo.setLastTargetQuery(lastTargetQuery);
|
||||
}
|
||||
if (address != null) {
|
||||
mtargetInfo.setAddress(address.toString());
|
||||
}
|
||||
return targetInfoRepository.save(mtargetInfo);
|
||||
}
|
||||
TargetInfo updateTargetStatus(@NotNull TargetInfo targetInfo, TargetUpdateStatus status, Long lastTargetQuery,
|
||||
URI address);
|
||||
|
||||
/**
|
||||
* Adds an {@link ActionStatus} for a {@link UpdateAction} and cancels the
|
||||
* {@link UpdateAction} if necessary.
|
||||
*
|
||||
* @param actionStatus
|
||||
* to be updated
|
||||
* @param action
|
||||
* the status is for
|
||||
* @return the persisted {@link Action}
|
||||
*
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Action addCancelActionStatus(@NotNull final ActionStatus actionStatus, final Action action) {
|
||||
|
||||
checkForToManyStatusEntries(action);
|
||||
action.setStatus(actionStatus.getStatus());
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
case WARNING:
|
||||
case ERROR:
|
||||
case RUNNING:
|
||||
break;
|
||||
case CANCELED:
|
||||
case FINISHED:
|
||||
// in case of successful cancellation we also report the success at
|
||||
// the canceled action itself.
|
||||
actionStatus.addMessage(
|
||||
ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation completion is finished sucessfully.");
|
||||
deploymentManagement.successCancellation(action);
|
||||
break;
|
||||
case RETRIEVED:
|
||||
actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation request retrieved.");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
actionRepository.save(action);
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an {@link ActionStatus} for a {@link UpdateAction}.
|
||||
*
|
||||
* @param actionStatus
|
||||
* to be updated
|
||||
* @param action
|
||||
* the update is for
|
||||
* @return the persisted {@link Action}
|
||||
*
|
||||
* @throws EntityAlreadyExistsException
|
||||
* if a given entity already exists
|
||||
* @throws ToManyStatusEntriesException
|
||||
* if more than the allowed number of status entries are
|
||||
* inserted
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus, final Action action) {
|
||||
|
||||
if (!action.isActive()) {
|
||||
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
|
||||
actionStatus.getId(), action.getId());
|
||||
return action;
|
||||
}
|
||||
return handleAddUpdateActionStatus(actionStatus, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
|
||||
*
|
||||
* @param actionStatus
|
||||
* @param action
|
||||
* @return
|
||||
*/
|
||||
public Action handleAddUpdateActionStatus(final ActionStatus actionStatus, final Action action) {
|
||||
LOG.debug("addUpdateActionStatus for action {}", action.getId());
|
||||
|
||||
final Action mergedAction = entityManager.merge(action);
|
||||
Target mergedTarget = mergedAction.getTarget();
|
||||
// check for a potential DOS attack
|
||||
checkForToManyStatusEntries(action);
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
case ERROR:
|
||||
mergedTarget = deploymentManagement.updateTargetInfo(mergedTarget, TargetUpdateStatus.ERROR, false);
|
||||
handleErrorOnAction(mergedAction, mergedTarget);
|
||||
break;
|
||||
case FINISHED:
|
||||
handleFinishedAndStoreInTargetStatus(mergedTarget, mergedAction);
|
||||
break;
|
||||
case CANCELED:
|
||||
case WARNING:
|
||||
case RUNNING:
|
||||
deploymentManagement.updateTargetInfo(mergedTarget, TargetUpdateStatus.PENDING, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
LOG.debug("addUpdateActionStatus {} for target {} is finished.", action.getId(), mergedTarget.getId());
|
||||
|
||||
return actionRepository.save(mergedAction);
|
||||
}
|
||||
|
||||
private void handleErrorOnAction(final Action mergedAction, final Target mergedTarget) {
|
||||
mergedAction.setActive(false);
|
||||
mergedAction.setStatus(Status.ERROR);
|
||||
mergedTarget.setAssignedDistributionSet(null);
|
||||
targetManagement.updateTarget(mergedTarget);
|
||||
}
|
||||
|
||||
private void checkForToManyStatusEntries(final Action action) {
|
||||
if (securityProperties.getDos().getMaxStatusEntriesPerAction() > 0) {
|
||||
|
||||
final Long statusCount = actionStatusRepository.countByAction(action);
|
||||
|
||||
if (statusCount >= securityProperties.getDos().getMaxStatusEntriesPerAction()) {
|
||||
LOG_DOS.error(
|
||||
"Potential denial of service (DOS) attack identfied. More status entries in the system than permitted ({})!",
|
||||
securityProperties.getDos().getMaxStatusEntriesPerAction());
|
||||
throw new ToManyStatusEntriesException(
|
||||
String.valueOf(securityProperties.getDos().getMaxStatusEntriesPerAction()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFinishedAndStoreInTargetStatus(final Target target, final Action action) {
|
||||
action.setActive(false);
|
||||
action.setStatus(Status.FINISHED);
|
||||
final TargetInfo targetInfo = target.getTargetInfo();
|
||||
final DistributionSet ds = entityManager.merge(action.getDistributionSet());
|
||||
targetInfo.setInstalledDistributionSet(ds);
|
||||
if (target.getAssignedDistributionSet() != null && targetInfo.getInstalledDistributionSet() != null && target
|
||||
.getAssignedDistributionSet().getId().equals(targetInfo.getInstalledDistributionSet().getId())) {
|
||||
targetInfo.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
targetInfo.setInstallationDate(System.currentTimeMillis());
|
||||
} else {
|
||||
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
targetInfo.setInstallationDate(System.currentTimeMillis());
|
||||
}
|
||||
targetInfoRepository.save(targetInfo);
|
||||
entityManager.detach(ds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates attributes of the controller.
|
||||
*
|
||||
* @param targetid
|
||||
* to update
|
||||
* @param data
|
||||
* to insert
|
||||
*
|
||||
* @return updated {@link Target}
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if target that has to be updated could not be found
|
||||
* @throws ToManyAttributeEntriesException
|
||||
* if maximum
|
||||
*/
|
||||
@Modifying
|
||||
@NotNull
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Target updateControllerAttributes(@NotEmpty final String targetid, @NotNull final Map<String, String> data) {
|
||||
final Target target = targetRepository.findByControllerId(targetid);
|
||||
|
||||
if (target == null) {
|
||||
throw new EntityNotFoundException(targetid);
|
||||
}
|
||||
|
||||
target.getTargetInfo().getControllerAttributes().putAll(data);
|
||||
|
||||
if (target.getTargetInfo().getControllerAttributes().size() > securityProperties.getDos()
|
||||
.getMaxAttributeEntriesPerTarget()) {
|
||||
LOG_DOS.info("Target tries to insert more than the allowed number of entries ({}). DOS attack anticipated!",
|
||||
securityProperties.getDos().getMaxAttributeEntriesPerTarget());
|
||||
throw new ToManyAttributeEntriesException(
|
||||
String.valueOf(securityProperties.getDos().getMaxAttributeEntriesPerTarget()));
|
||||
}
|
||||
|
||||
target.getTargetInfo().setLastTargetQuery(System.currentTimeMillis());
|
||||
target.getTargetInfo().setRequestControllerAttributes(false);
|
||||
return targetRepository.save(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers retrieved status for given {@link Target} and {@link Action} if
|
||||
* it does not exist yet.
|
||||
*
|
||||
* @param action
|
||||
* to the handle status for
|
||||
* @param target
|
||||
* to the handle status for
|
||||
* @param message
|
||||
* for the status
|
||||
* @return the update action in case the status has been changed to
|
||||
* {@link Status#RETRIEVED}
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
public Action registerRetrieved(final Action action, final String message) {
|
||||
return handleRegisterRetrieved(action, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers retrieved status for given {@link Target} and {@link Action} if
|
||||
* it does not exist yet.
|
||||
*
|
||||
* @param action
|
||||
* to the handle status for
|
||||
* @param message
|
||||
* for the status
|
||||
* @return the updated action in case the status has been changed to
|
||||
* {@link Status#RETRIEVED}
|
||||
*/
|
||||
public Action handleRegisterRetrieved(final Action action, final String message) {
|
||||
// do a manual query with CriteriaBuilder to avoid unnecessary field
|
||||
// queries and an extra
|
||||
// count query made by spring-data when using pageable requests, we
|
||||
// don't need an extra count
|
||||
// query, we just want to check if the last action status is a retrieved
|
||||
// or not.
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryActionStatus = cb.createQuery(Object[].class);
|
||||
final Root<ActionStatus> actionStatusRoot = queryActionStatus.from(ActionStatus.class);
|
||||
final CriteriaQuery<Object[]> query = queryActionStatus
|
||||
.multiselect(actionStatusRoot.get(ActionStatus_.id), actionStatusRoot.get(ActionStatus_.status))
|
||||
.where(cb.equal(actionStatusRoot.get(ActionStatus_.action), action))
|
||||
.orderBy(cb.desc(actionStatusRoot.get(ActionStatus_.id)));
|
||||
final List<Object[]> resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1)
|
||||
.getResultList();
|
||||
|
||||
// if the latest status is not in retrieve state then we add a retrieved
|
||||
// state again, we want
|
||||
// to document a deployment retrieved status and a cancel retrieved
|
||||
// status, but multiple
|
||||
// retrieves after the other we don't want to store to protect to
|
||||
// overflood action status in
|
||||
// case controller retrieves a action multiple times.
|
||||
if (resultList.isEmpty() || resultList.get(0)[1] != Status.RETRIEVED) {
|
||||
// document that the status has been retrieved
|
||||
actionStatusRepository
|
||||
.save(new ActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));
|
||||
|
||||
// don't change the action status itself in case the action is in
|
||||
// canceling state otherwise
|
||||
// we modify the action status and the controller won't get the
|
||||
// cancel job anymore.
|
||||
if (!action.isCancelingOrCanceled()) {
|
||||
final Action actionMerge = entityManager.merge(action);
|
||||
actionMerge.setStatus(Status.RETRIEVED);
|
||||
return actionRepository.save(actionMerge);
|
||||
}
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param statusMessage
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void addActionStatusMessage(final ActionStatus statusMessage) {
|
||||
actionStatusRepository.save(statusMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* An direct access to the security token of an
|
||||
* {@link Target#getSecurityToken()} without authorization. This is
|
||||
* necessary to be able to access the security-token without any
|
||||
* security-context information because the security-token is used for
|
||||
* authentication.
|
||||
*
|
||||
* @param controllerId
|
||||
* the ID of the controller to retrieve the security token for
|
||||
* @return the security context of the target, in case no target exists for
|
||||
* the given controllerId {@code null} is returned
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public String getSecurityTokenByControllerId(final String controllerId) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
return target != null ? target.getSecurityToken() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.AssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,77 +12,24 @@ import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.report.model.DataReportSeries;
|
||||
import org.eclipse.hawkbit.report.model.DataReportSeriesItem;
|
||||
import org.eclipse.hawkbit.report.model.InnerOuterDataReportSeries;
|
||||
import org.eclipse.hawkbit.report.model.ListReportSeries;
|
||||
import org.eclipse.hawkbit.report.model.SeriesTime;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo_;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
/**
|
||||
* Service layer for generating hawkBit reports.
|
||||
* Service layer for generating hawkBit statistics and reports.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class ReportManagement {
|
||||
|
||||
@Value("${spring.jpa.database}")
|
||||
private String databaseType;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
private static final String H2_TARGET_CREATED_SQL_TEMPLATE = "SELECT TO_CHAR( DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(target0_.controller_id) AS col_1_0_ from sp_target target0_ WHERE TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'),'%s') BETWEEN TO_CHAR('%s', '%s') and TO_CHAR('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s')";
|
||||
|
||||
private static final String H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(action_.id) AS col_1_0_ FROM sp_action action_ WHERE TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') BETWEEN TO_CHAR('%s', '%s') AND TO_CHAR('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s')";
|
||||
|
||||
private static final String MYSQL_TARGET_CREATED_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s') AS col_0_0_, COUNT(target0_.controller_id) AS col_1_0_ FROM sp_target target0_ WHERE DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s')";
|
||||
|
||||
private static final String MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s') AS col_0_0_, COUNT(action_.id) as col_1_0_ FROM sp_action action_ WHERE DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s')";
|
||||
|
||||
private static final String MYSQL_DB_TYPE = "MYSQL";
|
||||
|
||||
private static final String H2_DB_TYPE = "H2";
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private TenantAware tenantAware;
|
||||
public interface ReportManagement {
|
||||
|
||||
/**
|
||||
* Generates a report of all targets of their current update status count.
|
||||
@@ -92,29 +39,8 @@ public class ReportManagement {
|
||||
* @return a data report series which contains the target count for each
|
||||
* target update status
|
||||
*/
|
||||
@Cacheable("targetStatus")
|
||||
public DataReportSeries<TargetUpdateStatus> targetStatus() {
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
|
||||
final Root<Target> targetRoot = query.from(Target.class);
|
||||
final Join<Target, TargetInfo> targetInfo = targetRoot.join(Target_.targetInfo);
|
||||
final Expression<Long> countColumn = cb.count(targetInfo.get(TargetInfo_.targetId));
|
||||
final CriteriaQuery<Object[]> multiselect = query
|
||||
.multiselect(targetInfo.get(TargetInfo_.updateStatus), countColumn)
|
||||
.groupBy(targetInfo.get(TargetInfo_.updateStatus))
|
||||
.orderBy(cb.desc(targetInfo.get(TargetInfo_.updateStatus)));
|
||||
|
||||
// | col1 | col2 |
|
||||
// | U_STATUS | COUNT |
|
||||
final List<Object[]> resultList = entityManager.createQuery(multiselect).getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<TargetUpdateStatus>> reportSeriesItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<TargetUpdateStatus>((TargetUpdateStatus) r[0], (Long) r[1]))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("Target Status Overview", reportSeriesItems);
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
DataReportSeries<TargetUpdateStatus> targetStatus();
|
||||
|
||||
/**
|
||||
* Generates a report of the top x distribution set assigned usage as a list
|
||||
@@ -132,29 +58,8 @@ public class ReportManagement {
|
||||
* set entries are summarized as "misc"
|
||||
* @return a list of inner and outer series of distribution set usage
|
||||
*/
|
||||
@Cacheable("distributionUsageAssigned")
|
||||
public List<InnerOuterDataReportSeries<String>> distributionUsageAssigned(final int topXEntries) {
|
||||
|
||||
// top X entries distribution usage
|
||||
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
|
||||
final Root<DistributionSet> rootTopX = queryTopX.from(DistributionSet.class);
|
||||
final ListJoin<DistributionSet, Target> joinTopX = rootTopX.join(DistributionSet_.assignedToTargets,
|
||||
JoinType.LEFT);
|
||||
final Expression<Long> countColumn = cbTopX.count(joinTopX);
|
||||
// top x usage query
|
||||
final CriteriaQuery<Object[]> groupBy = queryTopX
|
||||
.multiselect(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version), countColumn)
|
||||
.where(cbTopX.equal(rootTopX.get(DistributionSet_.deleted), false))
|
||||
.groupBy(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version))
|
||||
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(DistributionSet_.name)));
|
||||
// | col1 | col2 | col3 |
|
||||
// | NAME | VER | COUNT |
|
||||
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
|
||||
// end of top X entries distribution usage
|
||||
|
||||
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
List<InnerOuterDataReportSeries<String>> distributionUsageAssigned(int topXEntries);
|
||||
|
||||
/**
|
||||
* Generates a report of the top x distribution set installed usage as a
|
||||
@@ -173,28 +78,8 @@ public class ReportManagement {
|
||||
* set entries are summarized as "misc"
|
||||
* @return a list of inner and outer series of distribution set usage
|
||||
*/
|
||||
@Cacheable("distributionUsageInstalled")
|
||||
public List<InnerOuterDataReportSeries<String>> distributionUsageInstalled(final int topXEntries) {
|
||||
// top X entries distribution usage
|
||||
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
|
||||
final Root<DistributionSet> rootTopX = queryTopX.from(DistributionSet.class);
|
||||
final ListJoin<DistributionSet, TargetInfo> joinTopX = rootTopX.join(DistributionSet_.installedAtTargets,
|
||||
JoinType.LEFT);
|
||||
final Expression<Long> countColumn = cbTopX.count(joinTopX);
|
||||
// top x usage query
|
||||
final CriteriaQuery<Object[]> groupBy = queryTopX
|
||||
.multiselect(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version), countColumn)
|
||||
.where(cbTopX.equal(rootTopX.get(DistributionSet_.deleted), false))
|
||||
.groupBy(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version))
|
||||
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(DistributionSet_.name)));
|
||||
// | col1 | col2 | col3 |
|
||||
// | NAME | VER | COUNT |
|
||||
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
|
||||
// end of top X entries distribution usage
|
||||
|
||||
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
List<InnerOuterDataReportSeries<String>> distributionUsageInstalled(int topXEntries);
|
||||
|
||||
/**
|
||||
* Generates report for target created over period.
|
||||
@@ -208,55 +93,9 @@ public class ReportManagement {
|
||||
* @return <T> DataReportSeries<T> ListReportSeries list of target created
|
||||
* count
|
||||
*/
|
||||
@Cacheable("targetsCreatedOverPeriod")
|
||||
public <T extends Serializable> DataReportSeries<T> targetsCreatedOverPeriod(final DateType<T> dateType,
|
||||
final LocalDateTime from, final LocalDateTime to) {
|
||||
final Query createNativeQuery = entityManager
|
||||
.createNativeQuery(getTargetsCreatedQueryTemplate(dateType, from, to));
|
||||
final List<Object[]> resultList = createNativeQuery.getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("CreatedTargets", reportItems);
|
||||
}
|
||||
|
||||
private String getTargetsCreatedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return String.format(H2_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
|
||||
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
case MYSQL_DB_TYPE:
|
||||
return String.format(MYSQL_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
|
||||
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getFeedbackReceivedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return String.format(H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
|
||||
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
case MYSQL_DB_TYPE:
|
||||
return String.format(MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
|
||||
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
<T extends Serializable> DataReportSeries<T> targetsCreatedOverPeriod(@NotNull DateType<T> dateType,
|
||||
@NotNull LocalDateTime from, @NotNull LocalDateTime to);
|
||||
|
||||
/**
|
||||
* Generates report for feedback over period.
|
||||
@@ -270,19 +109,9 @@ public class ReportManagement {
|
||||
* @return <T> DataReportSeries<T> ListReportSeries list of action status
|
||||
* count
|
||||
*/
|
||||
@Cacheable("feedbackReceivedOverTime")
|
||||
public <T extends Serializable> DataReportSeries<T> feedbackReceivedOverTime(final DateType<T> dateType,
|
||||
final LocalDateTime from, final LocalDateTime to) {
|
||||
final Query createNativeQuery = entityManager
|
||||
.createNativeQuery(getFeedbackReceivedQueryTemplate(dateType, from, to));
|
||||
final List<Object[]> resultList = createNativeQuery.getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("FeedbackRecieved", reportItems);
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
<T extends Serializable> DataReportSeries<T> feedbackReceivedOverTime(@NotNull DateType<T> dateType,
|
||||
@NotNull LocalDateTime from, @NotNull LocalDateTime to);
|
||||
|
||||
/**
|
||||
* Generates a report as a {@link ListReportSeries} targets polled based on
|
||||
@@ -297,212 +126,8 @@ public class ReportManagement {
|
||||
* than a year, never.
|
||||
*
|
||||
*/
|
||||
@Cacheable("targetsLastPoll")
|
||||
public DataReportSeries<SeriesTime> targetsLastPoll() {
|
||||
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
final LocalDateTime beforeHour = now.minusHours(1);
|
||||
final LocalDateTime beforeDay = now.minusDays(1);
|
||||
final LocalDateTime beforeWeek = now.minusWeeks(1);
|
||||
final LocalDateTime beforeMonth = now.minusMonths(1);
|
||||
final LocalDateTime beforeYear = now.minusYears(1);
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final List<DataReportSeriesItem<SeriesTime>> resultList = new ArrayList<>();
|
||||
|
||||
// hours
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.HOUR,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, beforeHour, now)).getSingleResult()));
|
||||
// days
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.DAY, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeDay, beforeHour)).getSingleResult()));
|
||||
// weeks
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.WEEK, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeWeek, beforeDay)).getSingleResult()));
|
||||
// months
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MONTH, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeMonth, beforeWeek)).getSingleResult()));
|
||||
// years
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.YEAR, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeYear, beforeMonth)).getSingleResult()));
|
||||
// years
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MORE_THAN_YEAR,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, beforeYear)).getSingleResult()));
|
||||
// never
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.NEVER,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, null)).getSingleResult()));
|
||||
|
||||
return new DataReportSeries<>("TargetLastPoll", resultList);
|
||||
}
|
||||
|
||||
private CriteriaQuery<Long> createCountSelectTargetsLastPoll(final CriteriaBuilder cb, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
|
||||
Long start = null;
|
||||
Long end = null;
|
||||
if (from != null) {
|
||||
start = from.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
if (to != null) {
|
||||
end = to.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
// count select statement
|
||||
final CriteriaQuery<Long> countSelect = cb.createQuery(Long.class);
|
||||
final Root<Target> countSelectRoot = countSelect.from(Target.class);
|
||||
final Join<Target, TargetInfo> targetInfoJoin = countSelectRoot.join(Target_.targetInfo);
|
||||
countSelect.select(cb.count(countSelectRoot));
|
||||
if (start != null && end != null) {
|
||||
countSelect.where(cb.between(targetInfoJoin.get(TargetInfo_.lastTargetQuery), start, end));
|
||||
} else if (from == null && to != null) {
|
||||
countSelect.where(cb.lessThanOrEqualTo(targetInfoJoin.get(TargetInfo_.lastTargetQuery), end));
|
||||
} else {
|
||||
countSelect.where(cb.isNull(targetInfoJoin.get(TargetInfo_.lastTargetQuery)));
|
||||
}
|
||||
return countSelect;
|
||||
}
|
||||
|
||||
private List<InnerOuterDataReportSeries<String>> mapDistirbutionUsageResultToDataReport(final int topXEntries,
|
||||
final List<Object[]> resultListTop) {
|
||||
final List<InnerOuterDataReportSeries<String>> innerOuterReport = new ArrayList<>();
|
||||
final Map<DSName, InnerOuter> map = new LinkedHashMap<>();
|
||||
|
||||
int topXCounter = 0;
|
||||
|
||||
for (final Object[] objects : resultListTop) {
|
||||
|
||||
final boolean containsInnerOuter = map.containsKey(new DSName((String) objects[0]));
|
||||
final String name = containsInnerOuter || topXCounter < topXEntries ? (String) objects[0] : null;
|
||||
final DSName dsName = new DSName(name);
|
||||
final String version = containsInnerOuter || topXCounter < topXEntries ? (String) objects[1] : null;
|
||||
final Long count = (Long) objects[2];
|
||||
|
||||
InnerOuter innerouter = map.get(dsName);
|
||||
if (innerouter == null) {
|
||||
topXCounter++;
|
||||
innerouter = new InnerOuter(dsName);
|
||||
map.put(new DSName(name), innerouter);
|
||||
}
|
||||
innerouter.addOuter(new DSName(version), count);
|
||||
}
|
||||
|
||||
for (final InnerOuter inner : map.values()) {
|
||||
final List<DataReportSeriesItem<String>> outerReportItems = new ArrayList<>();
|
||||
|
||||
if (inner.name.getName() != null) {
|
||||
for (final InnerOuter outer : inner.outer) {
|
||||
outerReportItems.add(outer.toItem());
|
||||
}
|
||||
} else {
|
||||
outerReportItems.add(new DataReportSeriesItem<String>("misc", inner.count));
|
||||
}
|
||||
|
||||
innerOuterReport.add(new InnerOuterDataReportSeries<String>(
|
||||
new DataReportSeries<>("DS-Name", Collections.singletonList(inner.toItem())),
|
||||
new DataReportSeries<>("DS-Version", outerReportItems)));
|
||||
}
|
||||
|
||||
return innerOuterReport;
|
||||
}
|
||||
|
||||
private static final class InnerOuter {
|
||||
final DSName name;
|
||||
long count;
|
||||
final List<InnerOuter> outer;
|
||||
|
||||
private InnerOuter(final DSName idName) {
|
||||
name = idName;
|
||||
outer = new ArrayList<>();
|
||||
}
|
||||
|
||||
private InnerOuter(final DSName idName, final long count) {
|
||||
name = idName;
|
||||
this.count = count;
|
||||
outer = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addOuter(final DSName idName, final long count) {
|
||||
outer.add(new InnerOuter(idName, count));
|
||||
this.count += count;
|
||||
}
|
||||
|
||||
private DataReportSeriesItem<String> toItem() {
|
||||
return new DataReportSeriesItem<>(name.getName() != null ? name.getName() : "misc", count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object contains the name and the id of an entity.
|
||||
*
|
||||
*/
|
||||
private static final class DSName {
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* the ID of an entity
|
||||
* @param name
|
||||
* the name of an entity
|
||||
*/
|
||||
private DSName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
private String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (name == null ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) { // NOSONAR - as this is
|
||||
// generated
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DSName other = (DSName) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DSName [name=" + name + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private String dateTimeFormatToSqlFormat(final DateType<?> datatype) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return datatype.h2Format();
|
||||
case MYSQL_DB_TYPE:
|
||||
return datatype.mySqlFormat();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
DataReportSeries<SeriesTime> targetsLastPoll();
|
||||
|
||||
/**
|
||||
* Return DateTypes.
|
||||
@@ -580,4 +205,5 @@ public class ReportManagement {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,65 +8,20 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action_;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup_;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* RolloutGroupManagement to control rollout groups. This service secures all
|
||||
* the functionality based on the {@link PreAuthorize} annotation on methods.
|
||||
*/
|
||||
@Validated
|
||||
@Service
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
public class RolloutGroupManagement {
|
||||
|
||||
@Autowired
|
||||
private RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
public interface RolloutGroupManagement {
|
||||
|
||||
/**
|
||||
* Retrieves a single {@link RolloutGroup} by its ID.
|
||||
@@ -77,9 +32,7 @@ public class RolloutGroupManagement {
|
||||
* does not exists
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) {
|
||||
return rolloutGroupRepository.findOne(rolloutGroupId);
|
||||
}
|
||||
RolloutGroup findRolloutGroupById(@NotNull Long rolloutGroupId);
|
||||
|
||||
/**
|
||||
* Retrieves a page of {@link RolloutGroup}s filtered by a given
|
||||
@@ -92,9 +45,7 @@ public class RolloutGroupManagement {
|
||||
* @return a page of found {@link RolloutGroup}s
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<RolloutGroup> findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable page) {
|
||||
return rolloutGroupRepository.findByRolloutId(rolloutId, page);
|
||||
}
|
||||
Page<RolloutGroup> findRolloutGroupsByRolloutId(@NotNull Long rolloutId, @NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Retrieves a page of {@link RolloutGroup}s filtered by a given
|
||||
@@ -110,12 +61,8 @@ public class RolloutGroupManagement {
|
||||
* @return a page of found {@link RolloutGroup}s
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<RolloutGroup> findRolloutGroupsByPredicate(final Rollout rollout,
|
||||
final Specification<RolloutGroup> specification, final Pageable page) {
|
||||
return rolloutGroupRepository.findAll((root, query, criteriaBuilder) -> criteriaBuilder.and(
|
||||
criteriaBuilder.equal(root.get(RolloutGroup_.rollout), rollout),
|
||||
specification.toPredicate(root, query, criteriaBuilder)), page);
|
||||
}
|
||||
Page<RolloutGroup> findRolloutGroupsAll(@NotNull Rollout rollout,
|
||||
@NotNull Specification<RolloutGroup> specification, @NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Retrieves a page of {@link RolloutGroup}s filtered by a given
|
||||
@@ -128,21 +75,7 @@ public class RolloutGroupManagement {
|
||||
* @return a page of found {@link RolloutGroup}s
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable page) {
|
||||
final Page<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, page);
|
||||
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
|
||||
.collect(Collectors.toList());
|
||||
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
|
||||
rolloutGroupIds);
|
||||
|
||||
for (final RolloutGroup rolloutGroup : rolloutGroups) {
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
|
||||
allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets());
|
||||
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
}
|
||||
|
||||
return rolloutGroups;
|
||||
}
|
||||
Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(@NotNull Long rolloutId, @NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Get count of targets in different status in rollout group.
|
||||
@@ -152,25 +85,9 @@ public class RolloutGroupManagement {
|
||||
* @return rolloutGroup with details of targets count for different statuses
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) {
|
||||
final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId);
|
||||
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
|
||||
.getStatusCountByRolloutGroupId(rolloutGroupId);
|
||||
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
|
||||
rolloutGroup.getTotalTargets());
|
||||
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
return rolloutGroup;
|
||||
|
||||
}
|
||||
|
||||
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(
|
||||
final List<Long> rolloutGroupIds) {
|
||||
final List<TotalTargetCountActionStatus> resultList = actionRepository
|
||||
.getStatusCountByRolloutGroupId(rolloutGroupIds);
|
||||
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
|
||||
}
|
||||
RolloutGroup findRolloutGroupWithDetailedStatus(@NotNull Long rolloutGroupId);
|
||||
|
||||
// TODO discuss: target read perm missing?
|
||||
/**
|
||||
* Get targets of specified rollout group.
|
||||
*
|
||||
@@ -184,15 +101,10 @@ public class RolloutGroupManagement {
|
||||
* @return Page<Target> list of targets of a rollout group
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup,
|
||||
final Specification<Target> specification, final Pageable page) {
|
||||
return targetRepository.findAll((root, query, criteriaBuilder) -> {
|
||||
final ListJoin<Target, RolloutTargetGroup> rolloutTargetJoin = root.join(Target_.rolloutTargetGroup);
|
||||
return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder),
|
||||
criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
}, page);
|
||||
}
|
||||
Page<Target> findRolloutGroupTargets(@NotNull RolloutGroup rolloutGroup,
|
||||
@NotNull Specification<Target> specification, @NotNull Pageable page);
|
||||
|
||||
// TODO discuss: target read perm missing?
|
||||
/**
|
||||
* Get targets of specified rollout group.
|
||||
*
|
||||
@@ -204,20 +116,9 @@ public class RolloutGroupManagement {
|
||||
* @return Page<Target> list of targets of a rollout group
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<Target> findRolloutGroupTargets(@NotNull final RolloutGroup rolloutGroup, final Pageable page) {
|
||||
if (isRolloutStatusReady(rolloutGroup)) {
|
||||
// in case of status ready the action has not been created yet and
|
||||
// the relation information between target and rollout-group is
|
||||
// stored in the #TargetRolloutGroup.
|
||||
return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page);
|
||||
}
|
||||
return targetRepository.findByActionsRolloutGroup(rolloutGroup, page);
|
||||
}
|
||||
|
||||
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
|
||||
return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus());
|
||||
}
|
||||
Page<Target> findRolloutGroupTargets(@NotNull RolloutGroup rolloutGroup, @NotNull Pageable page);
|
||||
|
||||
// TODO discuss: target read perm missing?
|
||||
/**
|
||||
*
|
||||
* Find all targets with action status by rollout group id. The action
|
||||
@@ -233,31 +134,7 @@ public class RolloutGroupManagement {
|
||||
* @return {@link TargetWithActionStatus} target with action status
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
|
||||
@NotNull final RolloutGroup rolloutGroup) {
|
||||
Page<TargetWithActionStatus> findAllTargetsWithActionStatus(@NotNull PageRequest pageRequest,
|
||||
@NotNull RolloutGroup rolloutGroup);
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
|
||||
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||
|
||||
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
|
||||
final Join<RolloutTargetGroup, Target> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
|
||||
final ListJoin<RolloutTargetGroup, Action> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
|
||||
JoinType.LEFT);
|
||||
|
||||
final Root<RolloutTargetGroup> countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class);
|
||||
countQueryFrom.join(RolloutTargetGroup_.target);
|
||||
countQueryFrom.join(RolloutTargetGroup_.actions, JoinType.LEFT);
|
||||
countQuery.select(cb.count(countQueryFrom))
|
||||
.where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
final Long totalCount = entityManager.createQuery(countQuery).getSingleResult();
|
||||
|
||||
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(Action_.status))
|
||||
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
|
||||
.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
|
||||
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
|
||||
.collect(Collectors.toList());
|
||||
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,131 +8,29 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.cache.CacheWriteNotify;
|
||||
import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||
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.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.eclipse.hawkbit.rollout.condition.RolloutGroupActionEvaluator;
|
||||
import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
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.domain.Sort.Direction;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* RolloutManagement to control rollouts e.g. like creating, starting, resuming
|
||||
* and pausing rollouts. This service secures all the functionality based on the
|
||||
* {@link PreAuthorize} annotation on methods.
|
||||
*/
|
||||
@Validated
|
||||
@Service
|
||||
@EnableScheduling
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
public class RolloutManagement {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private RolloutRepository rolloutRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private DeploymentManagement deploymentManagement;
|
||||
|
||||
@Autowired
|
||||
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private NoCountPagingRepository criteriaNoCountDao;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager txManager;
|
||||
|
||||
@Autowired
|
||||
private CacheWriteNotify cacheWriteNotify;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("asyncExecutor")
|
||||
private Executor executor;
|
||||
|
||||
/*
|
||||
* set which stores the rollouts which are asynchronously creating. This is
|
||||
* necessary to verify rollouts which maybe stuck during creationg e.g.
|
||||
* because of database interruption, failures or even application crash.
|
||||
* !This is not cluster aware!
|
||||
*/
|
||||
private static final Set<String> creatingRollouts = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/*
|
||||
* set which stores the rollouts which are asynchronously starting. This is
|
||||
* necessary to verify rollouts which maybe stuck during starting e.g.
|
||||
* because of database interruption, failures or even application crash.
|
||||
* !This is not cluster aware!
|
||||
*/
|
||||
private static final Set<String> startingRollouts = ConcurrentHashMap.newKeySet();
|
||||
public interface RolloutManagement {
|
||||
|
||||
/**
|
||||
* Retrieves all rollouts.
|
||||
@@ -142,9 +40,7 @@ public class RolloutManagement {
|
||||
* @return a page of found rollouts
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<Rollout> findAll(final Pageable page) {
|
||||
return rolloutRepository.findAll(page);
|
||||
}
|
||||
Page<Rollout> findAll(@NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Retrieves all rollouts found by the given specification.
|
||||
@@ -156,12 +52,8 @@ public class RolloutManagement {
|
||||
* @return a page of found rollouts
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<Rollout> findAllWithDetailedStatusByPredicate(final Specification<Rollout> specification,
|
||||
final Pageable page) {
|
||||
final Page<Rollout> findAll = rolloutRepository.findAll(specification, page);
|
||||
setRolloutStatusDetails(findAll);
|
||||
return findAll;
|
||||
}
|
||||
Page<Rollout> findAllWithDetailedStatusByPredicate(@NotNull Specification<Rollout> specification,
|
||||
@NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Retrieves a specific rollout by its ID.
|
||||
@@ -172,9 +64,7 @@ public class RolloutManagement {
|
||||
* not exists
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Rollout findRolloutById(final Long rolloutId) {
|
||||
return rolloutRepository.findOne(rolloutId);
|
||||
}
|
||||
Rollout findRolloutById(@NotNull Long rolloutId);
|
||||
|
||||
/**
|
||||
* Persists a new rollout entity. The filter within the
|
||||
@@ -201,14 +91,8 @@ public class RolloutManagement {
|
||||
* @throws IllegalArgumentException
|
||||
* in case the given groupSize is zero or lower.
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
|
||||
public Rollout createRollout(final Rollout rollout, final int amountGroup,
|
||||
final RolloutGroupConditions conditions) {
|
||||
final Rollout savedRollout = createRollout(rollout, amountGroup);
|
||||
return createRolloutGroups(amountGroup, conditions, savedRollout);
|
||||
}
|
||||
Rollout createRollout(@NotNull Rollout rollout, int amountGroup, @NotNull RolloutGroupConditions conditions);
|
||||
|
||||
/**
|
||||
* Persists a new rollout entity. The filter within the
|
||||
@@ -244,116 +128,8 @@ public class RolloutManagement {
|
||||
* @return the created rollout entity in state
|
||||
* {@link RolloutStatus#CREATING}
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
|
||||
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
|
||||
final RolloutGroupConditions conditions) {
|
||||
final Rollout savedRollout = createRollout(rollout, amountGroup);
|
||||
creatingRollouts.add(savedRollout.getName());
|
||||
// need to flush the entity manager here to get the ID of the rollout,
|
||||
// because entity manager is set to FlushMode#Auto, entitymanager will
|
||||
// flush the Target entity, due the indirect relationship to the Rollout
|
||||
// entity without set an ID JPA is throwing a Invalid
|
||||
// 'org.springframework.dao.InvalidDataAccessApiUsageException: During
|
||||
// synchronization aect was found through a relationship that was not
|
||||
// marked cascade PERSIST'
|
||||
entityManager.flush();
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
createRolloutGroupsInNewTransaction(amountGroup, conditions, savedRollout);
|
||||
} finally {
|
||||
creatingRollouts.remove(savedRollout.getName());
|
||||
}
|
||||
});
|
||||
return savedRollout;
|
||||
}
|
||||
|
||||
private Rollout createRollout(final Rollout rollout, final int amountGroup) {
|
||||
verifyRolloutGroupParameter(amountGroup);
|
||||
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
|
||||
rollout.setTotalTargets(totalTargets.longValue());
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
private static void verifyRolloutGroupParameter(final int amountGroup) {
|
||||
if (amountGroup <= 0) {
|
||||
throw new IllegalArgumentException("the amountGroup must be greater than zero");
|
||||
} else if (amountGroup > 500) {
|
||||
throw new IllegalArgumentException("the amountGroup must not be greater than 500");
|
||||
}
|
||||
}
|
||||
|
||||
private Rollout createRolloutGroupsInNewTransaction(final int amountOfGroups,
|
||||
final RolloutGroupConditions conditions, final Rollout savedRollout) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("creatingRollout");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def)
|
||||
.execute(status -> createRolloutGroups(amountOfGroups, conditions, savedRollout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for creating rollout groups and calculating group sizes. Group
|
||||
* sizes are calculated by dividing the total count of targets through the
|
||||
* amount of given groups. In same cases this will lead to less rollout
|
||||
* groups than given by client.
|
||||
*
|
||||
* @param amountOfGroups
|
||||
* the amount of groups
|
||||
* @param conditions
|
||||
* the rollout group conditions
|
||||
* @param savedRollout
|
||||
* the rollout
|
||||
* @return the rollout with created groups
|
||||
*/
|
||||
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
|
||||
final Rollout savedRollout) {
|
||||
int pageIndex = 0;
|
||||
int groupIndex = 0;
|
||||
final Long totalCount = savedRollout.getTotalTargets();
|
||||
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountOfGroups);
|
||||
// validate if the amount of groups that will be created are the amount
|
||||
// of groups that the client what's to have created.
|
||||
int amountGroupValidated = amountOfGroups;
|
||||
final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize));
|
||||
if (amountGroupCreation == (amountOfGroups - 1)) {
|
||||
amountGroupValidated--;
|
||||
}
|
||||
RolloutGroup lastSavedGroup = null;
|
||||
while (pageIndex < totalCount) {
|
||||
groupIndex++;
|
||||
final String nameAndDesc = "group-" + groupIndex;
|
||||
final RolloutGroup group = new RolloutGroup();
|
||||
group.setName(nameAndDesc);
|
||||
group.setDescription(nameAndDesc);
|
||||
group.setRollout(savedRollout);
|
||||
group.setParent(lastSavedGroup);
|
||||
group.setSuccessCondition(conditions.getSuccessCondition());
|
||||
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
|
||||
group.setErrorCondition(conditions.getErrorCondition());
|
||||
group.setErrorConditionExp(conditions.getErrorConditionExp());
|
||||
group.setErrorAction(conditions.getErrorAction());
|
||||
group.setErrorActionExp(conditions.getErrorActionExp());
|
||||
|
||||
final RolloutGroup savedGroup = rolloutGroupRepository.save(group);
|
||||
|
||||
final Slice<Target> targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(),
|
||||
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id")));
|
||||
savedGroup.setTotalTargets(targetGroup.getContent().size());
|
||||
|
||||
lastSavedGroup = savedGroup;
|
||||
|
||||
targetGroup
|
||||
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
|
||||
cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(),
|
||||
amountGroupValidated, groupIndex);
|
||||
pageIndex += groupSize;
|
||||
}
|
||||
|
||||
savedRollout.setStatus(RolloutStatus.READY);
|
||||
return rolloutRepository.save(savedRollout);
|
||||
}
|
||||
Rollout createRolloutAsync(@NotNull Rollout rollout, int amountGroup, @NotNull RolloutGroupConditions conditions);
|
||||
|
||||
/**
|
||||
* Starts a rollout which has been created. The rollout must be in
|
||||
@@ -373,15 +149,9 @@ public class RolloutManagement {
|
||||
* if given rollout is not in {@link RolloutStatus#READY}. Only
|
||||
* ready rollouts can be started.
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public Rollout startRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
checkIfRolloutCanStarted(rollout, mergedRollout);
|
||||
return doStartRollout(mergedRollout);
|
||||
}
|
||||
Rollout startRollout(@NotNull Rollout rollout);
|
||||
|
||||
/**
|
||||
* Starts a rollout asynchronously which has been created. The rollout must
|
||||
@@ -402,61 +172,9 @@ public class RolloutManagement {
|
||||
* if given rollout is not in {@link RolloutStatus#READY}. Only
|
||||
* ready rollouts can be started.
|
||||
*/
|
||||
@Transactional
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public Rollout startRolloutAsync(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
checkIfRolloutCanStarted(rollout, mergedRollout);
|
||||
mergedRollout.setStatus(RolloutStatus.STARTING);
|
||||
final Rollout updatedRollout = rolloutRepository.save(mergedRollout);
|
||||
startingRollouts.add(updatedRollout.getName());
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("startingRollout");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
new TransactionTemplate(txManager, def).execute(status -> {
|
||||
doStartRollout(updatedRollout);
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
startingRollouts.remove(updatedRollout.getName());
|
||||
}
|
||||
});
|
||||
return updatedRollout;
|
||||
|
||||
}
|
||||
|
||||
private Rollout doStartRollout(final Rollout rollout) {
|
||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||
final ActionType actionType = rollout.getActionType();
|
||||
final long forceTime = rollout.getForcedTime();
|
||||
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout);
|
||||
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) {
|
||||
final RolloutGroup rolloutGroup = rolloutGroups.get(iGroup);
|
||||
final List<Target> targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup);
|
||||
// firstgroup can already be started
|
||||
if (iGroup == 0) {
|
||||
final List<TargetWithActionType> targetsWithActionType = targetGroup.stream()
|
||||
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
|
||||
.collect(Collectors.toList());
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout,
|
||||
rolloutGroup);
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
|
||||
} else {
|
||||
// create only not active actions with status scheduled so they
|
||||
// can be activated later
|
||||
deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout,
|
||||
rolloutGroup);
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||
}
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
rollout.setStatus(RolloutStatus.RUNNING);
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
Rollout startRolloutAsync(@NotNull Rollout rollout);
|
||||
|
||||
/**
|
||||
* Pauses a rollout which is currently running. The Rollout switches
|
||||
@@ -477,24 +195,9 @@ public class RolloutManagement {
|
||||
* if given rollout is not in {@link RolloutStatus#RUNNING}.
|
||||
* Only running rollouts can be paused.
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public void pauseRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
if (mergedRollout.getStatus() != RolloutStatus.RUNNING) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
// setting the complete rollout only in paused state. This is sufficient
|
||||
// due the currently running groups will be completed and new groups are
|
||||
// not started until rollout goes back to running state again. The
|
||||
// periodically check for running rollouts will skip rollouts in pause
|
||||
// state.
|
||||
mergedRollout.setStatus(RolloutStatus.PAUSED);
|
||||
rolloutRepository.save(mergedRollout);
|
||||
}
|
||||
void pauseRollout(@NotNull Rollout rollout);
|
||||
|
||||
/**
|
||||
* Resumes a paused rollout. The rollout switches back to
|
||||
@@ -507,19 +210,9 @@ public class RolloutManagement {
|
||||
* if given rollout is not in {@link RolloutStatus#PAUSED}. Only
|
||||
* paused rollouts can be resumed.
|
||||
*/
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public void resumeRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
mergedRollout.setStatus(RolloutStatus.RUNNING);
|
||||
rolloutRepository.save(mergedRollout);
|
||||
}
|
||||
void resumeRollout(@NotNull Rollout rollout);
|
||||
|
||||
/**
|
||||
* Checking running rollouts. Rollouts which are checked updating the
|
||||
@@ -549,211 +242,30 @@ public class RolloutManagement {
|
||||
* this check. This check is only applied if the last check is
|
||||
* less than (lastcheck-delay).
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public void checkRunningRollouts(final long delayBetweenChecks) {
|
||||
verifyStuckedRollouts();
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
|
||||
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
|
||||
lastCheck + delayBetweenChecks);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Rollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
|
||||
RolloutStatus.RUNNING);
|
||||
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
for (final Rollout rollout : rolloutsToCheck) {
|
||||
LOGGER.debug("Checking rollout {}", rollout);
|
||||
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
||||
RolloutGroupStatus.RUNNING);
|
||||
|
||||
if (rolloutGroups.isEmpty()) {
|
||||
// no running rollouts, probably there was an error
|
||||
// somewhere at the latest group. And the latest group has
|
||||
// been switched from running into error state. So we need
|
||||
// to find the latest group which
|
||||
executeLatestRolloutGroup(rollout);
|
||||
} else {
|
||||
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size());
|
||||
executeRolloutGroups(rollout, rolloutGroups);
|
||||
}
|
||||
|
||||
if (isRolloutComplete(rollout)) {
|
||||
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
|
||||
rollout.setStatus(RolloutStatus.FINISHED);
|
||||
rolloutRepository.save(rollout);
|
||||
}
|
||||
}
|
||||
}
|
||||
void checkRunningRollouts(long delayBetweenChecks);
|
||||
|
||||
/**
|
||||
* Verifies and handles stucked rollouts in asynchronous creation or
|
||||
* starting state. If rollouts are created or started asynchronously it
|
||||
* might be that they keep in state {@link RolloutStatus#CREATING} or
|
||||
* {@link RolloutStatus#STARTING} due database or application interruption.
|
||||
* In case this happens, set the rollout to error state.
|
||||
*/
|
||||
private void verifyStuckedRollouts() {
|
||||
final List<Rollout> rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING);
|
||||
rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName()))
|
||||
.forEach(rollout -> {
|
||||
LOGGER.warn(
|
||||
"Determined error during rollout creation of rollout {}, stucking in creating state, setting to status",
|
||||
rollout, RolloutStatus.ERROR_CREATING);
|
||||
rollout.setStatus(RolloutStatus.ERROR_CREATING);
|
||||
rolloutRepository.save(rollout);
|
||||
});
|
||||
|
||||
final List<Rollout> rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING);
|
||||
rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName()))
|
||||
.forEach(rollout -> {
|
||||
LOGGER.warn(
|
||||
"Determined error during rollout starting of rollout {}, stucking in starting state, setting to status",
|
||||
rollout, RolloutStatus.ERROR_STARTING);
|
||||
rollout.setStatus(RolloutStatus.ERROR_STARTING);
|
||||
rolloutRepository.save(rollout);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void executeRolloutGroups(final Rollout rollout, final List<RolloutGroup> rolloutGroups) {
|
||||
for (final RolloutGroup rolloutGroup : rolloutGroups) {
|
||||
// error state check, do we need to stop the whole
|
||||
// rollout because of error?
|
||||
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
|
||||
final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition);
|
||||
if (isError) {
|
||||
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
||||
callErrorAction(rollout, rolloutGroup);
|
||||
} else {
|
||||
// not in error so check finished state, do we need to
|
||||
// start the next group?
|
||||
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
|
||||
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
|
||||
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLatestRolloutGroup(final Rollout rollout) {
|
||||
final List<RolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
||||
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||
if (latestRolloutGroup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||
}
|
||||
|
||||
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
try {
|
||||
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
|
||||
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the error action bean {}",
|
||||
rolloutGroup.getErrorAction().getBeanName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRolloutComplete(final Rollout rollout) {
|
||||
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
|
||||
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
|
||||
return groupsActiveLeft == 0;
|
||||
}
|
||||
|
||||
private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
final Long actionsLeftForRollout = actionRepository
|
||||
.countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup,
|
||||
Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED);
|
||||
return actionsLeftForRollout == 0;
|
||||
}
|
||||
|
||||
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||
final RolloutGroupErrorCondition errorCondition) {
|
||||
if (errorCondition == null) {
|
||||
// there is no error condition, so return false, don't have error.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
|
||||
rolloutGroup, rolloutGroup.getErrorConditionExp());
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the error condition bean {}",
|
||||
errorCondition.getBeanName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||
final RolloutGroupSuccessCondition finishCondition) {
|
||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
||||
try {
|
||||
final boolean isFinished = context
|
||||
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
|
||||
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
||||
if (isFinished) {
|
||||
LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||
} else {
|
||||
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
|
||||
}
|
||||
return isFinished;
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
|
||||
finishCondition.getBeanName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
|
||||
rolloutGroup, rolloutGroup.getSuccessActionExp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts all {@link Target}s in the repository.
|
||||
* Counts all {@link Rollout}s in the repository.
|
||||
*
|
||||
* @return number of targets
|
||||
* @return number of roll outs
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Long countRolloutsAll() {
|
||||
return rolloutRepository.count();
|
||||
}
|
||||
Long countRolloutsAll();
|
||||
|
||||
/**
|
||||
* Count rollouts by specified filter text.
|
||||
* Count rollouts by given text in name or description.
|
||||
*
|
||||
* @param searchText
|
||||
* name or description
|
||||
* @return total count rollouts for specified filter text.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Long countRolloutsAllByFilters(final String searchText) {
|
||||
return rolloutRepository.count(likeNameOrDescription(searchText));
|
||||
}
|
||||
|
||||
private static Specification<Rollout> likeNameOrDescription(final String searchText) {
|
||||
return (rolloutRoot, query, criteriaBuilder) -> {
|
||||
final String searchTextToLower = searchText.toLowerCase();
|
||||
return criteriaBuilder.or(
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower),
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)),
|
||||
searchTextToLower));
|
||||
};
|
||||
}
|
||||
Long countRolloutsAllByFilters(@NotEmpty String searchText);
|
||||
|
||||
/**
|
||||
* * Retrieves a specific rollout by its ID.
|
||||
* Finds rollouts by given text in name or description.
|
||||
*
|
||||
* @param pageable
|
||||
* the page request to sort and limit the result
|
||||
@@ -763,12 +275,7 @@ public class RolloutManagement {
|
||||
* not exists
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Slice<Rollout> findRolloutByFilters(final Pageable pageable, @NotEmpty final String searchText) {
|
||||
final Specification<Rollout> specs = likeNameOrDescription(searchText);
|
||||
final Slice<Rollout> findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class);
|
||||
setRolloutStatusDetails(findAll);
|
||||
return findAll;
|
||||
}
|
||||
Slice<Rollout> findRolloutByFilters(@NotNull Pageable pageable, @NotEmpty String searchText);
|
||||
|
||||
/**
|
||||
* Retrieves a specific rollout by its name.
|
||||
@@ -779,9 +286,7 @@ public class RolloutManagement {
|
||||
* does not exists
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Rollout findRolloutByName(final String rolloutName) {
|
||||
return rolloutRepository.findByName(rolloutName);
|
||||
}
|
||||
Rollout findRolloutByName(@NotNull String rolloutName);
|
||||
|
||||
/**
|
||||
* Update rollout details.
|
||||
@@ -791,14 +296,8 @@ public class RolloutManagement {
|
||||
*
|
||||
* @return Rollout updated rollout
|
||||
*/
|
||||
@NotNull
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
|
||||
public Rollout updateRollout(@NotNull final Rollout rollout) {
|
||||
Assert.notNull(rollout.getId());
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
Rollout updateRollout(@NotNull Rollout rollout);
|
||||
|
||||
/**
|
||||
* Get count of targets in different status in rollout.
|
||||
@@ -810,12 +309,7 @@ public class RolloutManagement {
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable page) {
|
||||
final Page<Rollout> rollouts = findAll(page);
|
||||
setRolloutStatusDetails(rollouts);
|
||||
return rollouts;
|
||||
|
||||
}
|
||||
Page<Rollout> findAllRolloutsWithDetailedStatus(@NotNull Pageable page);
|
||||
|
||||
/**
|
||||
* Get count of targets in different status in rollout.
|
||||
@@ -826,40 +320,7 @@ public class RolloutManagement {
|
||||
*
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
public Rollout findRolloutWithDetailedStatus(final Long rolloutId) {
|
||||
final Rollout rollout = findRolloutById(rolloutId);
|
||||
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
|
||||
.getStatusCountByRolloutId(rolloutId);
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
|
||||
rollout.getTotalTargets());
|
||||
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
return rollout;
|
||||
}
|
||||
|
||||
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rolloutIds) {
|
||||
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
|
||||
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
|
||||
}
|
||||
|
||||
private void setRolloutStatusDetails(final Slice<Rollout> rollouts) {
|
||||
final List<Long> rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId())
|
||||
.collect(Collectors.toList());
|
||||
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
|
||||
rolloutIds);
|
||||
|
||||
for (final Rollout rollout : rollouts) {
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
|
||||
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
|
||||
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
|
||||
if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
Rollout findRolloutWithDetailedStatus(@NotNull Long rolloutId);
|
||||
|
||||
/***
|
||||
* Get finished percentage details for a specified group which is in running
|
||||
@@ -871,16 +332,7 @@ public class RolloutManagement {
|
||||
* the ID of the {@link RolloutGroup}
|
||||
* @return percentage finished
|
||||
*/
|
||||
public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) {
|
||||
final Long totalGroup = rolloutGroup.getTotalTargets();
|
||||
final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId,
|
||||
rolloutGroup.getId(), Action.Status.FINISHED);
|
||||
if (totalGroup == 0) {
|
||||
// in case e.g. targets has been deleted we don't have any actions
|
||||
// left for this group, so the group is finished
|
||||
return 100;
|
||||
}
|
||||
// calculate threshold
|
||||
return ((float) finished / (float) totalGroup) * 100;
|
||||
}
|
||||
}
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
|
||||
float getFinishedPercentForRunningGroup(@NotNull Long rolloutId, @NotNull RolloutGroup rolloutGroup);
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
@@ -13,39 +5,11 @@ import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Central tenant configuration management operations of the SP server.
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationRepository tenantConfigurationRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private final ConfigurableConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
private Environment environment;
|
||||
public interface TenantConfigurationManagement {
|
||||
|
||||
/**
|
||||
* Retrieves a configuration value from the e.g. tenant overwritten
|
||||
@@ -70,37 +34,10 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
* if the property cannot be converted to the given
|
||||
* {@code propertyType}
|
||||
*/
|
||||
|
||||
@Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public <T> TenantConfigurationValue<T> getConfigurationValue(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data type of the tenant configuration. If it is possible to
|
||||
* cast to the given data type.
|
||||
*
|
||||
* @param configurationKey
|
||||
* the key
|
||||
* @param propertyType
|
||||
* the class
|
||||
*/
|
||||
protected <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
}
|
||||
<T> TenantConfigurationValue<T> getConfigurationValue(TenantConfigurationKey configurationKey,
|
||||
Class<T> propertyType);
|
||||
|
||||
/**
|
||||
* Build the tenant configuration by the given key
|
||||
@@ -114,24 +51,10 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
* @return <null> if no default value is set and no database value available
|
||||
* or returns the tenant configuration value
|
||||
*/
|
||||
protected <T> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
|
||||
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
|
||||
final TenantConfiguration tenantConfiguration) {
|
||||
if (tenantConfiguration != null) {
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(false).createdBy(tenantConfiguration.getCreatedBy())
|
||||
.createdAt(tenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
|
||||
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
|
||||
|
||||
} else if (configurationKey.getDefaultKeyName() != null) {
|
||||
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(true).createdBy(null).createdAt(null)
|
||||
.lastModifiedAt(null).lastModifiedBy(null)
|
||||
.value(getGlobalConfigurationValue(configurationKey, propertyType)).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
<T> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(TenantConfigurationKey configurationKey,
|
||||
Class<T> propertyType, TenantConfiguration tenantConfiguration);
|
||||
|
||||
/**
|
||||
* Retrieves a configuration value from the e.g. tenant overwritten
|
||||
@@ -153,9 +76,7 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
*/
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public TenantConfigurationValue<?> getConfigurationValue(final TenantConfigurationKey configurationKey) {
|
||||
return getConfigurationValue(configurationKey, configurationKey.getDataType());
|
||||
}
|
||||
TenantConfigurationValue<?> getConfigurationValue(TenantConfigurationKey configurationKey);
|
||||
|
||||
/**
|
||||
* returns the global configuration property either defined in the property
|
||||
@@ -179,23 +100,7 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
*/
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
public <T> T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
|
||||
final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType);
|
||||
|
||||
if (valueInProperties == null) {
|
||||
return conversionService.convert(configurationKey.getDefaultValue(), propertyType);
|
||||
}
|
||||
|
||||
return valueInProperties;
|
||||
}
|
||||
<T> T getGlobalConfigurationValue(TenantConfigurationKey configurationKey, Class<T> propertyType);
|
||||
|
||||
/**
|
||||
* Adds or updates a specific configuration for a specific tenant.
|
||||
@@ -213,41 +118,8 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
* @throws ConversionFailedException
|
||||
* if the property cannot be converted to the given
|
||||
*/
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
|
||||
public <T> TenantConfigurationValue<T> addOrUpdateConfiguration(final TenantConfigurationKey configurationKey,
|
||||
final T value) {
|
||||
|
||||
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value,
|
||||
value.getClass(), configurationKey.getDataType()));
|
||||
}
|
||||
|
||||
configurationKey.validate(applicationContext, value);
|
||||
|
||||
TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
if (tenantConfiguration == null) {
|
||||
tenantConfiguration = new TenantConfiguration(configurationKey.getKeyName(), value.toString());
|
||||
} else {
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
|
||||
final TenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository.save(tenantConfiguration);
|
||||
|
||||
final Class<T> clazzT = (Class<T>) value.getClass();
|
||||
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(false)
|
||||
.createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
|
||||
.value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build();
|
||||
}
|
||||
<T> TenantConfigurationValue<T> addOrUpdateConfiguration(TenantConfigurationKey configurationKey, T value);
|
||||
|
||||
/**
|
||||
* Deletes a specific configuration for the current tenant. Does nothing in
|
||||
@@ -256,16 +128,7 @@ public class TenantConfigurationManagement implements EnvironmentAware {
|
||||
* @param configurationKey
|
||||
* the configuration key to be deleted
|
||||
*/
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
|
||||
public void deleteConfiguration(final TenantConfigurationKey configurationKey) {
|
||||
tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName());
|
||||
}
|
||||
void deleteConfiguration(TenantConfigurationKey configurationKey);
|
||||
|
||||
@Override
|
||||
public void setEnvironment(final Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -27,8 +27,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
/**
|
||||
* {@link DistributionSet} repository.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
public interface DistributionSetRepository
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* 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 java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
|
||||
import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.GridFSDBFileNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.specifications.SoftwareModuleSpecification;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* JPA based {@link ArtifactManagement} implementation.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaArtifactManagement implements ArtifactManagement {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class);
|
||||
|
||||
@Autowired
|
||||
private LocalArtifactRepository localArtifactRepository;
|
||||
|
||||
@Autowired
|
||||
private ExternalArtifactRepository externalArtifactRepository;
|
||||
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private ExternalArtifactProviderRepository externalArtifactProviderRepository;
|
||||
|
||||
@Autowired
|
||||
private ArtifactRepository artifactRepository;
|
||||
|
||||
private static LocalArtifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
|
||||
final SoftwareModule softwareModule) {
|
||||
if (softwareModule.getLocalArtifactByFilename(filename).isPresent()) {
|
||||
if (overrideExisting) {
|
||||
LOG.debug("overriding existing artifact with new filename {}", filename);
|
||||
return softwareModule.getLocalArtifactByFilename(filename).get();
|
||||
} else {
|
||||
throw new EntityAlreadyExistsException("File with that name already exists in the Software Module");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public ExternalArtifact createExternalArtifact(final ExternalArtifactProvider externalRepository,
|
||||
final String urlSuffix, final Long moduleId) {
|
||||
|
||||
final SoftwareModule module = getModuleAndThrowExceptionIfThatFails(moduleId);
|
||||
return externalArtifactRepository.save(new ExternalArtifact(externalRepository, urlSuffix, module));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public ExternalArtifactProvider createExternalArtifactProvider(final String name, final String description,
|
||||
final String basePath, final String defaultUrlSuffix) {
|
||||
return externalArtifactProviderRepository
|
||||
.save(new ExternalArtifactProvider(name, description, basePath, defaultUrlSuffix));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public LocalArtifact createLocalArtifact(final InputStream stream, final Long moduleId, final String filename,
|
||||
final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting,
|
||||
final String contentType) {
|
||||
DbArtifact result = null;
|
||||
|
||||
final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId);
|
||||
|
||||
final LocalArtifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
|
||||
|
||||
try {
|
||||
result = artifactRepository.store(stream, filename, contentType,
|
||||
new DbArtifactHash(providedSha1Sum, providedMd5Sum));
|
||||
} catch (final ArtifactStoreException e) {
|
||||
throw new ArtifactUploadFailedException(e);
|
||||
} catch (final HashNotMatchException e) {
|
||||
if (e.getHashFunction().equals(HashNotMatchException.SHA1)) {
|
||||
throw new InvalidSHA1HashException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new InvalidMD5HashException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return storeArtifactMetadata(softwareModule, filename, result, existing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void deleteExternalArtifact(final Long id) {
|
||||
final ExternalArtifact existing = externalArtifactRepository.findOne(id);
|
||||
|
||||
if (null == existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
existing.getSoftwareModule().removeArtifact(existing);
|
||||
softwareModuleRepository.save(existing.getSoftwareModule());
|
||||
externalArtifactRepository.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void deleteLocalArtifact(final LocalArtifact existing) {
|
||||
if (existing == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean artifactIsOnlyUsedByOneSoftwareModule = true;
|
||||
for (final LocalArtifact lArtifact : localArtifactRepository
|
||||
.findByGridFsFileName(existing.getGridFsFileName())) {
|
||||
if (!lArtifact.getSoftwareModule().isDeleted()
|
||||
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
|
||||
artifactIsOnlyUsedByOneSoftwareModule = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (artifactIsOnlyUsedByOneSoftwareModule) {
|
||||
try {
|
||||
LOG.debug("deleting artifact from repository {}", existing.getGridFsFileName());
|
||||
artifactRepository.deleteBySha1(existing.getGridFsFileName());
|
||||
} catch (final ArtifactStoreException e) {
|
||||
throw new ArtifactDeleteFailedException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void deleteLocalArtifact(final Long id) {
|
||||
final LocalArtifact existing = localArtifactRepository.findOne(id);
|
||||
|
||||
if (null == existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteLocalArtifact(existing);
|
||||
|
||||
existing.getSoftwareModule().removeArtifact(existing);
|
||||
softwareModuleRepository.save(existing.getSoftwareModule());
|
||||
localArtifactRepository.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Artifact findArtifact(final Long id) {
|
||||
return localArtifactRepository.findOne(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LocalArtifact> findByFilenameAndSoftwareModule(final String filename, final Long softwareModuleId) {
|
||||
return localArtifactRepository.findByFilenameAndSoftwareModuleId(filename, softwareModuleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalArtifact findFirstLocalArtifactsBySHA1(final String sha1) {
|
||||
return localArtifactRepository.findFirstByGridFsFileName(sha1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LocalArtifact> findLocalArtifactByFilename(final String filename) {
|
||||
return localArtifactRepository.findByFilename(filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<LocalArtifact> findLocalArtifactBySoftwareModule(final Pageable pageReq, final Long swId) {
|
||||
return localArtifactRepository.findBySoftwareModuleId(pageReq, swId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftwareModule findSoftwareModuleById(final Long id) {
|
||||
|
||||
final Specification<SoftwareModule> spec = SoftwareModuleSpecification.byId(id);
|
||||
|
||||
return softwareModuleRepository.findOne(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftwareModule findSoftwareModuleWithDetails(final Long id) {
|
||||
final SoftwareModule result = findSoftwareModuleById(id);
|
||||
if (result != null) {
|
||||
result.getArtifacts().size();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SoftwareModule getModuleAndThrowExceptionIfThatFails(final Long moduleId) {
|
||||
final SoftwareModule softwareModule = findSoftwareModuleWithDetails(moduleId);
|
||||
|
||||
if (softwareModule == null) {
|
||||
LOG.debug("no software module with ID {} exists", moduleId);
|
||||
throw new EntityNotFoundException("Software Module: " + moduleId);
|
||||
}
|
||||
return softwareModule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbArtifact loadLocalArtifactBinary(final LocalArtifact artifact) {
|
||||
final DbArtifact result = artifactRepository.getArtifactBySha1(artifact.getGridFsFileName());
|
||||
if (result == null) {
|
||||
throw new GridFSDBFileNotFoundException(artifact.getGridFsFileName());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LocalArtifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,
|
||||
final DbArtifact result, final LocalArtifact existing) {
|
||||
LocalArtifact artifact = existing;
|
||||
if (existing == null) {
|
||||
artifact = new LocalArtifact(result.getHashes().getSha1(), providedFilename, softwareModule);
|
||||
}
|
||||
artifact.setMd5Hash(result.getHashes().getMd5());
|
||||
artifact.setSha1Hash(result.getHashes().getSha1());
|
||||
artifact.setSize(result.getSize());
|
||||
|
||||
LOG.debug("storing new artifact into repository {}", artifact);
|
||||
return localArtifactRepository.save(artifact);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* 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 java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.ToManyAttributeEntriesException;
|
||||
import org.eclipse.hawkbit.repository.exception.ToManyStatusEntriesException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
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.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.specifications.ActionSpecifications;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* JPA based {@link ControllerManagement} implementation.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaControllerManagement implements ControllerManagement {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
|
||||
private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos");
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
|
||||
@Autowired
|
||||
private TargetInfoRepository targetInfoRepository;
|
||||
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionStatusRepository actionStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private HawkbitSecurityProperties securityProperties;
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationRepository tenantConfigurationRepository;
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
@Override
|
||||
public String getPollingTime() {
|
||||
final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
|
||||
final Class<String> propertyType = String.class;
|
||||
JpaTenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
return tenantConfigurationManagement
|
||||
.buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Target updateLastTargetQuery(final String controllerId, final URI address) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
if (target == null) {
|
||||
throw new EntityNotFoundException(controllerId);
|
||||
}
|
||||
|
||||
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getActionForDownloadByTargetAndSoftwareModule(final String controllerId,
|
||||
final SoftwareModule module) {
|
||||
final List<Action> action = actionRepository.findActionByTargetAndSoftwareModule(controllerId, module);
|
||||
|
||||
if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) {
|
||||
throw new EntityNotFoundException(
|
||||
"No assigment found for module " + module.getId() + " to target " + controllerId);
|
||||
}
|
||||
|
||||
return action.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTargetArtifactAssigned(final String controllerId, final LocalArtifact localArtifact) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActionByTargetAndActive(final Target target) {
|
||||
return actionRepository.findByTargetAndActiveOrderByIdAsc(target, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SoftwareModule> findSoftwareModulesByDistributionSet(final DistributionSet distributionSet) {
|
||||
return softwareModuleRepository.findByAssignedTo(distributionSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action findActionWithDetails(final Long actionId) {
|
||||
return actionRepository.findById(actionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Target findOrRegisterTargetIfItDoesNotexist(final String controllerId, final URI address) {
|
||||
final Specification<Target> spec = (targetRoot, query, cb) -> cb.equal(targetRoot.get(Target_.controllerId),
|
||||
controllerId);
|
||||
|
||||
Target target = targetRepository.findOne(spec);
|
||||
|
||||
if (target == null) {
|
||||
target = new Target(controllerId);
|
||||
target.setDescription("Plug and Play target: " + controllerId);
|
||||
target.setName(controllerId);
|
||||
return targetManagement.createTarget(target, TargetUpdateStatus.REGISTERED, System.currentTimeMillis(),
|
||||
address);
|
||||
}
|
||||
|
||||
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public TargetInfo updateTargetStatus(final TargetInfo targetInfo, final TargetUpdateStatus status,
|
||||
final Long lastTargetQuery, final URI address) {
|
||||
final TargetInfo mtargetInfo = entityManager.merge(targetInfo);
|
||||
if (status != null) {
|
||||
mtargetInfo.setUpdateStatus(status);
|
||||
}
|
||||
if (lastTargetQuery != null) {
|
||||
mtargetInfo.setLastTargetQuery(lastTargetQuery);
|
||||
}
|
||||
if (address != null) {
|
||||
mtargetInfo.setAddress(address.toString());
|
||||
}
|
||||
return targetInfoRepository.save(mtargetInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Action addCancelActionStatus(final ActionStatus actionStatus) {
|
||||
final Action action = actionStatus.getAction();
|
||||
|
||||
checkForToManyStatusEntries(action);
|
||||
action.setStatus(actionStatus.getStatus());
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
case WARNING:
|
||||
case ERROR:
|
||||
case RUNNING:
|
||||
break;
|
||||
case CANCELED:
|
||||
case FINISHED:
|
||||
// in case of successful cancellation we also report the success at
|
||||
// the canceled action itself.
|
||||
actionStatus.addMessage(
|
||||
ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation completion is finished sucessfully.");
|
||||
DeploymentHelper.successCancellation(action, actionRepository, targetManagement, targetInfoRepository,
|
||||
entityManager);
|
||||
break;
|
||||
case RETRIEVED:
|
||||
actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation request retrieved.");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
actionRepository.save(action);
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus) {
|
||||
final Action action = actionStatus.getAction();
|
||||
|
||||
if (!action.isActive()) {
|
||||
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
|
||||
actionStatus.getId(), action.getId());
|
||||
return action;
|
||||
}
|
||||
return handleAddUpdateActionStatus(actionStatus, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
|
||||
*
|
||||
* @param actionStatus
|
||||
* @param action
|
||||
* @return
|
||||
*/
|
||||
private Action handleAddUpdateActionStatus(final ActionStatus actionStatus, final Action action) {
|
||||
LOG.debug("addUpdateActionStatus for action {}", action.getId());
|
||||
|
||||
final Action mergedAction = entityManager.merge(action);
|
||||
Target mergedTarget = mergedAction.getTarget();
|
||||
// check for a potential DOS attack
|
||||
checkForToManyStatusEntries(action);
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
case ERROR:
|
||||
mergedTarget = DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.ERROR, false,
|
||||
targetInfoRepository, entityManager);
|
||||
handleErrorOnAction(mergedAction, mergedTarget);
|
||||
break;
|
||||
case FINISHED:
|
||||
handleFinishedAndStoreInTargetStatus(mergedTarget, mergedAction);
|
||||
break;
|
||||
case CANCELED:
|
||||
case WARNING:
|
||||
case RUNNING:
|
||||
DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.PENDING, false, targetInfoRepository,
|
||||
entityManager);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
LOG.debug("addUpdateActionStatus {} for target {} is finished.", action.getId(), mergedTarget.getId());
|
||||
|
||||
return actionRepository.save(mergedAction);
|
||||
}
|
||||
|
||||
private void handleErrorOnAction(final Action mergedAction, final Target mergedTarget) {
|
||||
mergedAction.setActive(false);
|
||||
mergedAction.setStatus(Status.ERROR);
|
||||
mergedTarget.setAssignedDistributionSet(null);
|
||||
targetManagement.updateTarget(mergedTarget);
|
||||
}
|
||||
|
||||
private void checkForToManyStatusEntries(final Action action) {
|
||||
if (securityProperties.getDos().getMaxStatusEntriesPerAction() > 0) {
|
||||
|
||||
final Long statusCount = actionStatusRepository.countByAction(action);
|
||||
|
||||
if (statusCount >= securityProperties.getDos().getMaxStatusEntriesPerAction()) {
|
||||
LOG_DOS.error(
|
||||
"Potential denial of service (DOS) attack identfied. More status entries in the system than permitted ({})!",
|
||||
securityProperties.getDos().getMaxStatusEntriesPerAction());
|
||||
throw new ToManyStatusEntriesException(
|
||||
String.valueOf(securityProperties.getDos().getMaxStatusEntriesPerAction()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFinishedAndStoreInTargetStatus(final Target target, final Action action) {
|
||||
action.setActive(false);
|
||||
action.setStatus(Status.FINISHED);
|
||||
final TargetInfo targetInfo = target.getTargetInfo();
|
||||
final DistributionSet ds = entityManager.merge(action.getDistributionSet());
|
||||
targetInfo.setInstalledDistributionSet(ds);
|
||||
if (target.getAssignedDistributionSet() != null && targetInfo.getInstalledDistributionSet() != null && target
|
||||
.getAssignedDistributionSet().getId().equals(targetInfo.getInstalledDistributionSet().getId())) {
|
||||
targetInfo.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
targetInfo.setInstallationDate(System.currentTimeMillis());
|
||||
} else {
|
||||
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
targetInfo.setInstallationDate(System.currentTimeMillis());
|
||||
}
|
||||
targetInfoRepository.save(targetInfo);
|
||||
entityManager.detach(ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Target updateControllerAttributes(final String controllerId, final Map<String, String> data) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
|
||||
if (target == null) {
|
||||
throw new EntityNotFoundException(controllerId);
|
||||
}
|
||||
|
||||
target.getTargetInfo().getControllerAttributes().putAll(data);
|
||||
|
||||
if (target.getTargetInfo().getControllerAttributes().size() > securityProperties.getDos()
|
||||
.getMaxAttributeEntriesPerTarget()) {
|
||||
LOG_DOS.info("Target tries to insert more than the allowed number of entries ({}). DOS attack anticipated!",
|
||||
securityProperties.getDos().getMaxAttributeEntriesPerTarget());
|
||||
throw new ToManyAttributeEntriesException(
|
||||
String.valueOf(securityProperties.getDos().getMaxAttributeEntriesPerTarget()));
|
||||
}
|
||||
|
||||
target.getTargetInfo().setLastTargetQuery(System.currentTimeMillis());
|
||||
target.getTargetInfo().setRequestControllerAttributes(false);
|
||||
return targetRepository.save(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Action registerRetrieved(final Action action, final String message) {
|
||||
return handleRegisterRetrieved(action, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers retrieved status for given {@link Target} and {@link Action} if
|
||||
* it does not exist yet.
|
||||
*
|
||||
* @param action
|
||||
* to the handle status for
|
||||
* @param message
|
||||
* for the status
|
||||
* @return the updated action in case the status has been changed to
|
||||
* {@link Status#RETRIEVED}
|
||||
*/
|
||||
private Action handleRegisterRetrieved(final Action action, final String message) {
|
||||
// do a manual query with CriteriaBuilder to avoid unnecessary field
|
||||
// queries and an extra
|
||||
// count query made by spring-data when using pageable requests, we
|
||||
// don't need an extra count
|
||||
// query, we just want to check if the last action status is a retrieved
|
||||
// or not.
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryActionStatus = cb.createQuery(Object[].class);
|
||||
final Root<ActionStatus> actionStatusRoot = queryActionStatus.from(ActionStatus.class);
|
||||
final CriteriaQuery<Object[]> query = queryActionStatus
|
||||
.multiselect(actionStatusRoot.get(ActionStatus_.id), actionStatusRoot.get(ActionStatus_.status))
|
||||
.where(cb.equal(actionStatusRoot.get(ActionStatus_.action), action))
|
||||
.orderBy(cb.desc(actionStatusRoot.get(ActionStatus_.id)));
|
||||
final List<Object[]> resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1)
|
||||
.getResultList();
|
||||
|
||||
// if the latest status is not in retrieve state then we add a retrieved
|
||||
// state again, we want
|
||||
// to document a deployment retrieved status and a cancel retrieved
|
||||
// status, but multiple
|
||||
// retrieves after the other we don't want to store to protect to
|
||||
// overflood action status in
|
||||
// case controller retrieves a action multiple times.
|
||||
if (resultList.isEmpty() || resultList.get(0)[1] != Status.RETRIEVED) {
|
||||
// document that the status has been retrieved
|
||||
actionStatusRepository
|
||||
.save(new ActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));
|
||||
|
||||
// don't change the action status itself in case the action is in
|
||||
// canceling state otherwise
|
||||
// we modify the action status and the controller won't get the
|
||||
// cancel job anymore.
|
||||
if (!action.isCancelingOrCanceled()) {
|
||||
final Action actionMerge = entityManager.merge(action);
|
||||
actionMerge.setStatus(Status.RETRIEVED);
|
||||
return actionRepository.save(actionMerge);
|
||||
}
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void addInformationalActionStatus(final ActionStatus statusMessage) {
|
||||
actionStatusRepository.save(statusMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public String getSecurityTokenByControllerId(final String controllerId) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
return target != null ? target.getSecurityToken() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/**
|
||||
* 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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.Constants;
|
||||
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetInfoUpdateEvent;
|
||||
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||
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.ActionWithStatusCount;
|
||||
import org.eclipse.hawkbit.repository.model.Action_;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout_;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.specifications.TargetSpecifications;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
/**
|
||||
* JPA implementation for DeploymentManagement.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class);
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private DistributionSetRepository distributoinSetRepository;
|
||||
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionStatusRepository actionStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
|
||||
@Autowired
|
||||
private TargetInfoRepository targetInfoRepository;
|
||||
|
||||
@Autowired
|
||||
private AuditorAware<String> auditorProvider;
|
||||
|
||||
@Autowired
|
||||
private EventBus eventBus;
|
||||
|
||||
@Autowired
|
||||
private AfterTransactionCommitExecutor afterCommit;
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Modifying
|
||||
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset,
|
||||
final List<Target> targets) {
|
||||
|
||||
return assignDistributionSetByTargetId(pset,
|
||||
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()),
|
||||
ActionType.FORCED, Action.NO_FORCE_TIME);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
|
||||
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final String... targetIDs) {
|
||||
return assignDistributionSet(dsID, ActionType.FORCED, Action.NO_FORCE_TIME, targetIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
|
||||
// Exception squid:S2095: see
|
||||
// https://jira.sonarsource.com/browse/SONARJAVA-1478
|
||||
@SuppressWarnings({ "squid:S2095" })
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
|
||||
final long forcedTimestamp, final String... targetIDs) {
|
||||
return assignDistributionSet(dsID, Arrays.stream(targetIDs)
|
||||
.map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
|
||||
final List<TargetWithActionType> targets) {
|
||||
final DistributionSet set = distributoinSetRepository.findOne(dsID);
|
||||
if (set == null) {
|
||||
throw new EntityNotFoundException(
|
||||
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
|
||||
}
|
||||
|
||||
return assignDistributionSetToTargets(set, targets, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
|
||||
final List<TargetWithActionType> targets, final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
final DistributionSet set = distributoinSetRepository.findOne(dsID);
|
||||
if (set == null) {
|
||||
throw new EntityNotFoundException(
|
||||
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
|
||||
}
|
||||
|
||||
return assignDistributionSetToTargets(set, targets, rollout, rolloutGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* method assigns the {@link DistributionSet} to all {@link Target}s by
|
||||
* their IDs with a specific {@link ActionType} and {@code forcetime}.
|
||||
*
|
||||
* @param dsID
|
||||
* the ID of the distribution set to assign
|
||||
* @param targets
|
||||
* a list of all targets and their action type
|
||||
* @param rollout
|
||||
* the rollout for this assignment
|
||||
* @param rolloutGroup
|
||||
* the rollout group for this assignment
|
||||
* @return the assignment result
|
||||
*
|
||||
* @throw IncompleteDistributionSetException if mandatory
|
||||
* {@link SoftwareModuleType} are not assigned as define by the
|
||||
* {@link DistributionSetType}.
|
||||
*/
|
||||
private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final DistributionSet set,
|
||||
final List<TargetWithActionType> targetsWithActionType, final Rollout rollout,
|
||||
final RolloutGroup rolloutGroup) {
|
||||
|
||||
if (!set.isComplete()) {
|
||||
throw new IncompleteDistributionSetException(
|
||||
"Distribution set of type " + set.getType().getKey() + " is incomplete: " + set.getId());
|
||||
}
|
||||
|
||||
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getTargetId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size());
|
||||
|
||||
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
|
||||
.collect(Collectors.toMap(TargetWithActionType::getTargetId, Function.identity()));
|
||||
|
||||
// split tIDs length into max entries in-statement because many database
|
||||
// have constraint of max entries in in-statements e.g. Oracle with
|
||||
// maximum 1000 elements, so we need to split the entries here and
|
||||
// execute multiple statements we take the target only into account if
|
||||
// the requested operation is no duplicate of a previous one
|
||||
final List<Target> targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
|
||||
.map(ids -> targetRepository
|
||||
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId())))
|
||||
.flatMap(t -> t.stream()).collect(Collectors.toList());
|
||||
|
||||
if (targets.isEmpty()) {
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
// return with nothing as all targets had the DS already assigned
|
||||
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(),
|
||||
Collections.emptyList(), targetManagement);
|
||||
}
|
||||
|
||||
final List<List<Long>> targetIds = Lists.partition(
|
||||
targets.stream().map(Target::getId).collect(Collectors.toList()), Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||
|
||||
// override all active actions and set them into canceling state, we
|
||||
// need to remember which one we have been switched to canceling state
|
||||
// because for targets which we have changed to canceling we don't want
|
||||
// to publish the new action update event.
|
||||
final Set<Long> targetIdsCancellList = new HashSet<>();
|
||||
targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids)));
|
||||
|
||||
// cancel all scheduled actions which are in-active, these actions were
|
||||
// not active before and the manual assignment which has been done
|
||||
// cancels the
|
||||
targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED));
|
||||
|
||||
// set assigned distribution set and TargetUpdateStatus
|
||||
final String currentUser;
|
||||
if (auditorProvider != null) {
|
||||
currentUser = auditorProvider.getCurrentAuditor();
|
||||
} else {
|
||||
currentUser = null;
|
||||
}
|
||||
|
||||
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSet(set, System.currentTimeMillis(),
|
||||
currentUser, tIds));
|
||||
targetIds.forEach(tIds -> targetInfoRepository.setTargetUpdateStatus(TargetUpdateStatus.PENDING, tIds));
|
||||
final Map<String, Action> targetIdsToActions = actionRepository
|
||||
.save(targets.stream().map(t -> createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup))
|
||||
.collect(Collectors.toList()))
|
||||
.stream().collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
|
||||
|
||||
// create initial action status when action is created so we remember
|
||||
// the initial running status because we will change the status
|
||||
// of the action itself and with this action status we have a nicer
|
||||
// action history.
|
||||
targetIdsToActions.values().forEach(action -> {
|
||||
final ActionStatus actionStatus = new ActionStatus();
|
||||
actionStatus.setAction(action);
|
||||
actionStatus.setOccurredAt(action.getCreatedAt());
|
||||
actionStatus.setStatus(Status.RUNNING);
|
||||
actionStatusRepository.save(actionStatus);
|
||||
});
|
||||
|
||||
// flush to get action IDs
|
||||
entityManager.flush();
|
||||
// collect updated target and actions IDs in order to return them
|
||||
final DistributionSetAssignmentResult result = new DistributionSetAssignmentResult(
|
||||
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()), targets.size(),
|
||||
controllerIDs.size() - targets.size(),
|
||||
targetIdsToActions.values().stream().map(Action::getId).collect(Collectors.toList()), targetManagement);
|
||||
|
||||
LOG.debug("assignDistribution({}) finished {}", set, result);
|
||||
|
||||
final List<SoftwareModule> softwareModules = softwareModuleRepository.findByAssignedTo(set);
|
||||
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
|
||||
sendDistributionSetAssignmentEvent(targets, targetIdsCancellList, targetIdsToActions, softwareModules);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void sendDistributionSetAssignmentEvent(final List<Target> targets, final Set<Long> targetIdsCancellList,
|
||||
final Map<String, Action> targetIdsToActions, final List<SoftwareModule> softwareModules) {
|
||||
targets.stream().filter(t -> !!!targetIdsCancellList.contains(t.getId()))
|
||||
.forEach(t -> assignDistributionSetEvent(t, targetIdsToActions.get(t.getControllerId()).getId(),
|
||||
softwareModules));
|
||||
}
|
||||
|
||||
private static Action createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap,
|
||||
final Target target, final DistributionSet set, final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
final Action actionForTarget = new Action();
|
||||
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
|
||||
actionForTarget.setActionType(targetWithActionType.getActionType());
|
||||
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
|
||||
actionForTarget.setActive(true);
|
||||
actionForTarget.setStatus(Status.RUNNING);
|
||||
actionForTarget.setTarget(target);
|
||||
actionForTarget.setDistributionSet(set);
|
||||
actionForTarget.setRollout(rollout);
|
||||
actionForTarget.setRolloutGroup(rolloutGroup);
|
||||
return actionForTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the {@link TargetAssignDistributionSetEvent} for a specific target
|
||||
* to the {@link EventBus}.
|
||||
*
|
||||
* @param target
|
||||
* the Target which has been assigned to a distribution set
|
||||
* @param actionId
|
||||
* the action id of the assignment
|
||||
* @param softwareModules
|
||||
* the software modules which have been assigned
|
||||
*/
|
||||
private void assignDistributionSetEvent(final Target target, final Long actionId,
|
||||
final List<SoftwareModule> softwareModules) {
|
||||
target.getTargetInfo().setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
afterCommit.afterCommit(() -> {
|
||||
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
|
||||
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
|
||||
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
|
||||
target.getSecurityToken()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes {@link UpdateAction}s that are no longer necessary and sends
|
||||
* cancellations to the controller.
|
||||
*
|
||||
* @param myTarget
|
||||
* to override {@link UpdateAction}s
|
||||
*/
|
||||
private Set<Long> overrideObsoleteUpdateActions(final List<Long> targetsIds) {
|
||||
|
||||
final Set<Long> cancelledTargetIds = new HashSet<>();
|
||||
|
||||
// Figure out if there are potential target/action combinations that
|
||||
// need to be considered
|
||||
// for cancelation
|
||||
final List<Action> activeActions = actionRepository
|
||||
.findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
|
||||
Action.Status.CANCELING);
|
||||
activeActions.forEach(action -> {
|
||||
action.setStatus(Status.CANCELING);
|
||||
// document that the status has been retrieved
|
||||
|
||||
actionStatusRepository.save(new ActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
|
||||
"manual cancelation requested"));
|
||||
|
||||
cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
|
||||
|
||||
cancelledTargetIds.add(action.getTarget().getId());
|
||||
});
|
||||
|
||||
actionRepository.save(activeActions);
|
||||
|
||||
return cancelledTargetIds;
|
||||
|
||||
}
|
||||
|
||||
private DistributionSetAssignmentResult assignDistributionSetByTargetId(@NotNull final DistributionSet set,
|
||||
@NotEmpty final List<String> tIDs, final ActionType actionType, final long forcedTime) {
|
||||
|
||||
return assignDistributionSetToTargets(set, tIDs.stream()
|
||||
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null,
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
public Action cancelAction(final Action action, final Target target) {
|
||||
LOG.debug("cancelAction({}, {})", action, target);
|
||||
if (action.isCancelingOrCanceled()) {
|
||||
throw new CancelActionNotAllowedException("Actions in canceling or canceled state cannot be canceled");
|
||||
}
|
||||
final Action myAction = entityManager.merge(action);
|
||||
|
||||
if (myAction.isActive()) {
|
||||
LOG.debug("action ({}) was still active. Change to {}.", action, Status.CANCELING);
|
||||
myAction.setStatus(Status.CANCELING);
|
||||
|
||||
// document that the status has been retrieved
|
||||
actionStatusRepository.save(new ActionStatus(myAction, Status.CANCELING, System.currentTimeMillis(),
|
||||
"manual cancelation requested"));
|
||||
final Action saveAction = actionRepository.save(myAction);
|
||||
cancelAssignDistributionSetEvent(target, myAction.getId());
|
||||
|
||||
return saveAction;
|
||||
} else {
|
||||
throw new CancelActionNotAllowedException(
|
||||
"Action [id: " + action.getId() + "] is not active and cannot be canceled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the {@link CancelTargetAssignmentEvent} for a specific target to
|
||||
* the {@link EventBus}.
|
||||
*
|
||||
* @param target
|
||||
* the Target which has been assigned to a distribution set
|
||||
* @param actionId
|
||||
* the action id of the assignment
|
||||
*/
|
||||
private void cancelAssignDistributionSetEvent(final Target target, final Long actionId) {
|
||||
afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getOptLockRevision(),
|
||||
target.getTenant(), target.getControllerId(), actionId, target.getTargetInfo().getAddress())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
public Action forceQuitAction(final Action action) {
|
||||
final Action mergedAction = entityManager.merge(action);
|
||||
|
||||
if (!mergedAction.isCancelingOrCanceled()) {
|
||||
throw new ForceQuitActionNotAllowedException(
|
||||
"Action [id: " + action.getId() + "] is not canceled yet and cannot be force quit");
|
||||
}
|
||||
|
||||
if (!mergedAction.isActive()) {
|
||||
throw new ForceQuitActionNotAllowedException(
|
||||
"Action [id: " + action.getId() + "] is not active and cannot be force quit");
|
||||
}
|
||||
|
||||
LOG.warn("action ({}) was still activ and has been force quite.", action);
|
||||
|
||||
// document that the status has been retrieved
|
||||
actionStatusRepository.save(new ActionStatus(mergedAction, Status.CANCELED, System.currentTimeMillis(),
|
||||
"A force quit has been performed."));
|
||||
|
||||
DeploymentHelper.successCancellation(mergedAction, actionRepository, targetManagement, targetInfoRepository,
|
||||
entityManager);
|
||||
|
||||
return actionRepository.save(mergedAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
public void createScheduledAction(final List<Target> targets, final DistributionSet distributionSet,
|
||||
final ActionType actionType, final long forcedTime, final Rollout rollout,
|
||||
final RolloutGroup rolloutGroup) {
|
||||
// cancel all current scheduled actions for this target. E.g. an action
|
||||
// is already scheduled and a next action is created then cancel the
|
||||
// current scheduled action to cancel. E.g. a new scheduled action is
|
||||
// created.
|
||||
final List<Long> targetIds = targets.stream().map(t -> t.getId()).collect(Collectors.toList());
|
||||
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
|
||||
targets.forEach(target -> {
|
||||
final Action action = new Action();
|
||||
action.setTarget(target);
|
||||
action.setActive(false);
|
||||
action.setDistributionSet(distributionSet);
|
||||
action.setActionType(actionType);
|
||||
action.setForcedTime(forcedTime);
|
||||
action.setStatus(Status.SCHEDULED);
|
||||
action.setRollout(rollout);
|
||||
action.setRolloutGroup(rolloutGroup);
|
||||
actionRepository.save(action);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
public Action startScheduledAction(final Action action) {
|
||||
|
||||
final Action mergedAction = entityManager.merge(action);
|
||||
final Target mergedTarget = entityManager.merge(action.getTarget());
|
||||
|
||||
// check if we need to override running update actions
|
||||
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
|
||||
Collections.singletonList(action.getTarget().getId()));
|
||||
|
||||
final boolean hasDistributionSetAlreadyAssigned = targetRepository
|
||||
.count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(
|
||||
Collections.singletonList(mergedTarget.getControllerId()),
|
||||
action.getDistributionSet().getId())) == 0;
|
||||
if (hasDistributionSetAlreadyAssigned) {
|
||||
// the target has already the distribution set assigned, we don't
|
||||
// need to start the scheduled action, just finished it.
|
||||
mergedAction.setStatus(Status.FINISHED);
|
||||
mergedAction.setActive(false);
|
||||
return actionRepository.save(mergedAction);
|
||||
}
|
||||
|
||||
mergedAction.setActive(true);
|
||||
mergedAction.setStatus(Status.RUNNING);
|
||||
final Action savedAction = actionRepository.save(mergedAction);
|
||||
|
||||
final ActionStatus actionStatus = new ActionStatus();
|
||||
actionStatus.setAction(action);
|
||||
actionStatus.setOccurredAt(action.getCreatedAt());
|
||||
actionStatus.setStatus(Status.RUNNING);
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
mergedTarget.setAssignedDistributionSet(action.getDistributionSet());
|
||||
final TargetInfo targetInfo = mergedTarget.getTargetInfo();
|
||||
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
targetRepository.save(mergedTarget);
|
||||
targetInfoRepository.save(targetInfo);
|
||||
|
||||
// in case we canceled an action before for this target, then don't fire
|
||||
// assignment event
|
||||
if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) {
|
||||
final List<SoftwareModule> softwareModules = softwareModuleRepository
|
||||
.findByAssignedTo(action.getDistributionSet());
|
||||
// send distribution set assignment event
|
||||
|
||||
assignDistributionSetEvent(mergedAction.getTarget(), mergedAction.getId(), softwareModules);
|
||||
}
|
||||
return savedAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action findAction(final Long actionId) {
|
||||
return actionRepository.findOne(actionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action findActionWithDetails(final Long actionId) {
|
||||
return actionRepository.findById(actionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Action> findActionsByTarget(final Pageable pageable, final Target target) {
|
||||
return actionRepository.findByTarget(pageable, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActionsByTarget(final Target target) {
|
||||
return actionRepository.findByTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActionWithStatusCount> findActionsWithStatusCountByTargetOrderByIdDesc(final Target target) {
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<ActionWithStatusCount> query = cb.createQuery(ActionWithStatusCount.class);
|
||||
final Root<Action> actionRoot = query.from(Action.class);
|
||||
final ListJoin<Action, ActionStatus> actionStatusJoin = actionRoot.join(Action_.actionStatus, JoinType.LEFT);
|
||||
final Join<Action, DistributionSet> actionDsJoin = actionRoot.join(Action_.distributionSet);
|
||||
final Join<Action, Rollout> actionRolloutJoin = actionRoot.join(Action_.rollout, JoinType.LEFT);
|
||||
|
||||
final CriteriaQuery<ActionWithStatusCount> multiselect = query.distinct(true).multiselect(
|
||||
actionRoot.get(Action_.id), actionRoot.get(Action_.actionType), actionRoot.get(Action_.active),
|
||||
actionRoot.get(Action_.forcedTime), actionRoot.get(Action_.status), actionRoot.get(Action_.createdAt),
|
||||
actionRoot.get(Action_.lastModifiedAt), actionDsJoin.get(DistributionSet_.id),
|
||||
actionDsJoin.get(DistributionSet_.name), actionDsJoin.get(DistributionSet_.version),
|
||||
cb.count(actionStatusJoin), actionRolloutJoin.get(Rollout_.name));
|
||||
multiselect.where(cb.equal(actionRoot.get(Action_.target), target));
|
||||
multiselect.orderBy(cb.desc(actionRoot.get(Action_.id)));
|
||||
multiselect.groupBy(actionRoot.get(Action_.id));
|
||||
return entityManager.createQuery(multiselect).getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Action> findActionsByTarget(final Specification<Action> specifiction, final Target target,
|
||||
final Pageable pageable) {
|
||||
|
||||
return actionRepository.findAll((Specification<Action>) (root, query, cb) -> cb
|
||||
.and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Action> findActionsByTarget(final Target foundTarget, final Pageable pageable) {
|
||||
return actionRepository.findByTarget(pageable, foundTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final Target target) {
|
||||
return actionRepository.findByActiveAndTarget(pageable, target, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActiveActionsByTarget(final Target target) {
|
||||
return actionRepository.findByActiveAndTarget(target, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findInActiveActionsByTarget(final Target target) {
|
||||
return actionRepository.findByActiveAndTarget(target, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Action> findInActiveActionsByTarget(final Pageable pageable, final Target target) {
|
||||
return actionRepository.findByActiveAndTarget(pageable, target, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countActionsByTarget(final Target target) {
|
||||
return actionRepository.countByTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countActionsByTarget(final Specification<Action> spec, final Target target) {
|
||||
return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb),
|
||||
cb.equal(root.get(Action_.target), target)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public Action forceTargetAction(final Long actionId) {
|
||||
final Action action = actionRepository.findOne(actionId);
|
||||
if (action != null && !action.isForced()) {
|
||||
action.setActionType(ActionType.FORCED);
|
||||
return actionRepository.save(action);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ActionStatus> findActionStatusByAction(final Pageable pageReq, final Action action,
|
||||
final boolean withMessages) {
|
||||
if (withMessages) {
|
||||
return actionStatusRepository.getByAction(pageReq, action);
|
||||
} else {
|
||||
return actionStatusRepository.findByAction(pageReq, action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
|
||||
final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) {
|
||||
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus(rollout, rolloutGroupParent, actionStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
|
||||
return actionRepository.findByRolloutAndStatus(rollout, actionStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* 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 com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagAssigmentResultEvent;
|
||||
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetFilter;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetFilter.DistributionSetFilterBuilder;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityLockedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata_;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.specifications.DistributionSetSpecification;
|
||||
import org.eclipse.hawkbit.repository.specifications.DistributionSetTypeSpecification;
|
||||
import org.eclipse.hawkbit.repository.specifications.SpecificationsBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
/**
|
||||
* Business facade for managing the {@link DistributionSet}s.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaDistributionSetManagement implements DistributionSetManagement {
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private DistributionSetRepository distributionSetRepository;
|
||||
|
||||
@Autowired
|
||||
private TagManagement tagManagement;
|
||||
|
||||
@Autowired
|
||||
private SystemManagement systemManagement;
|
||||
|
||||
@Autowired
|
||||
private DistributionSetTypeRepository distributionSetTypeRepository;
|
||||
|
||||
@Autowired
|
||||
private DistributionSetMetadataRepository distributionSetMetadataRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private EventBus eventBus;
|
||||
|
||||
@Autowired
|
||||
private AfterTransactionCommitExecutor afterCommit;
|
||||
|
||||
@Override
|
||||
public DistributionSet findDistributionSetByIdWithDetails(final Long distid) {
|
||||
return distributionSetRepository.findOne(DistributionSetSpecification.byId(distid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSet findDistributionSetById(final Long distid) {
|
||||
return distributionSetRepository.findOne(distid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection<Long> dsIds, final String tagName) {
|
||||
|
||||
final Iterable<DistributionSet> sets = findDistributionSetListWithDetails(dsIds);
|
||||
final DistributionSetTag myTag = tagManagement.findDistributionSetTag(tagName);
|
||||
|
||||
DistributionSetTagAssignmentResult result;
|
||||
final List<DistributionSet> toBeChangedDSs = new ArrayList<>();
|
||||
for (final DistributionSet set : sets) {
|
||||
if (set.getTags().add(myTag)) {
|
||||
toBeChangedDSs.add(set);
|
||||
}
|
||||
}
|
||||
|
||||
// un-assignment case
|
||||
if (toBeChangedDSs.isEmpty()) {
|
||||
for (final DistributionSet set : sets) {
|
||||
if (set.getTags().remove(myTag)) {
|
||||
toBeChangedDSs.add(set);
|
||||
}
|
||||
}
|
||||
result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), 0,
|
||||
toBeChangedDSs.size(), Collections.emptyList(), distributionSetRepository.save(toBeChangedDSs),
|
||||
myTag);
|
||||
} else {
|
||||
result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), toBeChangedDSs.size(),
|
||||
0, distributionSetRepository.save(toBeChangedDSs), Collections.emptyList(), myTag);
|
||||
}
|
||||
|
||||
final DistributionSetTagAssignmentResult resultAssignment = result;
|
||||
afterCommit.afterCommit(() -> eventBus.post(new DistributionSetTagAssigmentResultEvent(resultAssignment)));
|
||||
|
||||
// no reason to persist the tag
|
||||
entityManager.detach(myTag);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DistributionSet> findDistributionSetListWithDetails(final Collection<Long> distributionIdSet) {
|
||||
return distributionSetRepository.findAll(DistributionSetSpecification.byIds(distributionIdSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSet updateDistributionSet(final DistributionSet ds) {
|
||||
checkNotNull(ds.getId());
|
||||
final DistributionSet persisted = findDistributionSetByIdWithDetails(ds.getId());
|
||||
checkDistributionSetSoftwareModulesIsAllowedToModify(ds, persisted.getModules());
|
||||
return distributionSetRepository.save(ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void deleteDistributionSet(final Long... distributionSetIDs) {
|
||||
final List<Long> toHardDelete = new ArrayList<>();
|
||||
|
||||
final List<Long> assigned = distributionSetRepository.findAssignedDistributionSetsById(distributionSetIDs);
|
||||
|
||||
// soft delete assigned
|
||||
if (!assigned.isEmpty()) {
|
||||
distributionSetRepository.deleteDistributionSet(assigned.toArray(new Long[assigned.size()]));
|
||||
}
|
||||
|
||||
// mark the rest as hard delete
|
||||
for (final Long setId : distributionSetIDs) {
|
||||
if (!assigned.contains(setId)) {
|
||||
toHardDelete.add(setId);
|
||||
}
|
||||
}
|
||||
|
||||
// hard delete the rest if exixts
|
||||
if (!toHardDelete.isEmpty()) {
|
||||
// don't give the delete statement an empty list, JPA/Oracle cannot
|
||||
// handle the empty list
|
||||
distributionSetRepository.deleteByIdIn(toHardDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSet createDistributionSet(final DistributionSet dSet) {
|
||||
prepareDsSave(dSet);
|
||||
if (dSet.getType() == null) {
|
||||
dSet.setType(systemManagement.getTenantMetadata().getDefaultDsType());
|
||||
}
|
||||
return distributionSetRepository.save(dSet);
|
||||
}
|
||||
|
||||
private void prepareDsSave(final DistributionSet dSet) {
|
||||
if (dSet.getId() != null) {
|
||||
throw new EntityAlreadyExistsException("Parameter seems to be an existing, already persisted entity");
|
||||
}
|
||||
|
||||
if (distributionSetRepository.countByNameAndVersion(dSet.getName(), dSet.getVersion()) > 0) {
|
||||
throw new EntityAlreadyExistsException("DistributionSet with that name and version already exists.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public List<DistributionSet> createDistributionSets(final Collection<DistributionSet> distributionSets) {
|
||||
for (final DistributionSet ds : distributionSets) {
|
||||
prepareDsSave(ds);
|
||||
if (ds.getType() == null) {
|
||||
ds.setType(systemManagement.getTenantMetadata().getDefaultDsType());
|
||||
}
|
||||
}
|
||||
return distributionSetRepository.save(distributionSets);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSet assignSoftwareModules(final DistributionSet ds, final Set<SoftwareModule> softwareModules) {
|
||||
checkDistributionSetSoftwareModulesIsAllowedToModify(ds, softwareModules);
|
||||
for (final SoftwareModule softwareModule : softwareModules) {
|
||||
ds.addModule(softwareModule);
|
||||
}
|
||||
return distributionSetRepository.save(ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSet unassignSoftwareModule(final DistributionSet ds, final SoftwareModule softwareModule) {
|
||||
final Set<SoftwareModule> softwareModules = new HashSet<>();
|
||||
softwareModules.add(softwareModule);
|
||||
ds.removeModule(softwareModule);
|
||||
checkDistributionSetSoftwareModulesIsAllowedToModify(ds, softwareModules);
|
||||
return distributionSetRepository.save(ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSetType updateDistributionSetType(final DistributionSetType dsType) {
|
||||
checkNotNull(dsType.getId());
|
||||
|
||||
final DistributionSetType persisted = distributionSetTypeRepository.findOne(dsType.getId());
|
||||
|
||||
// throw exception if user tries to update a DS type that is already in
|
||||
// use
|
||||
if (!persisted.areModuleEntriesIdentical(dsType) && distributionSetRepository.countByType(persisted) > 0) {
|
||||
throw new EntityReadOnlyException(
|
||||
String.format("distribution set type %s set is already assigned to targets and cannot be changed",
|
||||
dsType.getName()));
|
||||
}
|
||||
|
||||
return distributionSetTypeRepository.save(dsType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSetType> findDistributionSetTypesAll(final Specification<DistributionSetType> spec,
|
||||
final Pageable pageable) {
|
||||
return distributionSetTypeRepository.findAll(spec, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSetType> findDistributionSetTypesAll(final Pageable pageable) {
|
||||
return distributionSetTypeRepository.findByDeleted(pageable, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSet> findDistributionSetsByFilters(final Pageable pageable,
|
||||
final DistributionSetFilter distributionSetFilter) {
|
||||
final List<Specification<DistributionSet>> specList = buildDistributionSetSpecifications(distributionSetFilter);
|
||||
return findByCriteriaAPI(pageable, specList);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param distributionSetFilter
|
||||
* had details of filters to be applied
|
||||
* @return a single DistributionSet which is either installed or assigned to
|
||||
* a specific target or {@code null}.
|
||||
*/
|
||||
private DistributionSet findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
|
||||
final DistributionSetFilter distributionSetFilter) {
|
||||
final List<Specification<DistributionSet>> specList = buildDistributionSetSpecifications(distributionSetFilter);
|
||||
if (specList == null || specList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return distributionSetRepository.findOne(SpecificationsBuilder.combineWithAnd(specList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSet> findDistributionSetsAll(final Pageable pageReq, final Boolean deleted,
|
||||
final Boolean complete) {
|
||||
final List<Specification<DistributionSet>> specList = new ArrayList<>();
|
||||
|
||||
if (deleted != null) {
|
||||
final Specification<DistributionSet> spec = DistributionSetSpecification.isDeleted(deleted);
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
if (complete != null) {
|
||||
final Specification<DistributionSet> spec = DistributionSetSpecification.isCompleted(complete);
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
return findByCriteriaAPI(pageReq, specList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSet> findDistributionSetsAll(final Specification<DistributionSet> spec,
|
||||
final Pageable pageReq, final Boolean deleted) {
|
||||
final List<Specification<DistributionSet>> specList = new ArrayList<>();
|
||||
if (deleted != null) {
|
||||
specList.add(DistributionSetSpecification.isDeleted(deleted));
|
||||
}
|
||||
specList.add(spec);
|
||||
return findByCriteriaAPI(pageReq, specList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSet> findDistributionSetsAllOrderedByLinkTarget(final Pageable pageable,
|
||||
final DistributionSetFilterBuilder distributionSetFilterBuilder, final String assignedOrInstalled) {
|
||||
|
||||
final DistributionSetFilter filterWithInstalledTargets = distributionSetFilterBuilder
|
||||
.setInstalledTargetId(assignedOrInstalled).setAssignedTargetId(null).build();
|
||||
final DistributionSet installedDS = findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
|
||||
filterWithInstalledTargets);
|
||||
|
||||
final DistributionSetFilter filterWithAssignedTargets = distributionSetFilterBuilder.setInstalledTargetId(null)
|
||||
.setAssignedTargetId(assignedOrInstalled).build();
|
||||
final DistributionSet assignedDS = findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
|
||||
filterWithAssignedTargets);
|
||||
|
||||
final DistributionSetFilter dsFilterWithNoTargetLinked = distributionSetFilterBuilder.setInstalledTargetId(null)
|
||||
.setAssignedTargetId(null).build();
|
||||
// first fine the distribution sets filtered by the given filter
|
||||
// parameters
|
||||
final Page<DistributionSet> findDistributionSetsByFilters = findDistributionSetsByFilters(pageable,
|
||||
dsFilterWithNoTargetLinked);
|
||||
|
||||
final List<DistributionSet> resultSet = new LinkedList<>(findDistributionSetsByFilters.getContent());
|
||||
int orderIndex = 0;
|
||||
if (installedDS != null) {
|
||||
final boolean remove = resultSet.remove(installedDS);
|
||||
if (!remove) {
|
||||
resultSet.remove(resultSet.size() - 1);
|
||||
}
|
||||
resultSet.add(orderIndex, installedDS);
|
||||
orderIndex++;
|
||||
}
|
||||
if (assignedDS != null && !assignedDS.equals(installedDS)) {
|
||||
final boolean remove = resultSet.remove(assignedDS);
|
||||
if (!remove) {
|
||||
resultSet.remove(resultSet.size() - 1);
|
||||
}
|
||||
resultSet.add(orderIndex, assignedDS);
|
||||
}
|
||||
|
||||
return new PageImpl<>(resultSet, pageable, findDistributionSetsByFilters.getTotalElements());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSet findDistributionSetByNameAndVersion(final String distributionName, final String version) {
|
||||
final Specification<DistributionSet> spec = DistributionSetSpecification
|
||||
.equalsNameAndVersionIgnoreCase(distributionName, version);
|
||||
return distributionSetRepository.findOne(spec);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<DistributionSet> findDistributionSetList(final Collection<Long> dist) {
|
||||
return distributionSetRepository.findAll(dist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countDistributionSetsAll() {
|
||||
|
||||
final List<Specification<DistributionSet>> specList = new ArrayList<>();
|
||||
|
||||
final Specification<DistributionSet> spec = DistributionSetSpecification.isDeleted(Boolean.FALSE);
|
||||
specList.add(spec);
|
||||
|
||||
return distributionSetRepository.count(SpecificationsBuilder.combineWithAnd(specList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countDistributionSetTypesAll() {
|
||||
return distributionSetTypeRepository.countByDeleted(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSetType findDistributionSetTypeByName(final String name) {
|
||||
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byName(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSetType findDistributionSetTypeById(final Long id) {
|
||||
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byId(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSetType findDistributionSetTypeByKey(final String key) {
|
||||
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSetType createDistributionSetType(final DistributionSetType type) {
|
||||
if (type.getId() != null) {
|
||||
throw new EntityAlreadyExistsException("Given type contains an Id!");
|
||||
}
|
||||
|
||||
return distributionSetTypeRepository.save(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public void deleteDistributionSetType(final DistributionSetType type) {
|
||||
|
||||
if (distributionSetRepository.countByType(type) > 0) {
|
||||
final DistributionSetType toDelete = entityManager.merge(type);
|
||||
toDelete.setDeleted(true);
|
||||
distributionSetTypeRepository.save(toDelete);
|
||||
} else {
|
||||
distributionSetTypeRepository.delete(type.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public DistributionSetMetadata createDistributionSetMetadata(final DistributionSetMetadata metadata) {
|
||||
if (distributionSetMetadataRepository.exists(metadata.getId())) {
|
||||
throwMetadataKeyAlreadyExists(metadata.getId().getKey());
|
||||
}
|
||||
// merge base distribution set so optLockRevision gets updated and audit
|
||||
// log written because
|
||||
// modifying metadata is modifying the base distribution set itself for
|
||||
// auditing purposes.
|
||||
entityManager.merge(metadata.getDistributionSet()).setLastModifiedAt(0L);
|
||||
return distributionSetMetadataRepository.save(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public List<DistributionSetMetadata> createDistributionSetMetadata(
|
||||
final Collection<DistributionSetMetadata> metadata) {
|
||||
for (final DistributionSetMetadata distributionSetMetadata : metadata) {
|
||||
checkAndThrowAlreadyIfDistributionSetMetadataExists(distributionSetMetadata.getId());
|
||||
}
|
||||
metadata.forEach(m -> entityManager.merge(m.getDistributionSet()).setLastModifiedAt(-1L));
|
||||
return (List<DistributionSetMetadata>) distributionSetMetadataRepository.save(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public DistributionSetMetadata updateDistributionSetMetadata(final DistributionSetMetadata metadata) {
|
||||
// check if exists otherwise throw entity not found exception
|
||||
findOne(metadata.getId());
|
||||
// touch it to update the lock revision because we are modifying the
|
||||
// DS indirectly
|
||||
entityManager.merge(metadata.getDistributionSet()).setLastModifiedAt(0L);
|
||||
return distributionSetMetadataRepository.save(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void deleteDistributionSetMetadata(final DsMetadataCompositeKey id) {
|
||||
distributionSetMetadataRepository.delete(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSetMetadata> findDistributionSetMetadataByDistributionSetId(final Long distributionSetId,
|
||||
final Pageable pageable) {
|
||||
|
||||
return distributionSetMetadataRepository.findAll(
|
||||
(Specification<DistributionSetMetadata>) (root, query, cb) -> cb.equal(
|
||||
root.get(DistributionSetMetadata_.distributionSet).get(DistributionSet_.id), distributionSetId),
|
||||
pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DistributionSetMetadata> findDistributionSetMetadataByDistributionSetId(final Long distributionSetId,
|
||||
final Specification<DistributionSetMetadata> spec, final Pageable pageable) {
|
||||
return distributionSetMetadataRepository
|
||||
.findAll(
|
||||
(Specification<DistributionSetMetadata>) (root, query,
|
||||
cb) -> cb.and(
|
||||
cb.equal(root.get(DistributionSetMetadata_.distributionSet)
|
||||
.get(DistributionSet_.id), distributionSetId),
|
||||
spec.toPredicate(root, query, cb)),
|
||||
pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSetMetadata findOne(final DsMetadataCompositeKey id) {
|
||||
final DistributionSetMetadata findOne = distributionSetMetadataRepository.findOne(id);
|
||||
if (findOne == null) {
|
||||
throw new EntityNotFoundException("Metadata with key '" + id.getKey() + "' does not exist");
|
||||
}
|
||||
return findOne;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DistributionSet findDistributionSetByAction(final Action action) {
|
||||
return distributionSetRepository.findByAction(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDistributionSetInUse(final DistributionSet distributionSet) {
|
||||
return actionRepository.countByDistributionSet(distributionSet) > 0;
|
||||
}
|
||||
|
||||
private static List<Specification<DistributionSet>> buildDistributionSetSpecifications(
|
||||
final DistributionSetFilter distributionSetFilter) {
|
||||
final List<Specification<DistributionSet>> specList = new ArrayList<>();
|
||||
|
||||
Specification<DistributionSet> spec;
|
||||
|
||||
if (null != distributionSetFilter.getIsComplete()) {
|
||||
spec = DistributionSetSpecification.isCompleted(distributionSetFilter.getIsComplete());
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
if (null != distributionSetFilter.getIsDeleted()) {
|
||||
spec = DistributionSetSpecification.isDeleted(distributionSetFilter.getIsDeleted());
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
if (distributionSetFilter.getType() != null) {
|
||||
spec = DistributionSetSpecification.byType(distributionSetFilter.getType());
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
if (!Strings.isNullOrEmpty(distributionSetFilter.getSearchText())) {
|
||||
spec = DistributionSetSpecification.likeNameOrDescriptionOrVersion(distributionSetFilter.getSearchText());
|
||||
specList.add(spec);
|
||||
}
|
||||
|
||||
if (isDSWithNoTagSelected(distributionSetFilter) || isTagsSelected(distributionSetFilter)) {
|
||||
spec = DistributionSetSpecification.hasTags(distributionSetFilter.getTagNames(),
|
||||
distributionSetFilter.getSelectDSWithNoTag());
|
||||
specList.add(spec);
|
||||
}
|
||||
if (distributionSetFilter.getInstalledTargetId() != null) {
|
||||
spec = DistributionSetSpecification.installedTarget(distributionSetFilter.getInstalledTargetId());
|
||||
specList.add(spec);
|
||||
}
|
||||
if (distributionSetFilter.getAssignedTargetId() != null) {
|
||||
spec = DistributionSetSpecification.assignedTarget(distributionSetFilter.getAssignedTargetId());
|
||||
specList.add(spec);
|
||||
}
|
||||
return specList;
|
||||
}
|
||||
|
||||
private void checkDistributionSetSoftwareModulesIsAllowedToModify(final DistributionSet distributionSet,
|
||||
final Set<SoftwareModule> softwareModules) {
|
||||
if (!new HashSet<SoftwareModule>(distributionSet.getModules()).equals(softwareModules)
|
||||
&& actionRepository.countByDistributionSet(distributionSet) > 0) {
|
||||
throw new EntityLockedException(
|
||||
String.format("distribution set %s:%s is already assigned to targets and cannot be changed",
|
||||
distributionSet.getName(), distributionSet.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean isDSWithNoTagSelected(final DistributionSetFilter distributionSetFilter) {
|
||||
if (distributionSetFilter.getSelectDSWithNoTag() != null && distributionSetFilter.getSelectDSWithNoTag()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Boolean isTagsSelected(final DistributionSetFilter distributionSetFilter) {
|
||||
if (distributionSetFilter.getTagNames() != null && !distributionSetFilter.getTagNames().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* executes findAll with the given {@link DistributionSet}
|
||||
* {@link Specification}s.
|
||||
*
|
||||
* @param pageable
|
||||
* paging parameter
|
||||
* @param specList
|
||||
* list of @link {@link Specification}
|
||||
* @return the page with the found {@link DistributionSet}
|
||||
*/
|
||||
private Page<DistributionSet> findByCriteriaAPI(final Pageable pageable,
|
||||
final List<Specification<DistributionSet>> specList) {
|
||||
|
||||
if (specList == null || specList.isEmpty()) {
|
||||
return distributionSetRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
return distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
|
||||
}
|
||||
|
||||
private void checkAndThrowAlreadyIfDistributionSetMetadataExists(final DsMetadataCompositeKey metadataId) {
|
||||
if (distributionSetMetadataRepository.exists(metadataId)) {
|
||||
throw new EntityAlreadyExistsException(
|
||||
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwMetadataKeyAlreadyExists(final String metadataKey) {
|
||||
throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public List<DistributionSet> assignTag(final Collection<Long> dsIds, final DistributionSetTag tag) {
|
||||
final List<DistributionSet> allDs = findDistributionSetListWithDetails(dsIds);
|
||||
|
||||
allDs.forEach(ds -> ds.getTags().add(tag));
|
||||
final List<DistributionSet> save = distributionSetRepository.save(allDs);
|
||||
|
||||
afterCommit.afterCommit(() -> {
|
||||
|
||||
final DistributionSetTagAssignmentResult result = new DistributionSetTagAssignmentResult(0, save.size(), 0,
|
||||
save, Collections.emptyList(), tag);
|
||||
eventBus.post(new DistributionSetTagAssigmentResultEvent(result));
|
||||
});
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public List<DistributionSet> unAssignAllDistributionSetsByTag(final DistributionSetTag tag) {
|
||||
return unAssignTag(tag.getAssignedToDistributionSet(), tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
public DistributionSet unAssignTag(final Long dsId, final DistributionSetTag distributionSetTag) {
|
||||
final List<DistributionSet> allDs = findDistributionSetListWithDetails(Arrays.asList(dsId));
|
||||
final List<DistributionSet> unAssignTag = unAssignTag(allDs, distributionSetTag);
|
||||
return unAssignTag.isEmpty() ? null : unAssignTag.get(0);
|
||||
}
|
||||
|
||||
private List<DistributionSet> unAssignTag(final Collection<DistributionSet> distributionSets,
|
||||
final DistributionSetTag tag) {
|
||||
distributionSets.forEach(ds -> ds.getTags().remove(tag));
|
||||
return distributionSetRepository.save(distributionSets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* 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 java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.eclipse.hawkbit.report.model.DataReportSeries;
|
||||
import org.eclipse.hawkbit.report.model.DataReportSeriesItem;
|
||||
import org.eclipse.hawkbit.report.model.InnerOuterDataReportSeries;
|
||||
import org.eclipse.hawkbit.report.model.SeriesTime;
|
||||
import org.eclipse.hawkbit.repository.ReportManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo;
|
||||
import org.eclipse.hawkbit.repository.model.TargetInfo_;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Service layer for generating hawkBit reports.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaReportManagement implements ReportManagement {
|
||||
|
||||
@Value("${spring.jpa.database}")
|
||||
private String databaseType;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
private static final String H2_TARGET_CREATED_SQL_TEMPLATE = "SELECT TO_CHAR( DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(target0_.controller_id) AS col_1_0_ from sp_target target0_ WHERE TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'),'%s') BETWEEN TO_CHAR('%s', '%s') and TO_CHAR('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s')";
|
||||
|
||||
private static final String H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(action_.id) AS col_1_0_ FROM sp_action action_ WHERE TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') BETWEEN TO_CHAR('%s', '%s') AND TO_CHAR('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s')";
|
||||
|
||||
private static final String MYSQL_TARGET_CREATED_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s') AS col_0_0_, COUNT(target0_.controller_id) AS col_1_0_ FROM sp_target target0_ WHERE DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s')";
|
||||
|
||||
private static final String MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s') AS col_0_0_, COUNT(action_.id) as col_1_0_ FROM sp_action action_ WHERE DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s')";
|
||||
|
||||
private static final String MYSQL_DB_TYPE = "MYSQL";
|
||||
|
||||
private static final String H2_DB_TYPE = "H2";
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private TenantAware tenantAware;
|
||||
|
||||
@Override
|
||||
@Cacheable("targetStatus")
|
||||
public DataReportSeries<TargetUpdateStatus> targetStatus() {
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
|
||||
final Root<Target> targetRoot = query.from(Target.class);
|
||||
final Join<Target, TargetInfo> targetInfo = targetRoot.join(Target_.targetInfo);
|
||||
final Expression<Long> countColumn = cb.count(targetInfo.get(TargetInfo_.targetId));
|
||||
final CriteriaQuery<Object[]> multiselect = query
|
||||
.multiselect(targetInfo.get(TargetInfo_.updateStatus), countColumn)
|
||||
.groupBy(targetInfo.get(TargetInfo_.updateStatus))
|
||||
.orderBy(cb.desc(targetInfo.get(TargetInfo_.updateStatus)));
|
||||
|
||||
// | col1 | col2 |
|
||||
// | U_STATUS | COUNT |
|
||||
final List<Object[]> resultList = entityManager.createQuery(multiselect).getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<TargetUpdateStatus>> reportSeriesItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<TargetUpdateStatus>((TargetUpdateStatus) r[0], (Long) r[1]))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("Target Status Overview", reportSeriesItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("distributionUsageAssigned")
|
||||
public List<InnerOuterDataReportSeries<String>> distributionUsageAssigned(final int topXEntries) {
|
||||
|
||||
// top X entries distribution usage
|
||||
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
|
||||
final Root<DistributionSet> rootTopX = queryTopX.from(DistributionSet.class);
|
||||
final ListJoin<DistributionSet, Target> joinTopX = rootTopX.join(DistributionSet_.assignedToTargets,
|
||||
JoinType.LEFT);
|
||||
final Expression<Long> countColumn = cbTopX.count(joinTopX);
|
||||
// top x usage query
|
||||
final CriteriaQuery<Object[]> groupBy = queryTopX
|
||||
.multiselect(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version), countColumn)
|
||||
.where(cbTopX.equal(rootTopX.get(DistributionSet_.deleted), false))
|
||||
.groupBy(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version))
|
||||
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(DistributionSet_.name)));
|
||||
// | col1 | col2 | col3 |
|
||||
// | NAME | VER | COUNT |
|
||||
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
|
||||
// end of top X entries distribution usage
|
||||
|
||||
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("distributionUsageInstalled")
|
||||
public List<InnerOuterDataReportSeries<String>> distributionUsageInstalled(final int topXEntries) {
|
||||
// top X entries distribution usage
|
||||
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
|
||||
final Root<DistributionSet> rootTopX = queryTopX.from(DistributionSet.class);
|
||||
final ListJoin<DistributionSet, TargetInfo> joinTopX = rootTopX.join(DistributionSet_.installedAtTargets,
|
||||
JoinType.LEFT);
|
||||
final Expression<Long> countColumn = cbTopX.count(joinTopX);
|
||||
// top x usage query
|
||||
final CriteriaQuery<Object[]> groupBy = queryTopX
|
||||
.multiselect(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version), countColumn)
|
||||
.where(cbTopX.equal(rootTopX.get(DistributionSet_.deleted), false))
|
||||
.groupBy(rootTopX.get(DistributionSet_.name), rootTopX.get(DistributionSet_.version))
|
||||
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(DistributionSet_.name)));
|
||||
// | col1 | col2 | col3 |
|
||||
// | NAME | VER | COUNT |
|
||||
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
|
||||
// end of top X entries distribution usage
|
||||
|
||||
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("targetsCreatedOverPeriod")
|
||||
public <T extends Serializable> DataReportSeries<T> targetsCreatedOverPeriod(final DateType<T> dateType,
|
||||
final LocalDateTime from, final LocalDateTime to) {
|
||||
final Query createNativeQuery = entityManager
|
||||
.createNativeQuery(getTargetsCreatedQueryTemplate(dateType, from, to));
|
||||
final List<Object[]> resultList = createNativeQuery.getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("CreatedTargets", reportItems);
|
||||
}
|
||||
|
||||
private String getTargetsCreatedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return String.format(H2_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
|
||||
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
case MYSQL_DB_TYPE:
|
||||
return String.format(MYSQL_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
|
||||
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getFeedbackReceivedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return String.format(H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
|
||||
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
case MYSQL_DB_TYPE:
|
||||
return String.format(MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
|
||||
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
|
||||
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
|
||||
dateTimeFormatToSqlFormat(dateType));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("feedbackReceivedOverTime")
|
||||
public <T extends Serializable> DataReportSeries<T> feedbackReceivedOverTime(final DateType<T> dateType,
|
||||
final LocalDateTime from, final LocalDateTime to) {
|
||||
final Query createNativeQuery = entityManager
|
||||
.createNativeQuery(getFeedbackReceivedQueryTemplate(dateType, from, to));
|
||||
final List<Object[]> resultList = createNativeQuery.getResultList();
|
||||
|
||||
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
|
||||
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DataReportSeries<>("FeedbackRecieved", reportItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable("targetsLastPoll")
|
||||
public DataReportSeries<SeriesTime> targetsLastPoll() {
|
||||
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
final LocalDateTime beforeHour = now.minusHours(1);
|
||||
final LocalDateTime beforeDay = now.minusDays(1);
|
||||
final LocalDateTime beforeWeek = now.minusWeeks(1);
|
||||
final LocalDateTime beforeMonth = now.minusMonths(1);
|
||||
final LocalDateTime beforeYear = now.minusYears(1);
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final List<DataReportSeriesItem<SeriesTime>> resultList = new ArrayList<>();
|
||||
|
||||
// hours
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.HOUR,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, beforeHour, now)).getSingleResult()));
|
||||
// days
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.DAY, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeDay, beforeHour)).getSingleResult()));
|
||||
// weeks
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.WEEK, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeWeek, beforeDay)).getSingleResult()));
|
||||
// months
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MONTH, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeMonth, beforeWeek)).getSingleResult()));
|
||||
// years
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.YEAR, entityManager
|
||||
.createQuery(createCountSelectTargetsLastPoll(cb, beforeYear, beforeMonth)).getSingleResult()));
|
||||
// years
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MORE_THAN_YEAR,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, beforeYear)).getSingleResult()));
|
||||
// never
|
||||
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.NEVER,
|
||||
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, null)).getSingleResult()));
|
||||
|
||||
return new DataReportSeries<>("TargetLastPoll", resultList);
|
||||
}
|
||||
|
||||
private CriteriaQuery<Long> createCountSelectTargetsLastPoll(final CriteriaBuilder cb, final LocalDateTime from,
|
||||
final LocalDateTime to) {
|
||||
|
||||
Long start = null;
|
||||
Long end = null;
|
||||
if (from != null) {
|
||||
start = from.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
if (to != null) {
|
||||
end = to.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
// count select statement
|
||||
final CriteriaQuery<Long> countSelect = cb.createQuery(Long.class);
|
||||
final Root<Target> countSelectRoot = countSelect.from(Target.class);
|
||||
final Join<Target, TargetInfo> targetInfoJoin = countSelectRoot.join(Target_.targetInfo);
|
||||
countSelect.select(cb.count(countSelectRoot));
|
||||
if (start != null && end != null) {
|
||||
countSelect.where(cb.between(targetInfoJoin.get(TargetInfo_.lastTargetQuery), start, end));
|
||||
} else if (from == null && to != null) {
|
||||
countSelect.where(cb.lessThanOrEqualTo(targetInfoJoin.get(TargetInfo_.lastTargetQuery), end));
|
||||
} else {
|
||||
countSelect.where(cb.isNull(targetInfoJoin.get(TargetInfo_.lastTargetQuery)));
|
||||
}
|
||||
return countSelect;
|
||||
}
|
||||
|
||||
private List<InnerOuterDataReportSeries<String>> mapDistirbutionUsageResultToDataReport(final int topXEntries,
|
||||
final List<Object[]> resultListTop) {
|
||||
final List<InnerOuterDataReportSeries<String>> innerOuterReport = new ArrayList<>();
|
||||
final Map<DSName, InnerOuter> map = new LinkedHashMap<>();
|
||||
|
||||
int topXCounter = 0;
|
||||
|
||||
for (final Object[] objects : resultListTop) {
|
||||
|
||||
final boolean containsInnerOuter = map.containsKey(new DSName((String) objects[0]));
|
||||
final String name = containsInnerOuter || topXCounter < topXEntries ? (String) objects[0] : null;
|
||||
final DSName dsName = new DSName(name);
|
||||
final String version = containsInnerOuter || topXCounter < topXEntries ? (String) objects[1] : null;
|
||||
final Long count = (Long) objects[2];
|
||||
|
||||
InnerOuter innerouter = map.get(dsName);
|
||||
if (innerouter == null) {
|
||||
topXCounter++;
|
||||
innerouter = new InnerOuter(dsName);
|
||||
map.put(new DSName(name), innerouter);
|
||||
}
|
||||
innerouter.addOuter(new DSName(version), count);
|
||||
}
|
||||
|
||||
for (final InnerOuter inner : map.values()) {
|
||||
final List<DataReportSeriesItem<String>> outerReportItems = new ArrayList<>();
|
||||
|
||||
if (inner.name.getName() != null) {
|
||||
for (final InnerOuter outer : inner.outer) {
|
||||
outerReportItems.add(outer.toItem());
|
||||
}
|
||||
} else {
|
||||
outerReportItems.add(new DataReportSeriesItem<String>("misc", inner.count));
|
||||
}
|
||||
|
||||
innerOuterReport.add(new InnerOuterDataReportSeries<String>(
|
||||
new DataReportSeries<>("DS-Name", Collections.singletonList(inner.toItem())),
|
||||
new DataReportSeries<>("DS-Version", outerReportItems)));
|
||||
}
|
||||
|
||||
return innerOuterReport;
|
||||
}
|
||||
|
||||
private static final class InnerOuter {
|
||||
final DSName name;
|
||||
long count;
|
||||
final List<InnerOuter> outer;
|
||||
|
||||
private InnerOuter(final DSName idName) {
|
||||
name = idName;
|
||||
outer = new ArrayList<>();
|
||||
}
|
||||
|
||||
private InnerOuter(final DSName idName, final long count) {
|
||||
name = idName;
|
||||
this.count = count;
|
||||
outer = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addOuter(final DSName idName, final long count) {
|
||||
outer.add(new InnerOuter(idName, count));
|
||||
this.count += count;
|
||||
}
|
||||
|
||||
private DataReportSeriesItem<String> toItem() {
|
||||
return new DataReportSeriesItem<>(name.getName() != null ? name.getName() : "misc", count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object contains the name and the id of an entity.
|
||||
*
|
||||
*/
|
||||
private static final class DSName {
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* the ID of an entity
|
||||
* @param name
|
||||
* the name of an entity
|
||||
*/
|
||||
private DSName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
private String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (name == null ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) { // NOSONAR - as this is
|
||||
// generated
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DSName other = (DSName) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DSName [name=" + name + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private String dateTimeFormatToSqlFormat(final DateType<?> datatype) {
|
||||
switch (databaseType) {
|
||||
case H2_DB_TYPE:
|
||||
return datatype.h2Format();
|
||||
case MYSQL_DB_TYPE:
|
||||
return datatype.mySqlFormat();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action_;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup_;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target_;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* RolloutGroupManagement to control rollout groups. This service secures all
|
||||
* the functionality based on the {@link PreAuthorize} annotation on methods.
|
||||
*/
|
||||
@Validated
|
||||
@Service
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
public class JpaRolloutGroupManagement implements RolloutGroupManagement {
|
||||
|
||||
@Autowired
|
||||
private RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) {
|
||||
return rolloutGroupRepository.findOne(rolloutGroupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<RolloutGroup> findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable page) {
|
||||
return rolloutGroupRepository.findByRolloutId(rolloutId, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<RolloutGroup> findRolloutGroupsAll(final Rollout rollout,
|
||||
final Specification<RolloutGroup> specification, final Pageable page) {
|
||||
return rolloutGroupRepository.findAll((root, query, criteriaBuilder) -> criteriaBuilder.and(
|
||||
criteriaBuilder.equal(root.get(RolloutGroup_.rollout), rollout),
|
||||
specification.toPredicate(root, query, criteriaBuilder)), page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable page) {
|
||||
final Page<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, page);
|
||||
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
|
||||
.collect(Collectors.toList());
|
||||
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
|
||||
rolloutGroupIds);
|
||||
|
||||
for (final RolloutGroup rolloutGroup : rolloutGroups) {
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
|
||||
allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets());
|
||||
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
}
|
||||
|
||||
return rolloutGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) {
|
||||
final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId);
|
||||
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
|
||||
.getStatusCountByRolloutGroupId(rolloutGroupId);
|
||||
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
|
||||
rolloutGroup.getTotalTargets());
|
||||
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
return rolloutGroup;
|
||||
|
||||
}
|
||||
|
||||
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(
|
||||
final List<Long> rolloutGroupIds) {
|
||||
final List<TotalTargetCountActionStatus> resultList = actionRepository
|
||||
.getStatusCountByRolloutGroupId(rolloutGroupIds);
|
||||
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup,
|
||||
final Specification<Target> specification, final Pageable page) {
|
||||
return targetRepository.findAll((root, query, criteriaBuilder) -> {
|
||||
final ListJoin<Target, RolloutTargetGroup> rolloutTargetJoin = root.join(Target_.rolloutTargetGroup);
|
||||
return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder),
|
||||
criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
}, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup, final Pageable page) {
|
||||
if (isRolloutStatusReady(rolloutGroup)) {
|
||||
// in case of status ready the action has not been created yet and
|
||||
// the relation information between target and rollout-group is
|
||||
// stored in the #TargetRolloutGroup.
|
||||
return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page);
|
||||
}
|
||||
return targetRepository.findByActionsRolloutGroup(rolloutGroup, page);
|
||||
}
|
||||
|
||||
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
|
||||
return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
|
||||
final RolloutGroup rolloutGroup) {
|
||||
|
||||
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
|
||||
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||
|
||||
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
|
||||
final Join<RolloutTargetGroup, Target> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
|
||||
final ListJoin<RolloutTargetGroup, Action> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
|
||||
JoinType.LEFT);
|
||||
|
||||
final Root<RolloutTargetGroup> countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class);
|
||||
countQueryFrom.join(RolloutTargetGroup_.target);
|
||||
countQueryFrom.join(RolloutTargetGroup_.actions, JoinType.LEFT);
|
||||
countQuery.select(cb.count(countQueryFrom))
|
||||
.where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
final Long totalCount = entityManager.createQuery(countQuery).getSingleResult();
|
||||
|
||||
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(Action_.status))
|
||||
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
|
||||
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
|
||||
.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
|
||||
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
|
||||
.collect(Collectors.toList());
|
||||
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.cache.CacheWriteNotify;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||
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.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout_;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.eclipse.hawkbit.rollout.condition.RolloutGroupActionEvaluator;
|
||||
import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
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.domain.Sort.Direction;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* RolloutManagement to control rollouts e.g. like creating, starting, resuming
|
||||
* and pausing rollouts. This service secures all the functionality based on the
|
||||
* {@link PreAuthorize} annotation on methods.
|
||||
*/
|
||||
@Validated
|
||||
@Service
|
||||
@EnableScheduling
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
public class JpaRolloutManagement implements RolloutManagement {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
private RolloutRepository rolloutRepository;
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
|
||||
@Autowired
|
||||
private TargetRepository targetRepository;
|
||||
|
||||
@Autowired
|
||||
private RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private DeploymentManagement deploymentManagement;
|
||||
|
||||
@Autowired
|
||||
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private NoCountPagingRepository criteriaNoCountDao;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager txManager;
|
||||
|
||||
@Autowired
|
||||
private CacheWriteNotify cacheWriteNotify;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("asyncExecutor")
|
||||
private Executor executor;
|
||||
|
||||
/*
|
||||
* set which stores the rollouts which are asynchronously creating. This is
|
||||
* necessary to verify rollouts which maybe stuck during creationg e.g.
|
||||
* because of database interruption, failures or even application crash.
|
||||
* !This is not cluster aware!
|
||||
*/
|
||||
private static final Set<String> creatingRollouts = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/*
|
||||
* set which stores the rollouts which are asynchronously starting. This is
|
||||
* necessary to verify rollouts which maybe stuck during starting e.g.
|
||||
* because of database interruption, failures or even application crash.
|
||||
* !This is not cluster aware!
|
||||
*/
|
||||
private static final Set<String> startingRollouts = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findAll(final Pageable page) {
|
||||
return rolloutRepository.findAll(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findAllWithDetailedStatusByPredicate(final Specification<Rollout> specification,
|
||||
final Pageable page) {
|
||||
final Page<Rollout> findAll = rolloutRepository.findAll(specification, page);
|
||||
setRolloutStatusDetails(findAll);
|
||||
return findAll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rollout findRolloutById(final Long rolloutId) {
|
||||
return rolloutRepository.findOne(rolloutId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public Rollout createRollout(final Rollout rollout, final int amountGroup,
|
||||
final RolloutGroupConditions conditions) {
|
||||
final Rollout savedRollout = createRollout(rollout, amountGroup);
|
||||
return createRolloutGroups(amountGroup, conditions, savedRollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
|
||||
final RolloutGroupConditions conditions) {
|
||||
final Rollout savedRollout = createRollout(rollout, amountGroup);
|
||||
creatingRollouts.add(savedRollout.getName());
|
||||
// need to flush the entity manager here to get the ID of the rollout,
|
||||
// because entity manager is set to FlushMode#Auto, entitymanager will
|
||||
// flush the Target entity, due the indirect relationship to the Rollout
|
||||
// entity without set an ID JPA is throwing a Invalid
|
||||
// 'org.springframework.dao.InvalidDataAccessApiUsageException: During
|
||||
// synchronization aect was found through a relationship that was not
|
||||
// marked cascade PERSIST'
|
||||
entityManager.flush();
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
createRolloutGroupsInNewTransaction(amountGroup, conditions, savedRollout);
|
||||
} finally {
|
||||
creatingRollouts.remove(savedRollout.getName());
|
||||
}
|
||||
});
|
||||
return savedRollout;
|
||||
}
|
||||
|
||||
private Rollout createRollout(final Rollout rollout, final int amountGroup) {
|
||||
verifyRolloutGroupParameter(amountGroup);
|
||||
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
|
||||
rollout.setTotalTargets(totalTargets.longValue());
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
private static void verifyRolloutGroupParameter(final int amountGroup) {
|
||||
if (amountGroup <= 0) {
|
||||
throw new IllegalArgumentException("the amountGroup must be greater than zero");
|
||||
} else if (amountGroup > 500) {
|
||||
throw new IllegalArgumentException("the amountGroup must not be greater than 500");
|
||||
}
|
||||
}
|
||||
|
||||
private Rollout createRolloutGroupsInNewTransaction(final int amountOfGroups,
|
||||
final RolloutGroupConditions conditions, final Rollout savedRollout) {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("creatingRollout");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return new TransactionTemplate(txManager, def)
|
||||
.execute(status -> createRolloutGroups(amountOfGroups, conditions, savedRollout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for creating rollout groups and calculating group sizes. Group
|
||||
* sizes are calculated by dividing the total count of targets through the
|
||||
* amount of given groups. In same cases this will lead to less rollout
|
||||
* groups than given by client.
|
||||
*
|
||||
* @param amountOfGroups
|
||||
* the amount of groups
|
||||
* @param conditions
|
||||
* the rollout group conditions
|
||||
* @param savedRollout
|
||||
* the rollout
|
||||
* @return the rollout with created groups
|
||||
*/
|
||||
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
|
||||
final Rollout savedRollout) {
|
||||
int pageIndex = 0;
|
||||
int groupIndex = 0;
|
||||
final Long totalCount = savedRollout.getTotalTargets();
|
||||
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountOfGroups);
|
||||
// validate if the amount of groups that will be created are the amount
|
||||
// of groups that the client what's to have created.
|
||||
int amountGroupValidated = amountOfGroups;
|
||||
final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize));
|
||||
if (amountGroupCreation == (amountOfGroups - 1)) {
|
||||
amountGroupValidated--;
|
||||
}
|
||||
RolloutGroup lastSavedGroup = null;
|
||||
while (pageIndex < totalCount) {
|
||||
groupIndex++;
|
||||
final String nameAndDesc = "group-" + groupIndex;
|
||||
final RolloutGroup group = new RolloutGroup();
|
||||
group.setName(nameAndDesc);
|
||||
group.setDescription(nameAndDesc);
|
||||
group.setRollout(savedRollout);
|
||||
group.setParent(lastSavedGroup);
|
||||
group.setSuccessCondition(conditions.getSuccessCondition());
|
||||
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
|
||||
group.setErrorCondition(conditions.getErrorCondition());
|
||||
group.setErrorConditionExp(conditions.getErrorConditionExp());
|
||||
group.setErrorAction(conditions.getErrorAction());
|
||||
group.setErrorActionExp(conditions.getErrorActionExp());
|
||||
|
||||
final RolloutGroup savedGroup = rolloutGroupRepository.save(group);
|
||||
|
||||
final Slice<Target> targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(),
|
||||
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id")));
|
||||
savedGroup.setTotalTargets(targetGroup.getContent().size());
|
||||
|
||||
lastSavedGroup = savedGroup;
|
||||
|
||||
targetGroup
|
||||
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
|
||||
cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(),
|
||||
amountGroupValidated, groupIndex);
|
||||
pageIndex += groupSize;
|
||||
}
|
||||
|
||||
savedRollout.setStatus(RolloutStatus.READY);
|
||||
return rolloutRepository.save(savedRollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public Rollout startRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
checkIfRolloutCanStarted(rollout, mergedRollout);
|
||||
return doStartRollout(mergedRollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public Rollout startRolloutAsync(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
checkIfRolloutCanStarted(rollout, mergedRollout);
|
||||
mergedRollout.setStatus(RolloutStatus.STARTING);
|
||||
final Rollout updatedRollout = rolloutRepository.save(mergedRollout);
|
||||
startingRollouts.add(updatedRollout.getName());
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setName("startingRollout");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
new TransactionTemplate(txManager, def).execute(status -> {
|
||||
doStartRollout(updatedRollout);
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
startingRollouts.remove(updatedRollout.getName());
|
||||
}
|
||||
});
|
||||
return updatedRollout;
|
||||
|
||||
}
|
||||
|
||||
private Rollout doStartRollout(final Rollout rollout) {
|
||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||
final ActionType actionType = rollout.getActionType();
|
||||
final long forceTime = rollout.getForcedTime();
|
||||
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout);
|
||||
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) {
|
||||
final RolloutGroup rolloutGroup = rolloutGroups.get(iGroup);
|
||||
final List<Target> targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup);
|
||||
// firstgroup can already be started
|
||||
if (iGroup == 0) {
|
||||
final List<TargetWithActionType> targetsWithActionType = targetGroup.stream()
|
||||
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
|
||||
.collect(Collectors.toList());
|
||||
deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout,
|
||||
rolloutGroup);
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
|
||||
} else {
|
||||
// create only not active actions with status scheduled so they
|
||||
// can be activated later
|
||||
deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout,
|
||||
rolloutGroup);
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||
}
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
rollout.setStatus(RolloutStatus.RUNNING);
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void pauseRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
if (mergedRollout.getStatus() != RolloutStatus.RUNNING) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
// setting the complete rollout only in paused state. This is sufficient
|
||||
// due the currently running groups will be completed and new groups are
|
||||
// not started until rollout goes back to running state again. The
|
||||
// periodically check for running rollouts will skip rollouts in pause
|
||||
// state.
|
||||
mergedRollout.setStatus(RolloutStatus.PAUSED);
|
||||
rolloutRepository.save(mergedRollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void resumeRollout(final Rollout rollout) {
|
||||
final Rollout mergedRollout = entityManager.merge(rollout);
|
||||
if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
mergedRollout.setStatus(RolloutStatus.RUNNING);
|
||||
rolloutRepository.save(mergedRollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void checkRunningRollouts(final long delayBetweenChecks) {
|
||||
verifyStuckedRollouts();
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
|
||||
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
|
||||
lastCheck + delayBetweenChecks);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Rollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
|
||||
RolloutStatus.RUNNING);
|
||||
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
for (final Rollout rollout : rolloutsToCheck) {
|
||||
LOGGER.debug("Checking rollout {}", rollout);
|
||||
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
||||
RolloutGroupStatus.RUNNING);
|
||||
|
||||
if (rolloutGroups.isEmpty()) {
|
||||
// no running rollouts, probably there was an error
|
||||
// somewhere at the latest group. And the latest group has
|
||||
// been switched from running into error state. So we need
|
||||
// to find the latest group which
|
||||
executeLatestRolloutGroup(rollout);
|
||||
} else {
|
||||
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size());
|
||||
executeRolloutGroups(rollout, rolloutGroups);
|
||||
}
|
||||
|
||||
if (isRolloutComplete(rollout)) {
|
||||
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
|
||||
rollout.setStatus(RolloutStatus.FINISHED);
|
||||
rolloutRepository.save(rollout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies and handles stucked rollouts in asynchronous creation or
|
||||
* starting state. If rollouts are created or started asynchronously it
|
||||
* might be that they keep in state {@link RolloutStatus#CREATING} or
|
||||
* {@link RolloutStatus#STARTING} due database or application interruption.
|
||||
* In case this happens, set the rollout to error state.
|
||||
*/
|
||||
private void verifyStuckedRollouts() {
|
||||
final List<Rollout> rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING);
|
||||
rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName()))
|
||||
.forEach(rollout -> {
|
||||
LOGGER.warn(
|
||||
"Determined error during rollout creation of rollout {}, stucking in creating state, setting to status",
|
||||
rollout, RolloutStatus.ERROR_CREATING);
|
||||
rollout.setStatus(RolloutStatus.ERROR_CREATING);
|
||||
rolloutRepository.save(rollout);
|
||||
});
|
||||
|
||||
final List<Rollout> rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING);
|
||||
rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName()))
|
||||
.forEach(rollout -> {
|
||||
LOGGER.warn(
|
||||
"Determined error during rollout starting of rollout {}, stucking in starting state, setting to status",
|
||||
rollout, RolloutStatus.ERROR_STARTING);
|
||||
rollout.setStatus(RolloutStatus.ERROR_STARTING);
|
||||
rolloutRepository.save(rollout);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void executeRolloutGroups(final Rollout rollout, final List<RolloutGroup> rolloutGroups) {
|
||||
for (final RolloutGroup rolloutGroup : rolloutGroups) {
|
||||
// error state check, do we need to stop the whole
|
||||
// rollout because of error?
|
||||
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
|
||||
final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition);
|
||||
if (isError) {
|
||||
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
||||
callErrorAction(rollout, rolloutGroup);
|
||||
} else {
|
||||
// not in error so check finished state, do we need to
|
||||
// start the next group?
|
||||
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
|
||||
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
|
||||
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeLatestRolloutGroup(final Rollout rollout) {
|
||||
final List<RolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
||||
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||
if (latestRolloutGroup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||
}
|
||||
|
||||
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
try {
|
||||
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
|
||||
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the error action bean {}",
|
||||
rolloutGroup.getErrorAction().getBeanName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRolloutComplete(final Rollout rollout) {
|
||||
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
|
||||
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
|
||||
return groupsActiveLeft == 0;
|
||||
}
|
||||
|
||||
private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
final Long actionsLeftForRollout = actionRepository
|
||||
.countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup,
|
||||
Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED);
|
||||
return actionsLeftForRollout == 0;
|
||||
}
|
||||
|
||||
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||
final RolloutGroupErrorCondition errorCondition) {
|
||||
if (errorCondition == null) {
|
||||
// there is no error condition, so return false, don't have error.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
|
||||
rolloutGroup, rolloutGroup.getErrorConditionExp());
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the error condition bean {}",
|
||||
errorCondition.getBeanName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||
final RolloutGroupSuccessCondition finishCondition) {
|
||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
||||
try {
|
||||
final boolean isFinished = context
|
||||
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
|
||||
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
||||
if (isFinished) {
|
||||
LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||
} else {
|
||||
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
|
||||
}
|
||||
return isFinished;
|
||||
} catch (final BeansException e) {
|
||||
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
|
||||
finishCondition.getBeanName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
|
||||
rolloutGroup, rolloutGroup.getSuccessActionExp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countRolloutsAll() {
|
||||
return rolloutRepository.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countRolloutsAllByFilters(final String searchText) {
|
||||
return rolloutRepository.count(likeNameOrDescription(searchText));
|
||||
}
|
||||
|
||||
private static Specification<Rollout> likeNameOrDescription(final String searchText) {
|
||||
return (rolloutRoot, query, criteriaBuilder) -> {
|
||||
final String searchTextToLower = searchText.toLowerCase();
|
||||
return criteriaBuilder.or(
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower),
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)),
|
||||
searchTextToLower));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Rollout> findRolloutByFilters(final Pageable pageable, final String searchText) {
|
||||
final Specification<Rollout> specs = likeNameOrDescription(searchText);
|
||||
final Slice<Rollout> findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class);
|
||||
setRolloutStatusDetails(findAll);
|
||||
return findAll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rollout findRolloutByName(final String rolloutName) {
|
||||
return rolloutRepository.findByName(rolloutName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rollout details.
|
||||
*
|
||||
* @param rollout
|
||||
* rollout to be updated
|
||||
*
|
||||
* @return Rollout updated rollout
|
||||
*/
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public Rollout updateRollout(final Rollout rollout) {
|
||||
Assert.notNull(rollout.getId());
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of targets in different status in rollout.
|
||||
*
|
||||
* @param page
|
||||
* the page request to sort and limit the result
|
||||
* @return a list of rollouts with details of targets count for different
|
||||
* statuses
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable page) {
|
||||
final Page<Rollout> rollouts = findAll(page);
|
||||
setRolloutStatusDetails(rollouts);
|
||||
return rollouts;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rollout findRolloutWithDetailedStatus(final Long rolloutId) {
|
||||
final Rollout rollout = findRolloutById(rolloutId);
|
||||
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
|
||||
.getStatusCountByRolloutId(rolloutId);
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
|
||||
rollout.getTotalTargets());
|
||||
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
return rollout;
|
||||
}
|
||||
|
||||
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rolloutIds) {
|
||||
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
|
||||
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
|
||||
}
|
||||
|
||||
private void setRolloutStatusDetails(final Slice<Rollout> rollouts) {
|
||||
final List<Long> rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId())
|
||||
.collect(Collectors.toList());
|
||||
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
|
||||
rolloutIds);
|
||||
|
||||
for (final Rollout rollout : rollouts) {
|
||||
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
|
||||
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
|
||||
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
|
||||
if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) {
|
||||
final Long totalGroup = rolloutGroup.getTotalTargets();
|
||||
final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId,
|
||||
rolloutGroup.getId(), Action.Status.FINISHED);
|
||||
if (totalGroup == 0) {
|
||||
// in case e.g. targets has been deleted we don't have any actions
|
||||
// left for this group, so the group is finished
|
||||
return 100;
|
||||
}
|
||||
// calculate threshold
|
||||
return ((float) finished / (float) totalGroup) * 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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 org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Central tenant configuration management operations of the SP server.
|
||||
*/
|
||||
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Validated
|
||||
@Service
|
||||
public class JpaTenantConfigurationManagement implements EnvironmentAware, TenantConfigurationManagement {
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationRepository tenantConfigurationRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
public <T> TenantConfigurationValue<T> getConfigurationValue(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
validateTenantConfigurationDataType(configurationKey, propertyType);
|
||||
|
||||
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data type of the tenant configuration. If it is possible to
|
||||
* cast to the given data type.
|
||||
*
|
||||
* @param configurationKey
|
||||
* the key
|
||||
* @param propertyType
|
||||
* the class
|
||||
*/
|
||||
static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
|
||||
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
|
||||
final TenantConfiguration tenantConfiguration) {
|
||||
if (tenantConfiguration != null) {
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(false).createdBy(tenantConfiguration.getCreatedBy())
|
||||
.createdAt(tenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
|
||||
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
|
||||
|
||||
} else if (configurationKey.getDefaultKeyName() != null) {
|
||||
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(true).createdBy(null).createdAt(null)
|
||||
.lastModifiedAt(null).lastModifiedBy(null)
|
||||
.value(getGlobalConfigurationValue(configurationKey, propertyType)).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TenantConfigurationValue<?> getConfigurationValue(final TenantConfigurationKey configurationKey) {
|
||||
return getConfigurationValue(configurationKey, configurationKey.getDataType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey,
|
||||
final Class<T> propertyType) {
|
||||
|
||||
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
|
||||
throw new TenantConfigurationValidatorException(
|
||||
String.format("Cannot parse the database value of type %s into the type %s.",
|
||||
configurationKey.getDataType(), propertyType));
|
||||
}
|
||||
|
||||
final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType);
|
||||
|
||||
if (valueInProperties == null) {
|
||||
return conversionService.convert(configurationKey.getDefaultValue(), propertyType);
|
||||
}
|
||||
|
||||
return valueInProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public <T> TenantConfigurationValue<T> addOrUpdateConfiguration(final TenantConfigurationKey configurationKey,
|
||||
final T value) {
|
||||
|
||||
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
|
||||
throw new TenantConfigurationValidatorException(String.format(
|
||||
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value,
|
||||
value.getClass(), configurationKey.getDataType()));
|
||||
}
|
||||
|
||||
configurationKey.validate(applicationContext, value);
|
||||
|
||||
TenantConfiguration tenantConfiguration = tenantConfigurationRepository
|
||||
.findByKey(configurationKey.getKeyName());
|
||||
|
||||
if (tenantConfiguration == null) {
|
||||
tenantConfiguration = new TenantConfiguration(configurationKey.getKeyName(), value.toString());
|
||||
} else {
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
|
||||
final TenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository.save(tenantConfiguration);
|
||||
|
||||
final Class<T> clazzT = (Class<T>) value.getClass();
|
||||
|
||||
return TenantConfigurationValue.<T> builder().isGlobal(false)
|
||||
.createdBy(updatedTenantConfiguration.getCreatedBy())
|
||||
.createdAt(updatedTenantConfiguration.getCreatedAt())
|
||||
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
|
||||
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
|
||||
.value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void deleteConfiguration(final TenantConfigurationKey configurationKey) {
|
||||
tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(final Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutTargetGroupId;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@@ -27,6 +27,7 @@ import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.CustomSoftwareModule;
|
||||
@@ -307,7 +308,7 @@ public class SoftwareManagement {
|
||||
|
||||
private void deleteGridFsArtifacts(final SoftwareModule swModule) {
|
||||
for (final LocalArtifact localArtifact : swModule.getLocalArtifacts()) {
|
||||
artifactManagement.deleteGridFsArtifact(localArtifact);
|
||||
artifactManagement.deleteLocalArtifact(localArtifact);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
@@ -33,6 +33,7 @@ import org.eclipse.hawkbit.Constants;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetTagAssigmentResultEvent;
|
||||
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet_;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.report.model.TenantUsage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -43,7 +46,8 @@ public class TenantStatsManagement {
|
||||
* to collect for
|
||||
* @return collected statistics
|
||||
*/
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
|
||||
public TenantUsage getStatsOfTenant(final String tenant) {
|
||||
final TenantUsage result = new TenantUsage(tenant);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.model.helper;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,8 +10,8 @@ package org.eclipse.hawkbit.rollout.condition;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
|
||||
@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.rollout.condition;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rollout.condition;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.rollout.condition;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
|
||||
@@ -16,34 +16,34 @@ import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
|
||||
import org.eclipse.hawkbit.repository.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.ActionStatusRepository;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetRepository;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagRepository;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.ExternalArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.LocalArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutRepository;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataRepository;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleRepository;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetInfoRepository;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.TargetTagRepository;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantMetaDataRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionStatusRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.DistributionSetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.DistributionSetTagRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.DistributionSetTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.ExternalArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.LocalArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.RolloutRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareModuleMetadataRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareModuleRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareModuleTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetInfoRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetTagRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.TenantMetaDataRepository;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator.DatabaseCleanupUtil;
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.util.UUID;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
|
||||
@@ -58,14 +58,14 @@ public class ControllerManagementTest extends AbstractIntegrationTest {
|
||||
System.currentTimeMillis());
|
||||
actionStatusMessage.addMessage("foobar");
|
||||
savedAction.setStatus(Status.RUNNING);
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage, savedAction);
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage);
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.PENDING);
|
||||
|
||||
actionStatusMessage = new ActionStatus(savedAction, Action.Status.FINISHED, System.currentTimeMillis());
|
||||
actionStatusMessage.addMessage(RandomStringUtils.randomAscii(512));
|
||||
savedAction.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage, savedAction);
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage);
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.IN_SYNC);
|
||||
|
||||
@@ -111,21 +111,21 @@ public class ControllerManagementTest extends AbstractIntegrationTest {
|
||||
final ActionStatus actionStatusMessage = new ActionStatus(savedAction, Action.Status.RUNNING,
|
||||
System.currentTimeMillis());
|
||||
actionStatusMessage.addMessage("running");
|
||||
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage, savedAction);
|
||||
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage);
|
||||
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.PENDING);
|
||||
|
||||
final ActionStatus actionStatusMessage2 = new ActionStatus(savedAction, Action.Status.ERROR,
|
||||
System.currentTimeMillis());
|
||||
actionStatusMessage2.addMessage("error");
|
||||
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage2, savedAction);
|
||||
savedAction = controllerManagament.addUpdateActionStatus(actionStatusMessage2);
|
||||
assertThat(targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.ERROR);
|
||||
|
||||
final ActionStatus actionStatusMessage3 = new ActionStatus(savedAction, Action.Status.FINISHED,
|
||||
System.currentTimeMillis());
|
||||
actionStatusMessage3.addMessage("finish");
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage3, savedAction);
|
||||
controllerManagament.addUpdateActionStatus(actionStatusMessage3);
|
||||
|
||||
targetManagement.findTargetByControllerID("Rabbit").getTargetInfo().getUpdateStatus();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetRepository;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
@@ -104,7 +105,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
@Test
|
||||
@Description("Ensures that distribution sets can assigned and unassigned to a distribution set tag. Not exists distribution set will be ignored for the assignment.")
|
||||
public void assignAndUnassignDistributionSetToTag() {
|
||||
final List<Long> assignDS = new ArrayList<Long>();
|
||||
final List<Long> assignDS = new ArrayList<>();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
assignDS.add(TestDataUtil.generateDistributionSet("DS" + i, "1.0", softwareManagement,
|
||||
distributionSetManagement, new ArrayList<DistributionSetTag>()).getId());
|
||||
@@ -199,8 +200,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
secondAction = deploymentManagement.findActionWithDetails(secondAction.getId());
|
||||
// confirm cancellation
|
||||
secondAction.setStatus(Status.CANCELED);
|
||||
controllerManagement.addCancelActionStatus(
|
||||
new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()), secondAction);
|
||||
controllerManagement
|
||||
.addCancelActionStatus(new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()));
|
||||
assertThat(actionStatusRepository.findAll()).as("wrong size of actions status").hasSize(4);
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).as("wrong ds")
|
||||
.isEqualTo(dsFirst);
|
||||
@@ -213,8 +214,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
firstAction = deploymentManagement.findActionWithDetails(firstAction.getId());
|
||||
// confirm cancellation
|
||||
firstAction.setStatus(Status.CANCELED);
|
||||
controllerManagement.addCancelActionStatus(
|
||||
new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()), firstAction);
|
||||
controllerManagement
|
||||
.addCancelActionStatus(new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()));
|
||||
assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(6);
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet())
|
||||
.as("wrong assigned ds").isEqualTo(dsInstalled);
|
||||
@@ -256,8 +257,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
// confirm cancellation
|
||||
firstAction = deploymentManagement.findActionWithDetails(firstAction.getId());
|
||||
firstAction.setStatus(Status.CANCELED);
|
||||
controllerManagement.addCancelActionStatus(
|
||||
new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()), firstAction);
|
||||
controllerManagement
|
||||
.addCancelActionStatus(new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()));
|
||||
assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(4);
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet())
|
||||
.as("wrong assigned ds").isEqualTo(dsSecond);
|
||||
@@ -273,8 +274,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
.as("wrong assigned ds").isEqualTo(dsSecond);
|
||||
// confirm cancellation
|
||||
secondAction.setStatus(Status.CANCELED);
|
||||
controllerManagement.addCancelActionStatus(
|
||||
new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()), secondAction);
|
||||
controllerManagement
|
||||
.addCancelActionStatus(new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()));
|
||||
// cancelled success -> back to dsInstalled
|
||||
assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet())
|
||||
.as("wrong installed ds").isEqualTo(dsInstalled);
|
||||
@@ -742,7 +743,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
for (final String msg : msgs) {
|
||||
statusMessages.addMessage(msg);
|
||||
}
|
||||
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
|
||||
controllerManagament.addUpdateActionStatus(statusMessages);
|
||||
return targetManagement.findTargetByControllerID(t.getControllerId());
|
||||
}
|
||||
|
||||
@@ -786,7 +787,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest {
|
||||
final Action action = updAct.getContent().get(0);
|
||||
action.setStatus(Status.FINISHED);
|
||||
final ActionStatus statusMessage = new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "");
|
||||
controllerManagament.addUpdateActionStatus(statusMessage, action);
|
||||
controllerManagament.addUpdateActionStatus(statusMessage);
|
||||
|
||||
targ = targetManagement.findTargetByControllerID(targ.getControllerId());
|
||||
|
||||
|
||||
@@ -821,7 +821,7 @@ public class DistributionSetManagementTest extends AbstractIntegrationTest {
|
||||
for (final String msg : msgs) {
|
||||
statusMessages.addMessage(msg);
|
||||
}
|
||||
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
|
||||
controllerManagament.addUpdateActionStatus(statusMessages);
|
||||
return targetManagement.findTargetByControllerID(t.getControllerId());
|
||||
}
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ public class ReportManagementTest extends AbstractIntegrationTest {
|
||||
for (final String msg : msgs) {
|
||||
statusMessages.addMessage(msg);
|
||||
}
|
||||
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
|
||||
controllerManagament.addUpdateActionStatus(statusMessages);
|
||||
return targetManagement.findTargetByControllerID(t.getControllerId());
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.concurrent.Callable;
|
||||
|
||||
import org.eclipse.hawkbit.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.TestDataUtil;
|
||||
import org.eclipse.hawkbit.repository.jpa.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
@@ -136,8 +137,8 @@ public class RolloutManagementTest extends AbstractIntegrationTest {
|
||||
// 50%
|
||||
final Action action = runningActions.get(0);
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), ""), action);
|
||||
controllerManagament
|
||||
.addUpdateActionStatus(new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), ""));
|
||||
|
||||
// check running rollouts again, now the finish condition should be hit
|
||||
// and should start the next group
|
||||
@@ -178,8 +179,8 @@ public class RolloutManagementTest extends AbstractIntegrationTest {
|
||||
// finish actions with error
|
||||
for (final Action action : runningActions) {
|
||||
action.setStatus(Status.ERROR);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action);
|
||||
controllerManagament
|
||||
.addUpdateActionStatus(new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""));
|
||||
}
|
||||
|
||||
// check running rollouts again, now the error condition should be hit
|
||||
@@ -220,8 +221,8 @@ public class RolloutManagementTest extends AbstractIntegrationTest {
|
||||
// finish actions with error
|
||||
for (final Action action : runningActions) {
|
||||
action.setStatus(Status.ERROR);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action);
|
||||
controllerManagament
|
||||
.addUpdateActionStatus(new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""));
|
||||
}
|
||||
|
||||
// check running rollouts again, now the error condition should be hit
|
||||
@@ -977,8 +978,8 @@ public class RolloutManagementTest extends AbstractIntegrationTest {
|
||||
final List<Action> runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING);
|
||||
for (final Action action : runningActions) {
|
||||
action.setStatus(status);
|
||||
controllerManagament.addUpdateActionStatus(new ActionStatus(action, status, System.currentTimeMillis(), ""),
|
||||
action);
|
||||
controllerManagament
|
||||
.addUpdateActionStatus(new ActionStatus(action, status, System.currentTimeMillis(), ""));
|
||||
}
|
||||
return runningActions.size();
|
||||
}
|
||||
@@ -989,14 +990,13 @@ public class RolloutManagementTest extends AbstractIntegrationTest {
|
||||
assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged);
|
||||
for (int i = 0; i < amountOfTargetsToGetChanged; i++) {
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(runningActions.get(i), status, System.currentTimeMillis(), ""),
|
||||
runningActions.get(i));
|
||||
new ActionStatus(runningActions.get(i), status, System.currentTimeMillis(), ""));
|
||||
}
|
||||
return runningActions.size();
|
||||
}
|
||||
|
||||
private Map<TotalTargetCountStatus.Status, Long> createInitStatusMap() {
|
||||
final Map<TotalTargetCountStatus.Status, Long> map = new HashMap<TotalTargetCountStatus.Status, Long>();
|
||||
final Map<TotalTargetCountStatus.Status, Long> map = new HashMap<>();
|
||||
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
|
||||
map.put(status, 0L);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.TestDataUtil;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetFilter.DistributionSetFilterBuilder;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
||||
|
||||
@@ -13,6 +13,7 @@ import static org.junit.Assert.fail;
|
||||
|
||||
import org.eclipse.hawkbit.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest {
|
||||
final Action action = deploymentManagement.findActionWithDetails(actionId);
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action);
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"));
|
||||
deploymentManagement.assignDistributionSet(setA.getId(), installedC);
|
||||
|
||||
final List<TargetUpdateStatus> unknown = new ArrayList<>();
|
||||
@@ -745,7 +745,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest {
|
||||
final Action action = deploymentManagement.findActionWithDetails(actionId);
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action);
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"));
|
||||
});
|
||||
deploymentManagement.assignDistributionSet(assignedSet, assignedtargets);
|
||||
|
||||
@@ -773,7 +773,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest {
|
||||
final Action action = deploymentManagement.findActionWithDetails(actionId);
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action);
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"));
|
||||
});
|
||||
deploymentManagement.assignDistributionSet(assignedSet, installedtargets);
|
||||
|
||||
@@ -802,7 +802,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest {
|
||||
final Action action = deploymentManagement.findActionWithDetails(actionId);
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action);
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"));
|
||||
});
|
||||
deploymentManagement.assignDistributionSet(assignedSet, installedtargets);
|
||||
|
||||
@@ -840,7 +840,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest {
|
||||
for (final String msg : msgs) {
|
||||
statusMessages.addMessage(msg);
|
||||
}
|
||||
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
|
||||
controllerManagament.addUpdateActionStatus(statusMessages);
|
||||
return targetManagement.findTargetByControllerID(t.getControllerId());
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ public class TargetManagementTest extends AbstractIntegrationTest {
|
||||
final Action action = deploymentManagement.findActionWithDetails(result.getActions().get(0));
|
||||
action.setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action);
|
||||
new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"));
|
||||
deploymentManagement.assignDistributionSet(set2.getId(), "4711");
|
||||
|
||||
target = targetManagement.findTargetByControllerIDWithDetails("4711");
|
||||
|
||||
@@ -75,7 +75,7 @@ public class RSQLRolloutGroupFields extends AbstractIntegrationTest {
|
||||
}
|
||||
|
||||
private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {
|
||||
final Page<RolloutGroup> findTargetPage = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout,
|
||||
final Page<RolloutGroup> findTargetPage = rolloutGroupManagement.findRolloutGroupsAll(rollout,
|
||||
RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), new PageRequest(0, 100));
|
||||
final long countTargetsAll = findTargetPage.getTotalElements();
|
||||
assertThat(findTargetPage).isNotNull();
|
||||
|
||||
@@ -30,9 +30,9 @@ import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
@@ -207,14 +207,14 @@ public final class RepositoryDataGenerator {
|
||||
download.setAction(action);
|
||||
download.setStatus(Status.DOWNLOAD);
|
||||
download.addMessage("Controller started download.");
|
||||
action = controllerManagement.addUpdateActionStatus(download, action);
|
||||
action = controllerManagement.addUpdateActionStatus(download);
|
||||
|
||||
// warning
|
||||
final ActionStatus warning = new ActionStatus();
|
||||
warning.setAction(action);
|
||||
warning.setStatus(Status.WARNING);
|
||||
warning.addMessage("Some warning: " + jlorem.words(new Random().nextInt(50)));
|
||||
action = controllerManagement.addUpdateActionStatus(warning, action);
|
||||
action = controllerManagement.addUpdateActionStatus(warning);
|
||||
|
||||
// garbage
|
||||
for (int i = 0; i < new Random().nextInt(10); i++) {
|
||||
@@ -222,13 +222,13 @@ public final class RepositoryDataGenerator {
|
||||
running.setAction(action);
|
||||
running.setStatus(Status.RUNNING);
|
||||
running.addMessage("Still running: " + jlorem.words(new Random().nextInt(50)));
|
||||
action = controllerManagement.addUpdateActionStatus(running, action);
|
||||
action = controllerManagement.addUpdateActionStatus(running);
|
||||
for (int g = 0; g < new Random().nextInt(5); g++) {
|
||||
final ActionStatus rand = new ActionStatus();
|
||||
rand.setAction(action);
|
||||
rand.setStatus(Status.RUNNING);
|
||||
rand.addMessage(jlorem.words(new Random().nextInt(50)));
|
||||
action = controllerManagement.addUpdateActionStatus(rand, action);
|
||||
action = controllerManagement.addUpdateActionStatus(rand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,13 +241,13 @@ public final class RepositoryDataGenerator {
|
||||
if (incrementAndGet % 5 == 0) {
|
||||
close.setStatus(Status.ERROR);
|
||||
close.addMessage("Controller reported CLOSED with ERROR!");
|
||||
action = controllerManagement.addUpdateActionStatus(close, action);
|
||||
action = controllerManagement.addUpdateActionStatus(close);
|
||||
}
|
||||
// with OK
|
||||
else {
|
||||
close.setStatus(Status.FINISHED);
|
||||
close.addMessage("Controller reported CLOSED with OK!");
|
||||
action = controllerManagement.addUpdateActionStatus(close, action);
|
||||
action = controllerManagement.addUpdateActionStatus(close);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -266,7 +266,7 @@ public final class RepositoryDataGenerator {
|
||||
close.setAction(action);
|
||||
close.setStatus(Status.FINISHED);
|
||||
close.addMessage("Controller reported CLOSED with OK!");
|
||||
action = controllerManagement.addUpdateActionStatus(close, action);
|
||||
action = controllerManagement.addUpdateActionStatus(close);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -339,8 +339,8 @@ public final class RepositoryDataGenerator {
|
||||
|
||||
// garbage DS
|
||||
LOG.debug("initDemoRepo - start now DS garbage");
|
||||
TestDataUtil.generateDistributionSets("Generic Software Package", sizeMultiplikator,
|
||||
softwareManagement, distributionSetManagement);
|
||||
TestDataUtil.generateDistributionSets("Generic Software Package", sizeMultiplikator, softwareManagement,
|
||||
distributionSetManagement);
|
||||
LOG.debug("initDemoRepo - DS garbage finished");
|
||||
|
||||
LOG.debug("initDemoRepo - start now Extra Software Modules and types");
|
||||
|
||||
@@ -149,7 +149,7 @@ public class ArtifactStoreController {
|
||||
actionStatus.addMessage(
|
||||
ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads: " + request.getRequestURI());
|
||||
}
|
||||
controllerManagement.addActionStatusMessage(actionStatus);
|
||||
controllerManagement.addInformationalActionStatus(actionStatus);
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ import org.eclipse.hawkbit.controller.model.DeploymentBase;
|
||||
import org.eclipse.hawkbit.controller.model.Result.FinalResult;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
@@ -150,7 +150,7 @@ public class RootController {
|
||||
|
||||
return new ResponseEntity<>(
|
||||
DataConversionHelper.fromTarget(target, controllerManagement.findActionByTargetAndActive(target),
|
||||
controllerManagement.findPollingTime(), tenantAware),
|
||||
controllerManagement.getPollingTime(), tenantAware),
|
||||
HttpStatus.OK);
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ public class RootController {
|
||||
statusMessage.addMessage(
|
||||
ControllerManagement.SERVER_MESSAGE_PREFIX + "Target downloads " + request.getRequestURI());
|
||||
}
|
||||
controllerManagement.addActionStatusMessage(statusMessage);
|
||||
controllerManagement.addInformationalActionStatus(statusMessage);
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -371,8 +371,7 @@ public class RootController {
|
||||
return new ResponseEntity<>(HttpStatus.GONE);
|
||||
}
|
||||
|
||||
controllerManagement.addUpdateActionStatus(generateUpdateStatus(feedback, targetid, feedback.getId(), action),
|
||||
action);
|
||||
controllerManagement.addUpdateActionStatus(generateUpdateStatus(feedback, targetid, feedback.getId(), action));
|
||||
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
|
||||
@@ -546,7 +545,7 @@ public class RootController {
|
||||
}
|
||||
|
||||
controllerManagement
|
||||
.addCancelActionStatus(generateActionCancelStatus(feedback, target, feedback.getId(), action), action);
|
||||
.addCancelActionStatus(generateActionCancelStatus(feedback, target, feedback.getId(), action));
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
|
||||
@@ -18,12 +18,12 @@ import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetWithActionType;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetWithActionType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.TagFields;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
||||
|
||||
@@ -14,8 +14,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.rest.resource.api.DistributionSetTypeRestApi;
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
@@ -69,7 +69,7 @@ public class DistributionSetTypeResource implements DistributionSetTypeRestApi {
|
||||
final Slice<DistributionSetType> findModuleTypessAll;
|
||||
Long countModulesAll;
|
||||
if (rsqlParam != null) {
|
||||
findModuleTypessAll = distributionSetManagement.findDistributionSetTypesByPredicate(
|
||||
findModuleTypessAll = distributionSetManagement.findDistributionSetTypesAll(
|
||||
RSQLUtility.parse(rsqlParam, DistributionSetTypeFields.class), pageable);
|
||||
countModulesAll = ((Page<DistributionSetType>) findModuleTypessAll).getTotalElements();
|
||||
} else {
|
||||
|
||||
@@ -13,8 +13,8 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
|
||||
|
||||
@@ -178,7 +178,7 @@ public class RolloutResource implements RolloutRestApi {
|
||||
|
||||
final Page<RolloutGroup> findRolloutGroupsAll;
|
||||
if (rsqlParam != null) {
|
||||
findRolloutGroupsAll = this.rolloutGroupManagement.findRolloutGroupsByPredicate(rollout,
|
||||
findRolloutGroupsAll = this.rolloutGroupManagement.findRolloutGroupsAll(rollout,
|
||||
RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), pageable);
|
||||
} else {
|
||||
findRolloutGroupsAll = this.rolloutGroupManagement.findRolloutGroupsByRolloutId(rolloutId, pageable);
|
||||
|
||||
@@ -14,8 +14,8 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
|
||||
@@ -12,10 +12,10 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
||||
|
||||
@@ -10,9 +10,9 @@ package org.eclipse.hawkbit.rest.resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
|
||||
@@ -16,8 +16,8 @@ import java.util.stream.Collectors;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.report.model.SystemUsageReport;
|
||||
import org.eclipse.hawkbit.report.model.TenantUsage;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SystemManagement;
|
||||
import org.eclipse.hawkbit.rest.resource.model.systemmanagement.CacheRest;
|
||||
import org.eclipse.hawkbit.rest.resource.model.systemmanagement.SystemStatisticsRest;
|
||||
import org.eclipse.hawkbit.rest.resource.model.systemmanagement.TenantSystemUsageRest;
|
||||
|
||||
@@ -19,8 +19,8 @@ import org.eclipse.hawkbit.repository.ActionFields;
|
||||
import org.eclipse.hawkbit.repository.ActionStatusFields;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
|
||||
@@ -12,9 +12,9 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TagFields;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
||||
|
||||
@@ -30,12 +30,12 @@ import org.eclipse.hawkbit.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.MockMvcResultPrinter;
|
||||
import org.eclipse.hawkbit.TestDataUtil;
|
||||
import org.eclipse.hawkbit.WithUser;
|
||||
import org.eclipse.hawkbit.repository.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.ActionRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
@@ -890,7 +890,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest {
|
||||
for (final String msg : msgs) {
|
||||
statusMessages.addMessage(msg);
|
||||
}
|
||||
controllerManagament.addUpdateActionStatus(statusMessages, updActA);
|
||||
controllerManagament.addUpdateActionStatus(statusMessages);
|
||||
return targetManagement.findTargetByControllerID(t.getControllerId());
|
||||
}
|
||||
|
||||
|
||||
@@ -108,8 +108,7 @@ public class TargetResourceTest extends AbstractIntegrationTest {
|
||||
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
|
||||
actions.get(0).setStatus(Status.FINISHED);
|
||||
controllerManagament.addUpdateActionStatus(
|
||||
new ActionStatus(actions.get(0), Status.FINISHED, System.currentTimeMillis(), "testmessage"),
|
||||
actions.get(0));
|
||||
new ActionStatus(actions.get(0), Status.FINISHED, System.currentTimeMillis(), "testmessage"));
|
||||
|
||||
final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionFields.ID.getFieldName());
|
||||
final ActionStatus status = deploymentManagement
|
||||
@@ -1284,8 +1283,8 @@ public class TargetResourceTest extends AbstractIntegrationTest {
|
||||
final Pageable pageReq = new PageRequest(0, 100);
|
||||
final Action action = actionRepository.findByDistributionSet(pageReq, savedSet).getContent().get(0);
|
||||
|
||||
final ActionStatus actionStatus = new ActionStatus(action, Status.FINISHED, 0l);
|
||||
controllerManagement.addUpdateActionStatus(actionStatus, action);
|
||||
final ActionStatus actionStatus = new ActionStatus(action, Status.FINISHED, 0L);
|
||||
controllerManagement.addUpdateActionStatus(actionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.jpa.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent;
|
||||
import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState;
|
||||
import org.eclipse.hawkbit.ui.artifacts.state.CustomFile;
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.ui.common.UserDetailsFormatter;
|
||||
|
||||
@@ -10,7 +10,7 @@ package org.eclipse.hawkbit.ui.artifacts.smtable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
|
||||
import org.eclipse.hawkbit.ui.common.SoftwareModuleTypeBeanQuery;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SMFilterEvent;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleTypeEvent;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleTypeEvent.SoftwareModuleTypeEnum;
|
||||
|
||||
@@ -10,7 +10,7 @@ package org.eclipse.hawkbit.ui.artifacts.smtype;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SMFilterEvent;
|
||||
import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.jpa.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
|
||||
|
||||
@@ -11,8 +11,8 @@ package org.eclipse.hawkbit.ui.common;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.jpa.SoftwareManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
|
||||
|
||||
@@ -10,7 +10,7 @@ package org.eclipse.hawkbit.ui.common.tagdetails;
|
||||
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetTagCreatedBulkEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetTagDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.eclipse.hawkbit.eventbus.event.DistributionSetTagCreatedBulkEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagDeletedEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagUpdateEvent;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TagManagement;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetTagAssigmentResultEvent;
|
||||
import org.eclipse.hawkbit.eventbus.event.TargetTagUpdateEvent;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user