Rollouts can be deleted (#436)

* Management UI

Signed-off-by: Melanie Retter <melanie.retter@bosch-si.com>

* Repository

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* Optimisations and scheduler deleting enabled

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Melanie Retter
2017-02-18 07:19:28 +01:00
committed by Kai Zimmermann
parent 804522f966
commit 5628d625e8
159 changed files with 3029 additions and 1737 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

@@ -74,4 +74,4 @@ A _Distribution Set_ entity may have for example URIs to artifacts, _Software Mo
"metadata": {
"href": "http://localhost:8080/rest/v1/softwaremodules/83/metadata?offset=0&limit=50"
}
{% endhighlight %}
{% endhighlight %}

View File

@@ -83,8 +83,8 @@ Allows to:
Software rollout in large scale, rollout status overview and rollout management.
## Features explained
- Create, update and start of rollouts.
- Pause and resume of rollouts.
- Create, update, copy and delete of rollouts.
- Start, pause and resume of rollouts.
- Progress monitoring for the entire rollout and the individual groups.
- Drill down to see the groups in a rollout and targets in each group.
- Rollout attributes:

View File

@@ -153,6 +153,7 @@ public class DeviceSimulatorUpdater {
if (device.getProgress() <= 0 && modules != null) {
device.setUpdateStatus(simulateDownloads(device.getTargetSecurityToken()));
if (isErrorResponse(device.getUpdateStatus())) {
device.setProgress(1.0);
callback.updateFinished(device, actionId);
eventbus.post(new ProgressUpdate(device));
return;
@@ -216,8 +217,11 @@ public class DeviceSimulatorUpdater {
private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash,
final long size) {
LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url,
hideTokenDetails(targetToken), sha1Hash, size);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url,
hideTokenDetails(targetToken), sha1Hash, size);
}
try {
return readAndCheckDownloadUrl(url, targetToken, sha1Hash, size);

View File

@@ -232,7 +232,7 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, tenant);
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
actionUpdateStatus.getMessage().addAll(updateResultMessages);
actionUpdateStatus.addMessage(updateResultMessages);
actionUpdateStatus.setActionId(actionId);
return convertMessage(actionUpdateStatus, messageProperties);

View File

@@ -17,16 +17,16 @@ spring.datasource.username=root
spring.datasource.password=
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.max-active=100
spring.datasource.max-idle=10
spring.datasource.min-idle=10
spring.datasource.initial-size=10
spring.datasource.validation-query=select 1 from dual
spring.datasource.validation-interval=30000
spring.datasource.test-on-borrow=true
spring.datasource.test-on-return=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=30000
spring.datasource.min-evictable-idle-time-millis=60000
spring.datasource.max-wait=10000
spring.datasource.jmx-enabled=true
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.min-idle=10
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.validation-query=select 1 from dual
spring.datasource.tomcat.validation-interval=30000
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.test-on-return=false
spring.datasource.tomcat.test-while-idle=true
spring.datasource.tomcat.time-between-eviction-runs-millis=30000
spring.datasource.tomcat.min-evictable-idle-time-millis=60000
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.jmx-enabled=true

View File

@@ -41,6 +41,7 @@ import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import com.google.common.collect.Lists;
@@ -123,7 +124,6 @@ public class ConfigurableScenario {
.createTargetTags(new TagBuilder().name(group).description("Group " + group).build()).getBody()
.get(0).getTagId())
.collect(Collectors.toList());
}
private void cleanRepository() {
@@ -136,11 +136,20 @@ public class ConfigurableScenario {
}
private void deleteRollouts() {
// TODO: complete this as soon as rollouts can be deleted
LOGGER.info("Delete Rollouts");
PagedList<MgmtRolloutResponseBody> rollouts;
while ((rollouts = rolloutResource.getRollouts(0, PAGE_SIZE, null, null).getBody()).getTotal() > 0) {
rollouts.getContent().parallelStream().forEach(rollout -> {
rolloutResource.delete(rollout.getRolloutId());
waitUntilRolloutNoLongerExists(rollout.getRolloutId());
});
}
}
private void deleteSoftwareModules() {
LOGGER.info("Delete SoftwareModules");
PagedList<MgmtSoftwareModule> modules;
do {
modules = softwareModuleResource.getSoftwareModules(0, PAGE_SIZE, null, null).getBody();
@@ -150,6 +159,8 @@ public class ConfigurableScenario {
}
private void deleteDistributionSets() {
LOGGER.info("Delete DistributionSets");
PagedList<MgmtDistributionSet> distributionSets;
do {
distributionSets = distributionSetResource.getDistributionSets(0, PAGE_SIZE, null, null).getBody();
@@ -170,6 +181,8 @@ public class ConfigurableScenario {
}
private void deleteTargets() {
LOGGER.info("Delete Targets");
PagedList<MgmtTarget> targets;
do {
targets = targetResource.getTargets(0, PAGE_SIZE, null, null).getBody();
@@ -203,8 +216,9 @@ public class ConfigurableScenario {
LOGGER.info("Run semi automatic rollout for set {}", set.getDsId());
// create a Rollout
final MgmtRolloutResponseBody rolloutResponseBody = rolloutResource.create(new RolloutBuilder()
.name("Rollout" + set.getName() + set.getVersion()).semiAutomaticGroups(createRolloutGroups(scenario))
.targetFilterQuery("name==*").distributionSetId(set.getDsId())
.name("SemiAutomaticRollout" + set.getName() + set.getVersion())
.semiAutomaticGroups(createRolloutGroups(scenario)).targetFilterQuery("name==*")
.distributionSetId(set.getDsId())
.successThreshold(String.valueOf(scenario.getRolloutSuccessThreshold())).errorThreshold("5").build())
.getBody();
@@ -218,13 +232,15 @@ public class ConfigurableScenario {
}
private static List<MgmtRolloutGroup> createRolloutGroups(final Scenario scenario) {
final List<MgmtRolloutGroup> result = Lists.newArrayListWithCapacity(scenario.getDeviceGroups().size() * 2);
final List<MgmtRolloutGroup> result = Lists
.newArrayListWithExpectedSize((scenario.getDeviceGroups().size() * 3) + 1);
scenario.getDeviceGroups().forEach(groupname -> {
result.add(createGroup(1, groupname, 10F));
result.add(createGroup(2, groupname, 50F));
result.add(createGroup(3, groupname, 100F));
});
result.add(createFinalGroup());
return result;
}
@@ -238,6 +254,15 @@ public class ConfigurableScenario {
return one;
}
private static MgmtRolloutGroup createFinalGroup() {
final MgmtRolloutGroup one = new MgmtRolloutGroup();
one.setName("final");
one.setDescription("Group of non tagged devices");
one.setTargetFilterQuery("name==*");
one.setTargetPercentage(100F);
return one;
}
private void runRollout(final MgmtDistributionSet set, final Scenario scenario) {
LOGGER.info("Run rollout for set {}", set.getDsId());
// create a Rollout
@@ -256,6 +281,17 @@ public class ConfigurableScenario {
LOGGER.info("Run rollout for set {} -> Done", set.getDsId());
}
private void waitUntilRolloutNoLongerExists(final Long id) {
do {
try {
TimeUnit.SECONDS.sleep(5);
} catch (final InterruptedException e) {
LOGGER.warn("Interrupted!");
Thread.currentThread().interrupt();
}
} while (rolloutResource.getRollout(id).getStatusCode() != HttpStatus.NOT_FOUND);
}
private void waitUntilRolloutIsComplete(final Long id) {
do {
try {

View File

@@ -67,7 +67,7 @@ public class MgmtUiAutoConfiguration {
* UI has an own strategy.
*
* @param applicationContext
* the context to add the listener
* the context to add the listener to
* @param executorService
* the general scheduler service
* @param eventBus

View File

@@ -16,6 +16,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.support.locks.DefaultLockRegistry;
import org.springframework.integration.support.locks.LockRegistry;
/**
* Auto-Configuration for enabling JPA repository.
@@ -36,4 +38,9 @@ public class JpaRepositoryAutoConfiguration {
return new VirtualPropertyResolver();
}
@Bean
@ConditionalOnMissingBean
public LockRegistry lockRegistry() {
return new DefaultLockRegistry();
}
}

View File

@@ -13,7 +13,6 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
@@ -101,16 +100,16 @@ public final class DataConversionHelper {
}
static DdiControllerBase fromTarget(final Target target, final Optional<Action> action,
static DdiControllerBase fromTarget(final Target target, final Action action,
final String defaultControllerPollTime, final TenantAware tenantAware) {
final DdiControllerBase result = new DdiControllerBase(
new DdiConfig(new DdiPolling(defaultControllerPollTime)));
if (action.isPresent()) {
if (action.get().isCancelingOrCanceled()) {
if (action != null) {
if (action.isCancelingOrCanceled()) {
result.add(linkTo(
methodOn(DdiRootController.class, tenantAware.getCurrentTenant()).getControllerCancelAction(
tenantAware.getCurrentTenant(), target.getControllerId(), action.get().getId()))
tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()))
.withRel(DdiRestConstants.CANCEL_ACTION));
} else {
// we need to add the hashcode here of the actionWithStatus
@@ -120,7 +119,7 @@ public final class DataConversionHelper {
// response because of eTags.
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
.getControllerBasedeploymentAction(tenantAware.getCurrentTenant(), target.getControllerId(),
action.get().getId(), calculateEtag(action.get())))
action.getId(), calculateEtag(action)))
.withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION));
}
}

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -88,7 +89,8 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} else {
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash());
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
// we set a download status only if we are aware of the
// targetid, i.e. authenticated and not anonymous

View File

@@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException;
import org.eclipse.hawkbit.repository.model.Action;
@@ -130,7 +131,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
return new ResponseEntity<>(DataConversionHelper.fromTarget(target,
controllerManagement.findOldestActiveActionByTarget(controllerId),
controllerManagement.findOldestActiveActionByTarget(controllerId).orElse(null),
controllerManagement.getPollingTime(), tenantAware), HttpStatus.OK);
}
@@ -156,7 +157,8 @@ public class DdiRootController implements DdiRootControllerRestApi {
@SuppressWarnings("squid:S3655")
final Artifact artifact = module.getArtifactByFilename(fileName).get();
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash());
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {

View File

@@ -65,7 +65,7 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
savedTarget.getTargetInfo().getControllerAttributes().put("dsafsdf", "sdsds");
final Target updateControllerAttributes = controllerManagament.updateControllerAttributes(
final Target updateControllerAttributes = controllerManagement.updateControllerAttributes(
savedTarget.getControllerId(), savedTarget.getTargetInfo().getControllerAttributes());
// request controller attributes need to be false because we don't want
// to request the

View File

@@ -201,10 +201,9 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService {
checkIfArtifactIsAssignedToTarget(secruityToken, sha1Hash);
final Artifact artifact = convertDbArtifact(artifactManagement.loadArtifactBinary(sha1Hash));
if (artifact == null) {
throw new EntityNotFoundException();
}
final Artifact artifact = convertDbArtifact(artifactManagement.loadArtifactBinary(sha1Hash).orElseThrow(
() -> new EntityNotFoundException(org.eclipse.hawkbit.repository.model.Artifact.class, sha1Hash)));
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1

View File

@@ -33,6 +33,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignment
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@@ -50,6 +52,8 @@ import org.springframework.context.event.EventListener;
*/
public class AmqpMessageDispatcherService extends BaseAmqpService {
private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageDispatcherService.class);
private final ArtifactUrlHandler artifactUrlHandler;
private final AmqpSenderService amqpSenderService;
private final SystemSecurityContext systemSecurityContext;
@@ -102,6 +106,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return;
}
LOG.debug("targetAssignDistributionSet retrieved for controller {}. I will forward it to DMF broker.",
assignedEvent.getControllerId());
sendUpdateMessageToTarget(assignedEvent.getTenant(),
targetManagement.findTargetByControllerID(assignedEvent.getControllerId()).get(),
assignedEvent.getActionId(), assignedEvent.getModules());

View File

@@ -163,7 +163,7 @@ public class AmqpControllerAuthenticationTest {
final DbArtifact artifact = new DbArtifact();
artifact.setSize(ARTIFACT_SIZE);
artifact.setHashes(new DbArtifactHash(SHA1, "md5 test"));
when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(artifact);
when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(Optional.of(artifact));
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory());

View File

@@ -373,7 +373,7 @@ public class AmqpMessageHandlerServiceTest {
when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(localArtifactMock));
when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), SHA1))
.thenReturn(true);
when(artifactManagementMock.loadArtifactBinary(anyString())).thenReturn(dbArtifactMock);
when(artifactManagementMock.loadArtifactBinary(anyString())).thenReturn(Optional.of(dbArtifactMock));
when(dbArtifactMock.getArtifactId()).thenReturn("artifactId");
when(dbArtifactMock.getSize()).thenReturn(1L);
when(dbArtifactMock.getHashes()).thenReturn(new DbArtifactHash(SHA1, "md5"));

View File

@@ -105,6 +105,18 @@ public interface MgmtRolloutRestApi {
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> pause(@PathVariable("rolloutId") final Long rolloutId);
/**
* Handles the DELETE request for deleting a rollout.
*
* @param rolloutId
* the ID of the rollout to be deleted.
* @return OK response (200) if rollout could be deleted. In case of any
* exception the corresponding errors occur.
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{rolloutId}", produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> delete(@PathVariable("rolloutId") final Long rolloutId);
/**
* Handles the POST request for resuming a rollout.
*

View File

@@ -16,6 +16,7 @@ import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadArtifactRestApi;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
@@ -67,7 +68,8 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi
}
final Artifact artifact = module.getArtifact(artifactId).get();
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash());
final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
final HttpServletRequest request = requestResponseContextHolder.getHttpServletRequest();
final String ifMatch = request.getHeader("If-Match");
if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {

View File

@@ -80,9 +80,9 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
final Page<Rollout> findModulesAll;
if (rsqlParam != null) {
findModulesAll = this.rolloutManagement.findAllByPredicate(rsqlParam, pageable);
findModulesAll = this.rolloutManagement.findAllByPredicate(rsqlParam, pageable, false);
} else {
findModulesAll = this.rolloutManagement.findAll(pageable);
findModulesAll = this.rolloutManagement.findAll(pageable, false);
}
final List<MgmtRolloutResponseBody> rest = MgmtRolloutMapper.toResponseRollout(findModulesAll.getContent());
@@ -140,6 +140,12 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> delete(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.deleteRollout(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> resume(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.resumeRollout(rolloutId);

View File

@@ -16,6 +16,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -238,7 +239,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
@Step
private void retrieveAndVerifyRolloutInRunning(final Rollout rollout) throws Exception {
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts/" + rollout.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -272,7 +273,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
@Step
private void retrieveAndVerifyRolloutInReady(final Rollout rollout) throws Exception {
rolloutManagement.checkCreatingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts/" + rollout.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -330,7 +331,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
postRollout("rollout2", 5, dsA.getId(), "id==target-0001*", 10);
// Run here, because Scheduler is disabled during tests
rolloutManagement.checkCreatingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts").accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -387,7 +388,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
postRollout("rollout2", 5, dsA.getId(), "id==target*", 20);
// Run here, because Scheduler is disabled during tests
rolloutManagement.checkCreatingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts?limit=1").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -441,7 +442,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(jsonPath("status", equalTo("starting")));
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId()).accept(MediaType.APPLICATION_JSON))
@@ -467,7 +468,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
@@ -497,7 +498,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
@@ -531,7 +532,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// starting rollout - already started should lead into bad request
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
@@ -572,7 +573,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// retrieve rollout groups from created rollout - 2 groups exists
// (amountTargets / groupSize = 2)
@@ -615,7 +616,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
private void retrieveAndVerifyRolloutGroupInRunningAndScheduled(final Rollout rollout,
final RolloutGroup firstGroup, final RolloutGroup secondGroup) throws Exception {
rolloutManagement.startRollout(rollout.getId());
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -642,7 +643,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
@Step
private void retrieveAndVerifyRolloutGroupInReady(final Rollout rollout, final RolloutGroup firstGroup)
throws Exception {
rolloutManagement.checkCreatingRollouts(0);
rolloutManagement.handleRollouts();
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -748,7 +749,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
rolloutManagement.startRollout(rollout.getId());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
@@ -779,12 +780,29 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutManagement.handleRollouts();
// check if running
assertThat(doWithTimeout(() -> getRollout(rollout.getId()), this::success, 60_000, 100)).isNotNull();
}
@Test
@Description("Deletion of a rollout")
public void deleteRollout() throws Exception {
final int amountTargets = 10;
testdataFactory.createTargets(amountTargets, "rolloutDelete", "rolloutDelete");
final DistributionSet dsA = testdataFactory.createDistributionSet("");
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rolloutDelete", 4, dsA.getId(), "controllerId==rolloutDelete*");
// delete rollout
mvc.perform(delete("/rest/v1/rollouts/{rolloutid}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
assertThat(getRollout(rollout.getId()).getStatus()).isEqualTo(RolloutStatus.DELETING);
}
@Test
@Description("Testing that rollout paged list with rsql parameter")
public void getRolloutWithRSQLParam() throws Exception {
@@ -922,9 +940,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(
jsonPath("$._links.resume.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/resume"))))
.andExpect(jsonPath("$._links.groups.href",
allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups"))))
;
allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups"))));
}
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
@@ -935,16 +951,13 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
// Run here, because Scheduler is disabled during tests
rolloutManagement.fillRolloutGroupsWithTargets(rollout.getId());
rolloutManagement.handleRollouts();
return rolloutManagement.findRolloutById(rollout.getId()).get();
}
protected boolean success(final Rollout result) {
if (null != result && result.getStatus() == RolloutStatus.RUNNING) {
return true;
}
return false;
return result != null && result.getStatus() == RolloutStatus.RUNNING;
}
public Rollout getRollout(final Long rolloutId) throws Exception {

View File

@@ -162,7 +162,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
try (InputStream fileInputStream = artifactManagement
.loadArtifactBinary(
softwareManagement.findSoftwareModuleById(sm.getId()).get().getArtifacts().get(0).getSha1Hash())
.getFileInputStream()) {
.get().getFileInputStream()) {
assertTrue("Wrong artifact content",
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream));
}

View File

@@ -106,7 +106,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final int limitSize = 2;
final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actions.get(0).getId()).status(Status.FINISHED).message("test"));
final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionFields.ID.getFieldName());
@@ -1205,7 +1205,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
knownControllerAttrs.put("a", "1");
knownControllerAttrs.put("b", "2");
testdataFactory.createTarget(knownTargetId);
controllerManagament.updateControllerAttributes(knownTargetId, knownControllerAttrs);
controllerManagement.updateControllerAttributes(knownTargetId, knownControllerAttrs);
// test query target over rest resource
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/attributes"))
@@ -1246,7 +1246,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
private Target createSingleTarget(final String controllerId, final String name) {
targetManagement.createTarget(entityFactory.target().create().controllerId(controllerId).name(name)
.description(TARGET_DESCRIPTION_TEST));
return controllerManagament.updateLastTargetQuery(controllerId, null);
return controllerManagement.updateLastTargetQuery(controllerId, null);
}
/**
@@ -1261,7 +1261,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
for (int index = 0; index < amount; index++) {
final String str = String.valueOf(character);
targetManagement.createTarget(entityFactory.target().create().controllerId(str).name(str).description(str));
controllerManagament.updateLastTargetQuery(str, null);
controllerManagement.updateLastTargetQuery(str, null);
character++;
}
}

View File

@@ -15,7 +15,6 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -58,7 +57,10 @@ public interface ArtifactManagement {
*
* @return uploaded {@link Artifact}
*
* @throw ArtifactUploadFailedException if upload fails
* @throws ArtifactUploadFailedException
* if upload fails
* @throws EntityNotFoundException
* if given software module does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
Artifact createArtifact(@NotNull InputStream inputStream, @NotNull Long moduleId, final String filename,
@@ -101,16 +103,20 @@ public interface ArtifactManagement {
String providedMd5Sum, String providedSha1Sum, boolean overrideExisting, String contentType);
/**
* Garbage collects local artifact binary file if only referenced by given
* {@link Artifact} metadata object.
* Garbage collects artifact binaries if only referenced by given
* {@link SoftwareModule#getId()} or {@link SoftwareModules} that are marged
* as deleted.
*
*
* @param artifactId
* the related local artifact
* @param artifactSha1Hash
* no longer needed
* @param moduleId
* the garbage colelction call is made for
*
* @return <code>true</code> if an binary was actually garbage collected
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
boolean clearArtifactBinary(@NotNull Long artifactId);
boolean clearArtifactBinary(@NotEmpty String artifactSha1Hash, @NotNull Long moduleId);
/**
* Deletes {@link Artifact} based on given id.
@@ -144,6 +150,9 @@ public interface ArtifactManagement {
* @param softwareModuleId
* software module id.
* @return found {@link Artifact}
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
@@ -179,6 +188,9 @@ public interface ArtifactManagement {
* @param swId
* software module id
* @return Page<Artifact>
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<Artifact> findArtifactBySoftwareModule(@NotNull Pageable pageReq, @NotNull Long swId);
@@ -190,11 +202,9 @@ public interface ArtifactManagement {
* to search for
* @return loaded {@link DbArtifact}
*
* @throws ArtifactBinaryNotFoundException
* if file could not be found in store
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DOWNLOAD_ARTIFACT + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_CONTROLLER_DOWNLOAD)
DbArtifact loadArtifactBinary(@NotEmpty String sha1Hash);
Optional<DbArtifact> loadArtifactBinary(@NotEmpty String sha1Hash);
}

View File

@@ -1,61 +0,0 @@
/**
* 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.springframework.boot.context.properties.ConfigurationProperties;
/**
* Rollout Management properties.
*
*/
@ConfigurationProperties("hawkbit.autoassign")
public class AutoAssignProperties {
/**
* Autoassign scheduler configuration.
*/
public static class Scheduler {
// used by @Scheduled annotation which needs constant
public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:60000}";
/**
* Schedule where the autoassign scheduler looks necessary state changes
* in milliseconds.
*/
private long fixedDelay = 60000L;
/**
* Set to true to run the autoassign scheduler.
*/
private boolean enabled = true;
public long getFixedDelay() {
return fixedDelay;
}
public void setFixedDelay(final long fixedDelay) {
this.fixedDelay = fixedDelay;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
private final Scheduler scheduler = new Scheduler();
public Scheduler getScheduler() {
return scheduler;
}
}

View File

@@ -120,6 +120,9 @@ public interface ControllerManagement {
* @param controllerId
* identifies the target to retrieve the actions from
* @return a list of actions assigned to given target which are active
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Optional<Action> findOldestActiveActionByTarget(@NotNull String controllerId);
@@ -157,6 +160,9 @@ public interface ControllerManagement {
* of the the {@link SoftwareModule} that should be assigned to
* the target
* @return last {@link Action} for given combination
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
@@ -184,6 +190,9 @@ public interface ControllerManagement {
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash);
@@ -203,6 +212,9 @@ public interface ControllerManagement {
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotNull Long targetId, @NotEmpty String sha1Hash);

View File

@@ -149,6 +149,8 @@ public interface DeploymentManagement {
* given {@code fieldNameProvider}
* @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Long countActionsByTarget(@NotNull String rsqlParam, @NotEmpty String controllerId);
@@ -171,6 +173,9 @@ public interface DeploymentManagement {
* @param controllerId
* the target associated to the actions to count
* @return the count value of found actions associated to the target
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Long countActionsByTarget(@NotEmpty String controllerId);
@@ -309,6 +314,9 @@ public interface DeploymentManagement {
* @param controllerId
* the target associated with the actions
* @return a list of actions associated with the given target
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findActiveActionsByTarget(@NotEmpty String controllerId);
@@ -320,6 +328,9 @@ public interface DeploymentManagement {
* @param controllerId
* the target associated with the actions
* @return a list of actions associated with the given target
*
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findInActiveActionsByTarget(@NotEmpty String controllerId);

View File

@@ -21,7 +21,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -40,6 +39,9 @@ public interface RolloutGroupManagement {
* @param pageable
* the page request to sort and limit the result
* @return a page of found {@link RolloutGroup}s
*
* @throws EntityNotFoundException
* of rollout with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(@NotNull Long rolloutId, @NotNull Pageable pageable);
@@ -52,14 +54,16 @@ public interface RolloutGroupManagement {
* distribution set we do not create an action for it but the target is in
* the result list of the rollout-group.
*
* @param pageRequest
* @param pageable
* the page request to sort and limit the result
* @param rolloutGroupId
* rollout group
* @return {@link TargetWithActionStatus} target with action status
* @throws EntityNotFoundException
* if rollout group with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ)
Page<TargetWithActionStatus> findAllTargetsWithActionStatus(@NotNull PageRequest pageRequest,
Page<TargetWithActionStatus> findAllTargetsWithActionStatus(@NotNull Pageable pageable,
@NotNull Long rolloutGroupId);
/**
@@ -165,6 +169,9 @@ public interface RolloutGroupManagement {
* @param rolloutGroupId
* the rollout group id for the count
* @return the target rollout group count
*
* @throws EntityNotFoundException
* if rollout group with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Long countTargetsOfRolloutsGroup(@NotNull Long rolloutGroupId);

View File

@@ -29,6 +29,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.model.Target;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -44,72 +45,37 @@ import org.springframework.util.concurrent.ListenableFuture;
public interface RolloutManagement {
/**
* Checking running rollouts. Rollouts which are checked updating the
* lastCheck to indicate that the current instance is handling the specific
* rollout. This code should run as system-code.
*
* <pre>
* {@code
* SystemSecurityContext.runAsSystem(new Callable<Void>() {
* public Void call() throws Exception {
* //run system-code
* }
* });
* }
* </pre>
*
* This method is intended to be called by a scheduler. And must be running
* in an transaction so it's splitted from the scheduler.
*
* Rollouts which are currently running are investigated, by means the
* error- and finish condition of running groups in this rollout are
* evaluated.
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
* Process rollout based on its current {@link Rollout#getStatus()}.
*
* For {@link RolloutStatus#CREATING} that means creating the
* {@link RolloutGroup}s with {@link Target}s and when finished switch to
* {@link RolloutStatus#READY}.
*
* For {@link RolloutStatus#READY} that means switching to
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set
* and time of calling this method is beyond this point in time. This auto
* start mechanism is optional. Call {@link #startRollout(Long)} otherwise.
*
* For {@link RolloutStatus#STARTING} that means starting the first
* {@link RolloutGroup}s in line and when finished switch to
* {@link RolloutStatus#RUNNING}.
*
* For {@link RolloutStatus#RUNNING} that means checking to activate further
* groups based on the defined thresholds. Switched to
* {@link RolloutStatus#FINISHED} is all groups are finished.
*
* For {@link RolloutStatus#DELETING} that means either soft delete in case
* rollout was already {@link RolloutStatus#RUNNING} which results in status
* change {@link RolloutStatus#DELETED} or hard delete from the persistence
* otherwise.
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkRunningRollouts(long delayBetweenChecks);
void handleRollouts();
/**
* Checking Rollouts that are currently being created with asynchronous
* assignment of targets to the Rollout Groups.
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkCreatingRollouts(long delayBetweenChecks);
/**
* Checking Rollouts that are currently being started with asynchronous
* creation of actions to the targets of a group.
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkStartingRollouts(long delayBetweenChecks);
/**
* Checking Rollouts that are currently ready for an auto start.
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkReadyRollouts(long delayBetweenChecks);
/**
* Counts all {@link Rollout}s in the repository.
* Counts all {@link Rollout}s in the repository that are not marked as
* deleted.
*
* @return number of roll outs
*/
@@ -212,44 +178,30 @@ public interface RolloutManagement {
ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(List<RolloutGroupCreate> groups,
String targetFilter, Long createdAt);
/**
* Can be called on a Rollout in {@link RolloutStatus#CREATING} to
* automatically fill it with targets.
*
* Works through all Rollout groups in {@link RolloutGroupStatus#CREATING}
* and fills them with remaining targets until the supposed amount of
* targets for the group is reached. Targets are added to a group when they
* match the overall {@link Rollout#getTargetFilterQuery()} and the
* {@link RolloutGroup#getTargetFilterQuery()} and not more than
* {@link RolloutGroup#getTargetPercentage()} are assigned to the group.
*
* @param rollout
* the rollout
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void fillRolloutGroupsWithTargets(@NotNull Long rollout);
/**
* Retrieves all rollouts.
*
* @param pageable
* the page request to sort and limit the result
* @param deleted
* flag if deleted rollouts should be included
* @return a page of found rollouts
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Page<Rollout> findAll(@NotNull Pageable pageable);
Page<Rollout> findAll(@NotNull Pageable pageable, boolean deleted);
/**
* Get count of targets in different status in rollout.
*
* @param pageable
* the page request to sort and limit the result
* @param deleted
* flag if deleted rollouts should be included
* @return a list of rollouts with details of targets count for different
* statuses
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Page<Rollout> findAllRolloutsWithDetailedStatus(@NotNull Pageable pageable);
Page<Rollout> findAllRolloutsWithDetailedStatus(@NotNull Pageable pageable, boolean deleted);
/**
* Retrieves all rollouts found by the given specification.
@@ -258,6 +210,8 @@ public interface RolloutManagement {
* the specification to filter rollouts
* @param pageable
* the page request to sort and limit the result
* @param deleted
* flag if deleted rollouts should be included
* @return a page of found rollouts
*
* @throws RSQLParameterUnsupportedFieldException
@@ -267,7 +221,7 @@ public interface RolloutManagement {
* if the RSQL syntax is wrong
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Page<Rollout> findAllByPredicate(@NotNull String rsqlParam, @NotNull Pageable pageable);
Page<Rollout> findAllByPredicate(@NotNull String rsqlParam, @NotNull Pageable pageable, boolean deleted);
/**
* Finds rollouts by given text in name or description.
@@ -276,11 +230,14 @@ public interface RolloutManagement {
* the page request to sort and limit the result
* @param searchText
* search text which matches name or description of rollout
* @param deleted
* flag if deleted rollouts should be included
* @return the founded rollout or {@code null} if rollout with given ID does
* not exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
Slice<Rollout> findRolloutWithDetailedStatusByFilters(@NotNull Pageable pageable, @NotEmpty String searchText);
Slice<Rollout> findRolloutWithDetailedStatusByFilters(@NotNull Pageable pageable, @NotEmpty String searchText,
boolean deleted);
/**
* Retrieves a specific rollout by its ID.
@@ -309,6 +266,8 @@ public interface RolloutManagement {
*
* @param rolloutId
* rollout id
* @param deleted
* flag if deleted rollouts should be included
* @return rollout details of targets count for different statuses
*
*
@@ -417,4 +376,15 @@ public interface RolloutManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
Rollout updateRollout(@NotNull RolloutUpdate update);
/**
* Deletes a rollout. A rollout might be deleted asynchronously by
* indicating the rollout by {@link RolloutStatus#DELETING}
*
*
* @param rolloutId
* the ID of the rollout to be deleted
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void deleteRollout(long rolloutId);
}

View File

@@ -1,88 +0,0 @@
/**
* 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.springframework.boot.context.properties.ConfigurationProperties;
/**
* Rollout Management properties.
*
*/
@ConfigurationProperties("hawkbit.rollout")
public class RolloutProperties {
// used by @Scheduled annotation which needs constant
public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:30000}";
// used by @Scheduled annotation which needs constant
public static final String PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.creatingScheduler.fixedDelay:2000}";
// used by @Scheduled annotation which needs constant
public static final String PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.startingScheduler.fixedDelay:2000}";
// used by @Scheduled annotation which needs constant
public static final String PROP_READY_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.readyScheduler.fixedDelay:30000}";
/**
* Rollout scheduler configuration.
*/
public static class Scheduler {
/**
* Schedule where the rollout scheduler looks necessary state changes in
* milliseconds.
*/
private long fixedDelay;
private boolean enabled = true;
public Scheduler(final long fixedDelay) {
this.fixedDelay = fixedDelay;
}
public long getFixedDelay() {
return fixedDelay;
}
public void setFixedDelay(final long fixedDelay) {
this.fixedDelay = fixedDelay;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
private final Scheduler scheduler = new Scheduler(30000L);
private final Scheduler creatingScheduler = new Scheduler(2000L);
private final Scheduler startingScheduler = new Scheduler(2000L);
private final Scheduler readyScheduler = new Scheduler(30000L);
public Scheduler getScheduler() {
return scheduler;
}
public Scheduler getCreatingScheduler() {
return creatingScheduler;
}
public Scheduler getStartingScheduler() {
return startingScheduler;
}
public Scheduler getReadyScheduler() {
return readyScheduler;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.event.remote;
import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent;
import org.eclipse.hawkbit.repository.model.Rollout;
/**
*
* Defines the remote event of deleting a {@link Rollout}.
*/
public class RolloutDeletedEvent extends RemoteIdEvent {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public RolloutDeletedEvent() {
// for serialization libs like jackson
}
/**
* Constructor for json serialization.
*
* @param tenant
* the tenant
* @param entityId
* the entity id
* @param entityClass
* the entity class
* @param applicationId
* the origin application id
*/
public RolloutDeletedEvent(final String tenant, final Long entityId, final String entityClass,
final String applicationId) {
super(entityId, tenant, entityClass, applicationId);
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import org.eclipse.hawkbit.repository.model.Action;
/**
* Defines the remote event of creating a new {@link Action}.
*/
public abstract class AbstractActionEvent extends RemoteEntityEvent<Action> {
private static final long serialVersionUID = 1L;
private Long rolloutId;
private Long rolloutGroupId;
/**
* Default constructor.
*/
public AbstractActionEvent() {
// for serialization libs like jackson
}
/**
* Constructor
*
* @param action
* the created action
* @param rolloutId
* rollout identifier (optional)
* @param rolloutGroupId
* rollout group identifier (optional)
* @param applicationId
* the origin application id
*/
public AbstractActionEvent(final Action action, final Long rolloutId, final Long rolloutGroupId,
final String applicationId) {
super(action, applicationId);
this.rolloutId = rolloutId;
this.rolloutGroupId = rolloutGroupId;
}
public Long getRolloutId() {
return rolloutId;
}
public Long getRolloutGroupId() {
return rolloutGroupId;
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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.event.remote.entity;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
* TenantAwareEvent definition which is been published in case a rollout group
* has been created for a specific rollout or updated.
*
*/
public abstract class AbstractRolloutGroupEvent extends RemoteEntityEvent<RolloutGroup> {
private static final long serialVersionUID = 1L;
private Long rolloutId;
/**
* Default constructor.
*/
public AbstractRolloutGroupEvent() {
// for serialization libs like jackson
}
public AbstractRolloutGroupEvent(final RolloutGroup rolloutGroup, final Long rolloutId,
final String applicationId) {
super(rolloutGroup, applicationId);
this.rolloutId = rolloutId;
}
public Long getRolloutId() {
return rolloutId;
}
}

View File

@@ -13,8 +13,8 @@ import org.eclipse.hawkbit.repository.model.Action;
/**
* Defines the remote event of creating a new {@link Action}.
*/
public class ActionCreatedEvent extends RemoteEntityEvent<Action> {
private static final long serialVersionUID = 1L;
public class ActionCreatedEvent extends AbstractActionEvent {
private static final long serialVersionUID = 2L;
/**
* Default constructor.
@@ -28,11 +28,16 @@ public class ActionCreatedEvent extends RemoteEntityEvent<Action> {
*
* @param action
* the created action
* @param rolloutId
* rollout identifier (optional)
* @param rolloutGroupId
* rollout group identifier (optional)
* @param applicationId
* the origin application id
*/
public ActionCreatedEvent(final Action action, final String applicationId) {
super(action, applicationId);
public ActionCreatedEvent(final Action action, final Long rolloutId, final Long rolloutGroupId,
final String applicationId) {
super(action, rolloutId, rolloutGroupId, applicationId);
}
}

View File

@@ -13,8 +13,8 @@ import org.eclipse.hawkbit.repository.model.Action;
/**
* Defines the remote event of updated a {@link Action}.
*/
public class ActionUpdatedEvent extends RemoteEntityEvent<Action> {
private static final long serialVersionUID = 1L;
public class ActionUpdatedEvent extends AbstractActionEvent {
private static final long serialVersionUID = 2L;
/**
* Default constructor.
@@ -28,11 +28,16 @@ public class ActionUpdatedEvent extends RemoteEntityEvent<Action> {
*
* @param action
* the updated action
* @param rolloutId
* rollout identifier (optional)
* @param rolloutGroupId
* rollout group identifier (optional)
* @param applicationId
* the origin application id
*/
public ActionUpdatedEvent(final Action action, final String applicationId) {
super(action, applicationId);
public ActionUpdatedEvent(final Action action, final Long rolloutId, final Long rolloutGroupId,
final String applicationId) {
super(action, rolloutId, rolloutGroupId, applicationId);
}
}

View File

@@ -15,12 +15,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
* has been created for a specific rollout.
*
*/
public class RolloutGroupCreatedEvent extends RemoteEntityEvent<RolloutGroup> {
public class RolloutGroupCreatedEvent extends AbstractRolloutGroupEvent {
private static final long serialVersionUID = 1L;
private Long rolloutId;
/**
* Default constructor.
*/
@@ -33,16 +30,12 @@ public class RolloutGroupCreatedEvent extends RemoteEntityEvent<RolloutGroup> {
*
* @param rolloutGroup
* the updated rolloutGroup
* @param rolloutId
* of the related rollout
* @param applicationId
* the origin application id
*/
public RolloutGroupCreatedEvent(final RolloutGroup rolloutGroup, final String applicationId) {
super(rolloutGroup, applicationId);
this.rolloutId = rolloutGroup.getRollout().getId();
public RolloutGroupCreatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId, final String applicationId) {
super(rolloutGroup, rolloutId, applicationId);
}
public Long getRolloutId() {
return rolloutId;
}
}

View File

@@ -13,9 +13,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
* Defines the remote event of updated a {@link RolloutGroup}.
*/
public class RolloutGroupUpdatedEvent extends RemoteEntityEvent<RolloutGroup> {
public class RolloutGroupUpdatedEvent extends AbstractRolloutGroupEvent {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 2L;
/**
* Default constructor.
@@ -29,11 +29,13 @@ public class RolloutGroupUpdatedEvent extends RemoteEntityEvent<RolloutGroup> {
*
* @param rolloutGroup
* the updated rolloutGroup
* @param rolloutId
* of the related rollout
* @param applicationId
* the origin application id
*/
public RolloutGroupUpdatedEvent(final RolloutGroup rolloutGroup, final String applicationId) {
super(rolloutGroup, applicationId);
public RolloutGroupUpdatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId, final String applicationId) {
super(rolloutGroup, rolloutId, applicationId);
}
}

View File

@@ -18,9 +18,6 @@ import org.eclipse.hawkbit.exception.SpServerError;
*/
public final class ArtifactUploadFailedException extends AbstractServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**

View File

@@ -19,6 +19,10 @@ import org.springframework.hateoas.Identifiable;
*/
public interface BaseEntity extends Serializable, Identifiable<Long> {
static Long getIdOrNull(final BaseEntity entity) {
return entity == null ? null : entity.getId();
}
/**
* @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity}
* was created.

View File

@@ -26,6 +26,12 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status;
*/
public interface Rollout extends NamedEntity {
/**
* @return <code>true</code> if the rollout is deleted and only kept for
* history purposes.
*/
boolean isDeleted();
/**
* @return {@link DistributionSet} that is rolled out
*/
@@ -60,11 +66,11 @@ public interface Rollout extends NamedEntity {
long getForcedTime();
/**
* @return Timestamp when the rollout should be started automatically. Can be null.
* @return Timestamp when the rollout should be started automatically. Can
* be null.
*/
Long getStartAt();
/**
* @return number of {@link Target}s in this rollout.
*/
@@ -123,9 +129,22 @@ public interface Rollout extends NamedEntity {
*/
FINISHED,
/**
* Rollout is under deletion.
*/
DELETING,
/**
* Rollout has been deleted. This state is only set in case of a
* soft-deletion of the rollout which keeps references, in case of an
* hard-deletion of a rollout the rollout-entry itself is deleted.
*/
DELETED,
/**
* Rollout could not be created due to errors, might be a database
* problem during asynchronous creating.
*
* @deprecated legacy status is not used anymore
*/
@Deprecated
@@ -134,6 +153,7 @@ public interface Rollout extends NamedEntity {
/**
* Rollout could not be started due to errors, might be database problem
* during asynchronous starting.
*
* @deprecated legacy status is not used anymore
*/
@Deprecated

View File

@@ -15,6 +15,7 @@ import java.util.Optional;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
@@ -91,12 +92,13 @@ public class EventType {
// download
TYPES.put(20, DownloadProgressEvent.class);
TYPES.put(21, SoftwareModuleCreatedEvent.class);
TYPES.put(22, SoftwareModuleDeletedEvent.class);
TYPES.put(23, SoftwareModuleUpdatedEvent.class);
TYPES.put(24, TargetPollEvent.class);
TYPES.put(25, RolloutDeletedEvent.class);
}
private int value;

View File

@@ -0,0 +1,175 @@
/**
* 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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.ConstraintDeclarationException;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Core functionality for {@link RolloutManagement} implementations.
*
*/
public abstract class AbstractRolloutManagement implements RolloutManagement {
protected final TargetManagement targetManagement;
protected final DeploymentManagement deploymentManagement;
protected final RolloutGroupManagement rolloutGroupManagement;
protected final DistributionSetManagement distributionSetManagement;
protected final ApplicationContext context;
protected final ApplicationEventPublisher eventPublisher;
protected final VirtualPropertyReplacer virtualPropertyReplacer;
protected final PlatformTransactionManager txManager;
protected final TenantAware tenantAware;
protected final LockRegistry lockRegistry;
protected AbstractRolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement;
this.rolloutGroupManagement = rolloutGroupManagement;
this.distributionSetManagement = distributionSetManagement;
this.context = context;
this.eventPublisher = eventPublisher;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.txManager = txManager;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
}
protected int runInNewTransaction(final String transactionName, final TransactionCallback<Integer> action) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(transactionName);
def.setReadOnly(false);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value());
return new TransactionTemplate(txManager, def).execute(action);
}
protected RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), targetManagement::countTargetByTargetFilterQuery));
long unusedTargetsCount = 0;
for (int i = 0; i < groups.size(); i++) {
final RolloutGroup group = groups.get(i);
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group);
RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage());
final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter);
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i,
targetFilterCounts);
final long realTargetsInGroup;
// Assume that targets which were not used in the previous groups
// are used in this group
if (overlappingTargets > 0 && unusedTargetsCount > 0) {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount;
unusedTargetsCount = 0;
} else {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
}
final long reducedTargetsInGroup = Math
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
groupTargetCounts.add(reducedTargetsInGroup);
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
}
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
}
private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
// there can't be overlapping targets in the first group
if (groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter,
previousGroups, group);
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
return targetFilterCounts.get(overlappingTargetsFilter);
} else {
final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
return overlappingTargets;
}
}
protected long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
final Long createdAt) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets);
return totalTargets - validation.getTargetsInGroups();
}
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
final String targetFilter, final Long createdAt) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
return new AsyncResult<>(validateTargetsInGroups(
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
}
}

View File

@@ -6,34 +6,23 @@
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
package org.eclipse.hawkbit.repository;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
/**
* A collection of static helper methods for the {@link JpaRolloutManagement}
* A collection of static helper methods for the {@link RolloutManagement}
*/
final class RolloutHelper {
public final class RolloutHelper {
private RolloutHelper() {
}
@@ -43,7 +32,7 @@ final class RolloutHelper {
* @param conditions
* input conditions and actions
*/
static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) {
public static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) {
if (conditions.getSuccessCondition() == null) {
throw new ConstraintViolationException("Rollout group is missing success condition");
}
@@ -60,7 +49,7 @@ final class RolloutHelper {
* the input group
* @return the verified group
*/
static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) {
public static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) {
if (group.getTargetPercentage() < 1F || group.getTargetPercentage() > 100F) {
throw new ConstraintViolationException("Target percentage has to be between 1 and 100");
}
@@ -74,55 +63,13 @@ final class RolloutHelper {
return group;
}
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
* @param create
* group to check
* @param conditions
* default conditions and actions
*/
static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
/**
* Verify if the supplied amount of groups is in range
*
* @param amountGroup
* amount of groups
*/
static void verifyRolloutGroupParameter(final int amountGroup) {
public static void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new ConstraintViolationException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
@@ -136,7 +83,7 @@ final class RolloutHelper {
* @param percentage
* the percentage
*/
static void verifyRolloutGroupTargetPercentage(final float percentage) {
public static void verifyRolloutGroupTargetPercentage(final float percentage) {
if (percentage <= 0) {
throw new ConstraintViolationException("the percentage must be greater than zero");
} else if (percentage > 100) {
@@ -152,7 +99,7 @@ final class RolloutHelper {
* Rollout to derive the filter from
* @return resulting target filter query
*/
static String getTargetFilterQuery(final Rollout rollout) {
public static String getTargetFilterQuery(final Rollout rollout) {
return getTargetFilterQuery(rollout.getTargetFilterQuery(), rollout.getCreatedAt());
}
@@ -164,7 +111,7 @@ final class RolloutHelper {
* @return a target filter query that only matches targets that were created
* after the provided timestamp.
*/
static String getTargetFilterQuery(final String targetFilter, final Long createdAt) {
public static String getTargetFilterQuery(final String targetFilter, final Long createdAt) {
if (createdAt != null) {
return targetFilter + ";createdat=le=" + createdAt.toString();
}
@@ -179,7 +126,7 @@ final class RolloutHelper {
* @param status
* the Status
*/
static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) {
public static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) {
if (!rollout.getStatus().equals(status)) {
throw new RolloutIllegalStateException("Rollout is not in status " + status.toString());
}
@@ -197,7 +144,7 @@ final class RolloutHelper {
* the group to add
* @return list of groups
*/
static List<Long> getGroupsByStatusIncludingGroup(final Rollout rollout,
public static List<Long> getGroupsByStatusIncludingGroup(final Rollout rollout,
final RolloutGroup.RolloutGroupStatus status, final RolloutGroup group) {
return rollout.getRolloutGroups().stream()
.filter(innerGroup -> innerGroup.getStatus().equals(status) || innerGroup.equals(group))
@@ -211,7 +158,7 @@ final class RolloutHelper {
* the rollout
* @return ordered list of groups
*/
static List<RolloutGroup> getOrderedGroups(final Rollout rollout) {
public static List<RolloutGroup> getOrderedGroups(final Rollout rollout) {
return rollout.getRolloutGroups().stream().sorted((group1, group2) -> {
if (group1.getId() < group2.getId()) {
return -1;
@@ -232,7 +179,7 @@ final class RolloutHelper {
* @return RSQL string without base filter of the Rollout. Can be an empty
* string.
*/
static String getAllGroupsTargetFilter(final List<RolloutGroup> groups) {
public static String getAllGroupsTargetFilter(final List<RolloutGroup> groups) {
if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) {
return "";
}
@@ -254,7 +201,7 @@ final class RolloutHelper {
* @return RSQL string without base filter of the Rollout. Can be an empty
* string.
*/
static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List<RolloutGroup> groups,
public static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group) {
final String groupFilter = group.getTargetFilterQuery();
// when any previous group has the same filter as the target group the
@@ -283,7 +230,7 @@ final class RolloutHelper {
&& prevGroup.getTargetFilterQuery().equals(groupFilter));
}
private static String concatAndTargetFilters(String... filters) {
private static String concatAndTargetFilters(final String... filters) {
return "(" + Arrays.stream(filters).collect(Collectors.joining(");(")) + ")";
}
@@ -308,7 +255,7 @@ final class RolloutHelper {
* @param targetCount
* the count of left targets
*/
static void verifyRemainingTargets(final long targetCount) {
public static void verifyRemainingTargets(final long targetCount) {
if (targetCount > 0) {
throw new ConstraintViolationException(
"Rollout groups don't match all targets that are targeted by the rollout");
@@ -318,35 +265,11 @@ final class RolloutHelper {
}
}
/**
* @param searchText
* search string
* @return criteria specification with a query for name or description of a
* rollout
*/
static Specification<JpaRollout> likeNameOrDescription(final String searchText) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
searchTextToLower));
};
}
static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
public static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
if (!(Rollout.RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
+ rollout.getStatus().name().toLowerCase());
}
}
static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
}
}

View File

@@ -195,6 +195,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* the status which the actions should not have
* @return the found list of {@link Action}s
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
@Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1 AND a.status != ?2")
List<JpaAction> findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(
Collection<Long> targetIds, Action.Status notStatus);
@@ -264,6 +265,44 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/
Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status);
/**
* Counts all actions referring to a given rollout and status.
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @param status
* the status the actions should have
* @return the count of actions referring to a rollout and are in a given
* status
*/
Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status);
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @return {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId")
boolean existsByRolloutId(@Param("rolloutId") Long rolloutId);
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*
* @param rolloutId
* the ID of the rollout the actions belong to
* @param status
* the action is not to be in
* @return {@code true} if actions for the given rollout exists, otherwise
* {@code false}
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status")
boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
/**
* Retrieving all actions referring to a given rollout with a specific
* action as parent reference and a specific status.
@@ -281,6 +320,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
Page<Action> findByRolloutAndRolloutGroupParentAndStatus(Pageable pageable, JpaRollout rollout,
JpaRolloutGroup rolloutGroupParent, Status actionStatus);
@@ -296,19 +336,22 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
Page<Action> findByRolloutAndRolloutGroupParentIsNullAndStatus(Pageable pageable, JpaRollout rollout,
Status actionStatus);
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
*
* @param pageable
* page parameters
* @param rolloutId
* the rollout the actions beglong to
* @param actionStatus
* the status of the actions
* @return the actions referring a specific rollout an in a specific status
*/
List<Action> findByRolloutIdAndStatus(Long rolloutId, Status actionStatus);
Page<JpaAction> findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus);
/**
* Get list of objects which has details of status and count of targets in
@@ -354,4 +397,15 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
/**
* Deletes all actions with the given IDs.
*
* @param actionIDs
* the IDs of the actions to be deleted.
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM JpaAction a WHERE a.id IN ?1")
void deleteByIdIn(final Collection<Long> actionIDs);
}

View File

@@ -12,7 +12,6 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
@@ -72,11 +71,11 @@ public interface DistributionSetRepository
* Finds {@link DistributionSet}s where given {@link SoftwareModule} is
* assigned.
*
* @param module
* @param moduleId
* to search for
* @return {@link List} of found {@link DistributionSet}s
*/
Long countByModules(JpaSoftwareModule module);
Long countByModulesId(Long moduleId);
/**
* Finds {@link DistributionSet}s based on given ID that are assigned yet to

View File

@@ -17,7 +17,6 @@ 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.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -104,23 +103,16 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public boolean clearArtifactBinary(final Long artifactId) {
return clearArtifactBinary(localArtifactRepository.findById(artifactId)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId)));
}
public boolean clearArtifactBinary(final String sha1Hash, final Long moduleId) {
private boolean clearArtifactBinary(final JpaArtifact existing) {
for (final Artifact lArtifact : localArtifactRepository.findBySha1Hash(existing.getSha1Hash())) {
if (!lArtifact.getSoftwareModule().isDeleted()
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
return false;
}
if (localArtifactRepository.existsWithSha1HashAndSoftwareModuleIdIsNot(sha1Hash, moduleId)) {
// there are still other artifacts that need the binary
return false;
}
try {
LOG.debug("deleting artifact from repository {}", existing.getSha1Hash());
artifactRepository.deleteBySha1(existing.getSha1Hash());
LOG.debug("deleting artifact from repository {}", sha1Hash);
artifactRepository.deleteBySha1(sha1Hash);
return true;
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
@@ -134,7 +126,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
final JpaArtifact existing = (JpaArtifact) findArtifact(id)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, id));
clearArtifactBinary(existing);
clearArtifactBinary(existing.getSha1Hash(), existing.getSoftwareModule().getId());
((JpaSoftwareModule) existing.getSoftwareModule()).removeArtifact(existing);
softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule());
@@ -148,6 +140,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
public Optional<Artifact> findByFilenameAndSoftwareModule(final String filename, final Long softwareModuleId) {
throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId);
return localArtifactRepository.findFirstByFilenameAndSoftwareModuleId(filename, softwareModuleId);
}
@@ -163,13 +157,20 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
public Page<Artifact> findArtifactBySoftwareModule(final Pageable pageReq, final Long swId) {
throwExceptionIfSoftwareModuleDoesNotExist(swId);
return localArtifactRepository.findBySoftwareModuleId(pageReq, swId);
}
private void throwExceptionIfSoftwareModuleDoesNotExist(final Long swId) {
if (!softwareModuleRepository.exists(swId)) {
throw new EntityNotFoundException(SoftwareModule.class, swId);
}
}
@Override
public DbArtifact loadArtifactBinary(final String sha1Hash) {
return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash))
.orElseThrow(() -> new ArtifactBinaryNotFoundException(sha1Hash));
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash) {
return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash));
}
private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,

View File

@@ -46,6 +46,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
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.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -85,6 +86,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired
private TargetRepository targetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TargetManagement targetManagement;
@@ -140,6 +144,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Override
public Optional<Action> getActionForDownloadByTargetAndSoftwareModule(final String controllerId,
final Long moduleId) {
throwExceptionIfTargetDoesNotExist(controllerId);
throwExceptionIfSoftwareModuleDoesNotExist(moduleId);
final List<Action> action = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId);
if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) {
@@ -149,26 +156,40 @@ public class JpaControllerManagement implements ControllerManagement {
return Optional.ofNullable(action.get(0));
}
private void throwExceptionIfTargetDoesNotExist(final String controllerId) {
if (!targetRepository.existsByControllerId(controllerId)) {
throw new EntityNotFoundException(Target.class, controllerId);
}
}
private void throwExceptionIfTargetDoesNotExist(final Long targetId) {
if (!targetRepository.exists(targetId)) {
throw new EntityNotFoundException(Target.class, targetId);
}
}
private void throwExceptionIfSoftwareModuleDoesNotExist(final Long moduleId) {
if (!softwareModuleRepository.exists(moduleId)) {
throw new EntityNotFoundException(SoftwareModule.class, moduleId);
}
}
@Override
public boolean hasTargetArtifactAssigned(final String controllerId, final String sha1Hash) {
final Optional<Target> target = targetRepository.findByControllerId(controllerId);
if (!target.isPresent()) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target.get(), sha1Hash)) > 0;
throwExceptionIfTargetDoesNotExist(controllerId);
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(controllerId, sha1Hash)) > 0;
}
@Override
public boolean hasTargetArtifactAssigned(final Long targetId, final String sha1Hash) {
final Target target = targetRepository.findOne(targetId);
if (target == null) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, sha1Hash)) > 0;
throwExceptionIfTargetDoesNotExist(targetId);
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(targetId, sha1Hash)) > 0;
}
@Override
public Optional<Action> findOldestActiveActionByTarget(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId);
// used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to
// DATAJPA-841 issue.
return actionRepository.findFirstByTargetControllerIdAndActive(new Sort(Direction.ASC, "id"), controllerId,

View File

@@ -225,7 +225,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final List<JpaTarget> 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());
.flatMap(List::stream).collect(Collectors.toList());
if (targets.isEmpty()) {
// detaching as it is not necessary to persist the set itself
@@ -456,6 +456,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final RolloutGroup rolloutGroupParent, final int limit) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("startScheduledActions");
def.setReadOnly(false);
def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value());
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(status -> {
final Page<Action> rolloutGroupActions = findActionsByRolloutAndRolloutGroupParent(rollout,
@@ -599,24 +601,38 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Override
public List<Action> findActiveActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.findByActiveAndTarget(controllerId, true);
}
@Override
public List<Action> findInActiveActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.findByActiveAndTarget(controllerId, false);
}
@Override
public Long countActionsByTarget(final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.countByTargetControllerId(controllerId);
}
@Override
public Long countActionsByTarget(final String rsqlParam, final String controllerId) {
throwExceptionIfTargetFoesNotExist(controllerId);
return actionRepository.count(createSpecificationFor(controllerId, rsqlParam));
}
private void throwExceptionIfTargetFoesNotExist(final String controllerId) {
if (!targetRepository.existsByControllerId(controllerId)) {
throw new EntityNotFoundException(Target.class, controllerId);
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)

View File

@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
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.Target;
@@ -48,7 +49,6 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
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.transaction.annotation.Isolation;
@@ -65,6 +65,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private ActionRepository actionRepository;
@@ -115,9 +118,19 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
@Override
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable pageable) {
if (!rolloutRepository.exists(rolloutId)) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
final Page<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, pageable);
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(RolloutGroup::getId)
.collect(Collectors.toList());
if (rolloutGroupIds.isEmpty()) {
// groups might already deleted, so return empty list.
return new PageImpl<>(Collections.emptyList());
}
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
rolloutGroupIds);
@@ -192,8 +205,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}
@Override
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final Pageable pageRequest,
final Long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
@@ -218,11 +232,14 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
.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);
}
@Override
public Long countTargetsOfRolloutsGroup(@NotNull final Long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.from(RolloutTargetGroup.class);
@@ -231,4 +248,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return entityManager.createQuery(countQuery).getSingleResult();
}
private void throwExceptionIfRolloutGroupDoesNotExist(final Long rolloutGroupId) {
if (!rolloutGroupRepository.exists(rolloutGroupId)) {
throw new EntityNotFoundException(RolloutGroup.class, rolloutGroupId);
}
}
}

View File

@@ -0,0 +1,106 @@
/**
* 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.Collections;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
/**
* A collection of static helper methods for the {@link JpaRolloutManagement}
*/
final class JpaRolloutHelper {
private JpaRolloutHelper() {
}
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
* @param create
* group to check
* @param conditions
* default conditions and actions
*/
static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
/**
* Builds a {@link Specification} to search a rollout by name or
* description.
*
* @param searchText
* search string
* @param isDeleted
* <code>true</code> if deleted rollouts should be included in
* the search. Otherwise <code>false</code>
* @return criteria specification with a query for name or description of a
* rollout
*/
static Specification<JpaRollout> likeNameOrDescription(final String searchText, final boolean isDeleted) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.and(criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
searchTextToLower)),
criteriaBuilder.equal(rolloutRoot.get(JpaRollout_.deleted), isDeleted));
};
}
static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
}
}

View File

@@ -8,22 +8,23 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.validation.ConstraintDeclarationException;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.AbstractRolloutManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
@@ -31,6 +32,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -43,6 +45,8 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -58,7 +62,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
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.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -71,87 +77,87 @@ 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.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
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.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
/**
* JPA implementation of {@link RolloutManagement}.
*/
@Validated
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class JpaRolloutManagement implements RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
public class JpaRolloutManagement extends AbstractRolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
/**
* Max amount of targets that are handled in one transaction.
*/
private static final int TRANSACTION_TARGETS = 1000;
private static final int TRANSACTION_TARGETS = 5_000;
/**
* Maximum amount of actions that are deleted in one transaction.
*/
private static final int TRANSACTION_ACTIONS = 5_000;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
private RolloutGroupManagement rolloutGroupManagement;
@Autowired
private DistributionSetManagement distributionSetManagement;
@Autowired
private ActionRepository actionRepository;
@Autowired
private ApplicationContext context;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Override
public Page<Rollout> findAll(final Pageable pageable) {
return RolloutHelper.convertPage(rolloutRepository.findAll(pageable), pageable);
JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry);
}
@Override
public Page<Rollout> findAllByPredicate(final String rsqlParam, final Pageable pageable) {
final Specification<JpaRollout> specification = RSQLUtility.parse(rsqlParam, RolloutFields.class,
virtualPropertyReplacer);
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
final Specification<JpaRollout> spec = RolloutSpecification.isDeleted(deleted);
return JpaRolloutHelper.convertPage(rolloutRepository.findAll(spec, pageable), pageable);
}
final Page<JpaRollout> findAll = rolloutRepository.findAll(specification, pageable);
return RolloutHelper.convertPage(findAll, pageable);
@Override
public Page<Rollout> findAllByPredicate(final String rsqlParam, final Pageable pageable, final boolean deleted) {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(RSQLUtility.parse(rsqlParam, RolloutFields.class, virtualPropertyReplacer));
specList.add(RolloutSpecification.isDeleted(deleted));
return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable);
}
/**
* Executes findAll with the given {@link Rollout} {@link Specification}s.
*/
private Page<JpaRollout> findByCriteriaAPI(final Pageable pageable,
final List<Specification<JpaRollout>> specList) {
if (specList == null || specList.isEmpty()) {
return rolloutRepository.findAll(pageable);
}
return rolloutRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
}
@Override
@@ -225,8 +231,7 @@ public class JpaRolloutManagement implements RolloutManagement {
group.setTargetPercentage(1.0F / (amountOfGroups - i) * 100);
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
savedRollout.setRolloutGroupsCreated(amountOfGroups);
@@ -240,7 +245,7 @@ public class JpaRolloutManagement implements RolloutManagement {
// Preparing the groups
final List<RolloutGroup> groups = groupList.stream()
.map(group -> RolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList());
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);
@@ -261,7 +266,7 @@ public class JpaRolloutManagement implements RolloutManagement {
if (srcGroup.getTargetFilterQuery() != null) {
group.setTargetFilterQuery(srcGroup.getTargetFilterQuery());
} else {
group.setTargetFilterQuery("");
group.setTargetFilterQuery(StringUtils.EMPTY);
}
group.setSuccessCondition(srcGroup.getSuccessCondition());
@@ -277,25 +282,20 @@ public class JpaRolloutManagement implements RolloutManagement {
group.setErrorActionExp(srcGroup.getErrorActionExp());
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
}
savedRollout.setRolloutGroupsCreated(groups.size());
return rolloutRepository.save(savedRollout);
}
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group) {
afterCommit
.afterCommit(() -> eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId())));
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
afterCommit.afterCommit(() -> eventPublisher
.publishEvent(new RolloutGroupCreatedEvent(group, rollout.getId(), context.getId())));
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void fillRolloutGroupsWithTargets(final Long rolloutId) {
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
private void handleCreateRollout(final JpaRollout rollout) {
LOGGER.debug("handleCreateRollout called for rollout {}", rollout.getId());
final List<RolloutGroup> rolloutGroups = RolloutHelper.getOrderedGroups(rollout);
int readyGroups = 0;
@@ -317,6 +317,7 @@ public class JpaRolloutManagement implements RolloutManagement {
// When all groups are ready the rollout status can be changed to be
// ready, too.
if (readyGroups == rolloutGroups.size()) {
LOGGER.debug("rollout {} creatin done. Switch to READY.", rollout.getId());
rollout.setStatus(RolloutStatus.READY);
rollout.setLastCheck(0);
rollout.setTotalTargets(totalTargets);
@@ -372,17 +373,10 @@ public class JpaRolloutManagement implements RolloutManagement {
}
}
private int runInNewCountingTransaction(final String transactionName, final TransactionCallback<Integer> action) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(transactionName);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(action);
}
private Integer assignTargetsToGroupInNewTransaction(final Rollout rollout, final RolloutGroup group,
final String targetFilter, final long limit) {
return runInNewCountingTransaction("assignTargetsToRolloutGroup", status -> {
return runInNewTransaction("assignTargetsToRolloutGroup", status -> {
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout,
RolloutGroupStatus.READY, group);
@@ -399,19 +393,6 @@ public class JpaRolloutManagement implements RolloutManagement {
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
}
private long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
final Long createdAt) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets);
return totalTargets - validation.getTargetsInGroups();
}
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
@@ -427,67 +408,12 @@ public class JpaRolloutManagement implements RolloutManagement {
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
}
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
final Map<String, Long> targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), targetManagement::countTargetByTargetFilterQuery));
long unusedTargetsCount = 0;
for (int i = 0; i < groups.size(); i++) {
final RolloutGroup group = groups.get(i);
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group);
RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage());
final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter);
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i,
targetFilterCounts);
final long realTargetsInGroup;
// Assume that targets which were not used in the previous groups
// are used in this group
if (overlappingTargets > 0 && unusedTargetsCount > 0) {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount;
unusedTargetsCount = 0;
} else {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
}
final long reducedTargetsInGroup = Math
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
groupTargetCounts.add(reducedTargetsInGroup);
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
}
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
}
private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
// there can't be overlapping targets in the first group
if (groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter,
previousGroups, group);
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
return targetFilterCounts.get(overlappingTargetsFilter);
} else {
final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
return overlappingTargets;
}
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRollout(final Long rolloutId) {
LOGGER.debug("startRollout called for rollout {}", rolloutId);
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
RolloutHelper.checkIfRolloutCanStarted(rollout, rollout);
rollout.setStatus(RolloutStatus.STARTING);
@@ -496,6 +422,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private void startFirstRolloutGroup(final Rollout rollout) {
LOGGER.debug("startFirstRolloutGroup called for rollout {}", rollout.getId());
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
@@ -516,7 +443,6 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
@@ -566,7 +492,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private Integer createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) {
return runInNewCountingTransaction("createActionsForTargets", status -> {
return runInNewTransaction("createActionsForTargets", status -> {
final PageRequest pageRequest = new PageRequest(0, limit);
final Rollout rollout = rolloutRepository.findOne(rolloutId);
final RolloutGroup group = rolloutGroupRepository.findOne(groupId);
@@ -618,7 +544,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Modifying
public void pauseRollout(final Long rolloutId) {
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
if (rollout.getStatus() != RolloutStatus.RUNNING) {
if (!RolloutStatus.RUNNING.equals(rollout.getStatus())) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
@@ -644,39 +570,27 @@ public class JpaRolloutManagement implements RolloutManagement {
rolloutRepository.save(rollout);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkRunningRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.RUNNING);
if (rolloutsToCheck.isEmpty()) {
return;
private void handleRunningRollout(final JpaRollout rollout) {
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroupsRunning.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(), rolloutGroupsRunning.size());
executeRolloutGroups(rollout, rolloutGroupsRunning);
}
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
for (final JpaRollout rollout : rolloutsToCheck) {
LOGGER.debug("Checking rollout {}", rollout);
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroupsRunning.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(), rolloutGroupsRunning.size());
executeRolloutGroups(rollout, rolloutGroupsRunning);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting FINISHED status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
}
@@ -741,7 +655,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private boolean isRolloutComplete(final JpaRollout rollout) {
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutIdAndStatusOrStatus(rollout.getId(),
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
return groupsActiveLeft == 0;
}
@@ -798,90 +712,170 @@ public class JpaRolloutManagement implements RolloutManagement {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkCreatingRollouts(final long delayBetweenChecks) {
final List<Long> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.CREATING)
.stream().map(Rollout::getId).collect(Collectors.toList());
if (rolloutsToCheck.isEmpty()) {
// No transaction, will be created per handled rollout
@Transactional(propagation = Propagation.NEVER)
public void handleRollouts() {
rolloutRepository
.findByStatusIn(Lists.newArrayList(RolloutStatus.CREATING, RolloutStatus.DELETING,
RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING))
.forEach(this::handleRollout);
}
private void handleRollout(final Long rolloutId) {
LOGGER.debug("handleRollout called for rollout {}", rolloutId);
final String tenant = tenantAware.getCurrentTenant();
final String handlerId = tenant + "-rollout-" + rolloutId;
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
return;
}
LOGGER.info("Found {} creating rollouts to check", rolloutsToCheck.size());
try {
runInNewTransaction(handlerId, status -> executeFittingHandler(rolloutId));
} finally {
lock.unlock();
}
}
rolloutsToCheck.forEach(this::fillRolloutGroupsWithTargets);
private int executeFittingHandler(final Long rolloutId) {
final JpaRollout rollout = rolloutRepository.findOne(rolloutId);
switch (rollout.getStatus()) {
case CREATING:
handleCreateRollout(rollout);
break;
case DELETING:
handleDeleteRollout(rollout);
break;
case READY:
handleReadyRollout(rollout);
break;
case STARTING:
handleStartingRollout(rollout);
break;
case RUNNING:
handleRunningRollout(rollout);
break;
default:
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break;
}
return 0;
}
private void handleStartingRollout(final Rollout rollout) {
LOGGER.debug("handleStartingRollout called for rollout {}", rollout.getId());
if (ensureAllGroupsAreScheduled(rollout)) {
startFirstRolloutGroup(rollout);
}
}
private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
LOGGER.debug(
"handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING",
rollout.getId());
startRollout(rollout.getId());
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkStartingRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks,
RolloutStatus.STARTING);
if (rolloutsToCheck.isEmpty()) {
public void deleteRollout(final long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findOne(rolloutId);
if (RolloutStatus.DELETING.equals(jpaRollout.getStatus())) {
return;
}
LOGGER.info("Found {} starting rollouts to check", rolloutsToCheck.size());
rolloutsToCheck.forEach(rollout -> {
if (ensureAllGroupsAreScheduled(rollout)) {
startFirstRolloutGroup(rollout);
}
});
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkReadyRollouts(final long delayBetweenChecks) {
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.READY);
if (rolloutsToCheck.isEmpty()) {
private void handleDeleteRollout(final JpaRollout rollout) {
LOGGER.debug("handleDeleteRollout called for {}", rollout.getId());
// check if there are actions beyond schedule
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(),
Status.SCHEDULED);
if (hardDeleteRolloutGroups) {
LOGGER.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId());
hardDeleteRollout(rollout);
return;
}
// clean up all scheduled actions
final Slice<JpaAction> scheduledActions = findScheduledActionsByRollout(rollout);
deleteScheduledActions(rollout, scheduledActions);
// avoid another scheduler round and re-check if all scheduled actions
// has been cleaned up
final boolean hasScheduledActionsLeft = findScheduledActionsByRollout(rollout).getNumberOfElements() > 0;
if (hasScheduledActionsLeft) {
return;
}
LOGGER.info("Found {} ready rollouts to check", rolloutsToCheck.size());
final long now = System.currentTimeMillis();
rolloutsToCheck.forEach(rollout -> {
if (rollout.getStartAt() != null && rollout.getStartAt() <= now) {
startRollout(rollout.getId());
}
});
}
private List<JpaRollout> getRolloutsToCheckForStatus(final long delayBetweenChecks, final RolloutStatus status) {
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, status);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}",
lastCheck, lastCheck + delayBetweenChecks);
return Collections.emptyList();
// only hard delete the rollout if no actions are left for the rollout.
// In case actions are left, they are probably are running or were
// running before, so only soft delete.
hardDeleteRolloutGroups = !actionRepository.existsByRolloutId(rollout.getId());
if (hardDeleteRolloutGroups) {
hardDeleteRollout(rollout);
return;
}
return rolloutRepository.findByLastCheckAndStatus(lastCheck, status);
// set soft delete
rollout.setStatus(RolloutStatus.DELETED);
rollout.setDeleted(true);
rolloutRepository.save(rollout);
}
private void hardDeleteRollout(final JpaRollout rollout) {
rolloutRepository.delete(rollout);
}
private void deleteScheduledActions(final JpaRollout rollout, final Slice<JpaAction> scheduledActions) {
final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0;
if (hasScheduledActions) {
try {
final Iterable<JpaAction> iterable = scheduledActions::iterator;
final List<Long> actionIds = StreamSupport.stream(iterable.spliterator(), false).map(Action::getId)
.collect(Collectors.toList());
actionRepository.deleteByIdIn(actionIds);
afterCommit.afterCommit(() -> eventPublisher.publishEvent(
new RolloutUpdatedEvent(rollout, EventPublisherHolder.getInstance().getApplicationId())));
} catch (final RuntimeException e) {
LOGGER.error("Exception during deletion of actions of rollout {}", rollout, e);
}
}
}
private Slice<JpaAction> findScheduledActionsByRollout(final JpaRollout rollout) {
return actionRepository.findByRolloutIdAndStatus(new PageRequest(0, TRANSACTION_ACTIONS), rollout.getId(),
Status.SCHEDULED);
}
@Override
public Long countRolloutsAll() {
return rolloutRepository.count();
return rolloutRepository.count(RolloutSpecification.isDeleted(false));
}
@Override
public Long countRolloutsAllByFilters(final String searchText) {
return rolloutRepository.count(RolloutHelper.likeNameOrDescription(searchText));
return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false));
}
@Override
public Slice<Rollout> findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText) {
final Specification<JpaRollout> specs = RolloutHelper.likeNameOrDescription(searchText);
final Slice<JpaRollout> findAll = criteriaNoCountDao.findAll(specs, pageable, JpaRollout.class);
public Slice<Rollout> findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText,
final boolean deleted) {
final Slice<JpaRollout> findAll = findByCriteriaAPI(pageable,
Lists.newArrayList(JpaRolloutHelper.likeNameOrDescription(searchText, deleted)));
setRolloutStatusDetails(findAll);
return RolloutHelper.convertPage(findAll, pageable);
return JpaRolloutHelper.convertPage(findAll, pageable);
}
@Override
@@ -917,10 +911,12 @@ public class JpaRolloutManagement implements RolloutManagement {
}
@Override
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable) {
final Page<JpaRollout> rollouts = rolloutRepository.findAll(pageable);
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable, final boolean deleted) {
Page<JpaRollout> rollouts;
final Specification<JpaRollout> spec = RolloutSpecification.isDeleted(deleted);
rollouts = rolloutRepository.findAll(spec, pageable);
setRolloutStatusDetails(rollouts);
return RolloutHelper.convertPage(rollouts, pageable);
return JpaRolloutHelper.convertPage(rollouts, pageable);
}
@Override
@@ -940,8 +936,12 @@ public class JpaRolloutManagement implements RolloutManagement {
}
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));
if (!rolloutIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutId(rolloutIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
return null;
}
private void setRolloutStatusDetails(final Slice<JpaRollout> rollouts) {
@@ -949,10 +949,12 @@ public class JpaRolloutManagement implements RolloutManagement {
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
for (final Rollout rollout : rollouts) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
if (allStatesForRollout != null) {
rollouts.forEach(rollout -> {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
});
}
}

View File

@@ -209,8 +209,8 @@ public class JpaSoftwareManagement implements SoftwareManagement {
return softwareModuleRepository.findOneByNameAndVersionAndTypeId(name, version, typeId);
}
private boolean isUnassigned(final JpaSoftwareModule bsmMerged) {
return distributionSetRepository.countByModules(bsmMerged) <= 0;
private boolean isUnassigned(final Long moduleId) {
return distributionSetRepository.countByModulesId(moduleId) <= 0;
}
private Slice<JpaSoftwareModule> findSwModuleByCriteriaAPI(final Pageable pageable,
@@ -225,7 +225,7 @@ public class JpaSoftwareManagement implements SoftwareManagement {
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
for (final Artifact localArtifact : swModule.getArtifacts()) {
artifactManagement.clearArtifactBinary(localArtifact.getId());
artifactManagement.clearArtifactBinary(localArtifact.getSha1Hash(), swModule.getId());
}
}
@@ -246,12 +246,9 @@ public class JpaSoftwareManagement implements SoftwareManagement {
// delete binary data of artifacts
deleteGridFsArtifacts(swModule);
if (isUnassigned(swModule)) {
softwareModuleRepository.delete(swModule);
if (isUnassigned(swModule.getId())) {
softwareModuleRepository.delete(swModule.getId());
} else {
assignedModuleIds.add(swModule.getId());
}
});

View File

@@ -218,8 +218,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant);
targetRepository.deleteByTenantIgnoreCase(tenant);
targetFilterQueryRepository.deleteByTenantIgnoreCase(tenant);
actionRepository.deleteByTenantIgnoreCase(tenant);
rolloutGroupRepository.deleteByTenantIgnoreCase(tenant);
rolloutRepository.deleteByTenantIgnoreCase(tenant);
artifactRepository.deleteByTenantIgnoreCase(tenant);
targetTagRepository.deleteByTenantIgnoreCase(tenant);

View File

@@ -607,7 +607,7 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = create.build();
if (targetRepository.findByControllerId(target.getControllerId()).isPresent()) {
if (targetRepository.existsByControllerId(target.getControllerId())) {
throw new EntityAlreadyExistsException();
}

View File

@@ -13,9 +13,11 @@ import java.util.Optional;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -54,6 +56,21 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
*/
List<Artifact> findBySha1Hash(String sha1Hash);
/**
* Verifies if an artifact exists that has given hash and is still related
* to a {@link SoftwareModule} other than a given one and not
* {@link SoftwareModule#isDeleted()}.
*
* @param sha1
* to search for
* @param moduleId
* to ignore in relationship check
*
* @return <code>true</code> if such an artifact exists
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaArtifact a WHERE a.sha1Hash = :sha1 AND a.softwareModule.id != :moduleId AND a.softwareModule.deleted = 0")
boolean existsWithSha1HashAndSoftwareModuleIdIsNot(@Param("sha1") String sha1, @Param("moduleId") Long moduleId);
/**
* Searches for a {@link Artifact} based on given gridFsFileName.
*

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa;
import java.util.Map;
import java.util.concurrent.Executor;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@@ -23,7 +24,6 @@ import org.eclipse.hawkbit.repository.ReportManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutProperties;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TagManagement;
@@ -53,6 +53,7 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -69,18 +70,23 @@ import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -101,7 +107,7 @@ import com.google.common.collect.Maps;
@EnableAspectJAutoProxy
@Configuration
@ComponentScan
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, RolloutProperties.class,
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class,
TenantConfigurationProperties.class })
@EnableScheduling
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
@@ -115,7 +121,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean
@ConditionalOnMissingBean
public RsqlValidationOracle rsqlValidationOracle() {
RsqlValidationOracle rsqlValidationOracle() {
return new RsqlParserValidationOracle();
}
@@ -127,7 +133,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return DistributionSetBuilder bean
*/
@Bean
public DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement,
DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement,
final SoftwareManagement softwareManagement) {
return new JpaDistributionSetBuilder(distributionSetManagement, softwareManagement);
}
@@ -140,7 +146,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return DistributionSetTypeBuilder bean
*/
@Bean
public DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) {
DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) {
return new JpaDistributionSetTypeBuilder(softwareManagement);
}
@@ -150,7 +156,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return SoftwareModuleBuilder bean
*/
@Bean
public SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) {
SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) {
return new JpaSoftwareModuleBuilder(softwareManagement);
}
@@ -160,7 +166,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return RolloutBuilder bean
*/
@Bean
public RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) {
RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) {
return new JpaRolloutBuilder(distributionSetManagement);
}
@@ -171,8 +177,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return TargetFilterQueryBuilder bean
*/
@Bean
public TargetFilterQueryBuilder targetFilterQueryBuilder(
final DistributionSetManagement distributionSetManagement) {
TargetFilterQueryBuilder targetFilterQueryBuilder(final DistributionSetManagement distributionSetManagement) {
return new JpaTargetFilterQueryBuilder(distributionSetManagement);
}
@@ -182,7 +187,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* e.g. JPA entities.
*/
@Bean
public SystemSecurityContextHolder systemSecurityContextHolder() {
SystemSecurityContextHolder systemSecurityContextHolder() {
return SystemSecurityContextHolder.getInstance();
}
@@ -192,7 +197,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* directly, e.g. JPA entities.
*/
@Bean
public TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
@@ -203,7 +208,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* entities.
*/
@Bean
public SystemManagementHolder systemManagementHolder() {
SystemManagementHolder systemManagementHolder() {
return SystemManagementHolder.getInstance();
}
@@ -214,7 +219,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* entities.
*/
@Bean
public TenantAwareHolder tenantAwareHolder() {
TenantAwareHolder tenantAwareHolder() {
return TenantAwareHolder.getInstance();
}
@@ -225,7 +230,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* injection
*/
@Bean
public SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
return SecurityTokenGeneratorHolder.getInstance();
}
@@ -233,7 +238,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return the singleton instance of the {@link EntityInterceptorHolder}
*/
@Bean
public EntityInterceptorHolder entityInterceptorHolder() {
EntityInterceptorHolder entityInterceptorHolder() {
return EntityInterceptorHolder.getInstance();
}
@@ -243,7 +248,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* {@link AfterTransactionCommitExecutorHolder}
*/
@Bean
public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
return AfterTransactionCommitExecutorHolder.getInstance();
}
@@ -261,7 +266,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* @return {@link ExceptionMappingAspectHandler} aspect bean
*/
@Bean
public ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() {
ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() {
return new ExceptionMappingAspectHandler();
}
@@ -305,7 +310,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public SystemManagement systemManagement() {
SystemManagement systemManagement() {
return new JpaSystemManagement();
}
@@ -316,7 +321,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public ReportManagement reportManagement() {
ReportManagement reportManagement() {
return new JpaReportManagement();
}
@@ -327,7 +332,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public DistributionSetManagement distributionSetManagement() {
DistributionSetManagement distributionSetManagement() {
return new JpaDistributionSetManagement();
}
@@ -338,7 +343,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TenantStatsManagement tenantStatsManagement() {
TenantStatsManagement tenantStatsManagement() {
return new JpaTenantStatsManagement();
}
@@ -349,7 +354,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TenantConfigurationManagement tenantConfigurationManagement() {
TenantConfigurationManagement tenantConfigurationManagement() {
return new JpaTenantConfigurationManagement();
}
@@ -360,7 +365,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TargetManagement targetManagement() {
TargetManagement targetManagement() {
return new JpaTargetManagement();
}
@@ -378,7 +383,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TargetFilterQueryManagement targetFilterQueryManagement(
TargetFilterQueryManagement targetFilterQueryManagement(
final TargetFilterQueryRepository targetFilterQueryRepository,
final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement) {
@@ -393,7 +398,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public TagManagement tagManagement() {
TagManagement tagManagement() {
return new JpaTagManagement();
}
@@ -404,19 +409,21 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public SoftwareManagement softwareManagement() {
SoftwareManagement softwareManagement() {
return new JpaSoftwareManagement();
}
/**
* {@link JpaRolloutManagement} bean.
*
* @return a new {@link RolloutManagement}
*/
@Bean
@ConditionalOnMissingBean
public RolloutManagement rolloutManagement() {
return new JpaRolloutManagement();
RolloutManagement rolloutManagement(final TargetManagement targetManagement,
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
distributionSetManagement, context, eventPublisher, virtualPropertyReplacer, txManager, tenantAware,
lockRegistry);
}
/**
@@ -426,7 +433,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public RolloutGroupManagement rolloutGroupManagement() {
RolloutGroupManagement rolloutGroupManagement() {
return new JpaRolloutGroupManagement();
}
@@ -437,7 +444,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public DeploymentManagement deploymentManagement() {
DeploymentManagement deploymentManagement() {
return new JpaDeploymentManagement();
}
@@ -448,7 +455,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public ControllerManagement controllerManagement() {
ControllerManagement controllerManagement() {
return new JpaControllerManagement();
}
@@ -460,7 +467,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean
@ConditionalOnMissingBean
public ArtifactManagement artifactManagement() {
ArtifactManagement artifactManagement() {
return new JpaArtifactManagement();
}
@@ -482,7 +489,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public EventEntityManagerHolder eventEntityManagerHolder() {
EventEntityManagerHolder eventEntityManagerHolder() {
return EventEntityManagerHolder.getInstance();
}
@@ -497,7 +504,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) {
EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) {
return new JpaEventEntityManager(aware, entityManager);
}
@@ -516,7 +523,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final PlatformTransactionManager transactionManager) {
return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement,
@@ -525,6 +532,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoAssignScheduler} bean.
*
* Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality.
*
* @param tenantAware
* to run as specific tenant
@@ -534,14 +544,49 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* to run as system
* @param autoAssignChecker
* to run a check as tenant
* @param lockRegistry
* to lock the tenant for auto assigment
* @return a new {@link AutoAssignChecker}
*/
@Bean
@ConditionalOnMissingBean
// don't active the auto assign scheduler in test, otherwise it is hard to
// test
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autoassign.scheduler", name = "enabled", matchIfMissing = true)
public AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext,
final AutoAssignChecker autoAssignChecker) {
return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker);
AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker,
final LockRegistry lockRegistry) {
return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker,
lockRegistry);
}
/**
* {@link RolloutScheduler} bean.
*
* Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality.
*
* @param tenantAware
* to run as specific tenant
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @param threadPoolExecutor
* to execute the handlers in parallel
* @return a new {@link RolloutScheduler} bean.
*/
@Bean
@ConditionalOnMissingBean
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true)
RolloutScheduler rolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext,
@Qualifier("asyncExecutor") final Executor threadPoolExecutor) {
return new RolloutScheduler(tenantAware, systemManagement, rolloutManagement, systemSecurityContext,
threadPoolExecutor);
}
}

View File

@@ -60,8 +60,8 @@ public interface RolloutGroupRepository
* rollout-management to find out rolloutgroups which are in specific
* states.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutId
* the ID of the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1
* the status of the rollout groups
* @param rolloutGroupStatus2
@@ -69,22 +69,45 @@ public interface RolloutGroupRepository
* @return the count of rollout groups belonging to a rollout in specific
* status
*/
@Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)")
Long countByRolloutAndStatusOrStatus(@Param("rollout") JpaRollout rollout,
@Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout.id = :rolloutId and (r.status = :status1 or r.status = :status2)")
Long countByRolloutIdAndStatusOrStatus(@Param("rolloutId") long rolloutId,
@Param("status1") RolloutGroupStatus rolloutGroupStatus1,
@Param("status2") RolloutGroupStatus rolloutGroupStatus2);
/**
*
* Counts all rollout-groups refering to a given {@link Rollout} by its ID
* and groups which not having the given status.
*
* @param rolloutId
* the ID of the rollout refering the groups
* @param status1
* the status which the groups should not have
* @param status2
* the status which the groups should not have
* @param status2
* the status which the groups should not have
* @return count of rollout-groups referning a rollout and not in the given
* states
*/
long countByRolloutIdAndStatusNotAndStatusNotAndStatusNot(@Param("rolloutId") long rolloutId,
@Param("status1") JpaRolloutGroup.RolloutGroupStatus status1,
@Param("status2") JpaRolloutGroup.RolloutGroupStatus status2,
@Param("status3") JpaRolloutGroup.RolloutGroupStatus status3);
/**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status.
*
* @param rolloutGroup
* the parent rolloutgroup
* @param rolloutGroupId
* the rolloutgroupId to find the parents
* @param status
* the status of the rolloutgroups
* @return The child {@link RolloutGroup}s in a specific status
*/
List<JpaRolloutGroup> findByParentAndStatus(JpaRolloutGroup rolloutGroup, RolloutGroupStatus status);
@Query("SELECT g FROM JpaRolloutGroup g WHERE g.parent.id=:rolloutGroupId and g.status=:status")
List<JpaRolloutGroup> findByParentIdAndStatus(@Param("rolloutGroupId") long rolloutGroupId,
@Param("status") RolloutGroupStatus status);
/**
* Updates all {@link RolloutGroup#getStatus()} of children for given
@@ -124,4 +147,8 @@ public interface RolloutGroupRepository
*/
Page<JpaRolloutGroup> findByRolloutId(final Long rolloutId, Pageable page);
@Modifying
@Query("DELETE FROM JpaRolloutGroup g where g.id in :rolloutGroupIds")
void deleteByIds(@Param("rolloutGroupIds") List<Long> rolloutGroups);
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -15,11 +16,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -30,36 +28,15 @@ public interface RolloutRepository
extends BaseEntityRepository<JpaRollout, Long>, JpaSpecificationExecutor<JpaRollout> {
/**
* Updates the {@code lastCheck} field of the {@link Rollout} for rollouts
* in a specific status and only if the {@code lastCheck} is overdue.
* Retrieves all {@link Rollout} for given status.
*
* @param lastCheck
* the time in milliseconds to set to the lastCheck column
* @param delay
* the delay between last checks
* @param status
* the status which the rollout should have to update the last
* check field
* @return the count of the updated rows. Zero if no row has been updated
* the status of the rollouts to find
* @return the list of {@link Rollout} for specific status
*/
@Modifying
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
@Query("UPDATE JpaRollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status")
int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay,
@Param("status") final RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code lastCheck} time and
* for a specific status.
*
* @param lastCheck
* the lastCheck time to find the specific rollout.
* @param status
* the status of the rollout to find
* @return the list of {@link Rollout} for specific lastCheck time and
* status
*/
List<JpaRollout> findByLastCheckAndStatus(long lastCheck, RolloutStatus status);
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT sm.id FROM JpaRollout sm WHERE sm.status IN ?1")
List<Long> findByStatusIn(Collection<RolloutStatus> status);
/**
* Retrieves all {@link Rollout} for a specific {@code name}

View File

@@ -47,6 +47,15 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
@EntityGraph(value = "Target.detail", type = EntityGraphType.LOAD)
Optional<Target> findByControllerId(String controllerID);
/**
* Checks if target with given id exists.
*
* @param controllerId to check
* @return <code>true</code> if target with given id exists
*/
@Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaTarget t WHERE t.controllerId=:controllerId")
boolean existsByControllerId(@Param("controllerId") String controllerId);
/**
* Deletes the {@link Target}s with the given target IDs.
*

View File

@@ -97,6 +97,7 @@ public class AutoAssignChecker {
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void check() {
LOGGER.debug("Auto assigned check call");
final PageRequest pageRequest = new PageRequest(0, PAGE_SIZE);

View File

@@ -9,27 +9,24 @@
package org.eclipse.hawkbit.repository.jpa.autoassign;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.eclipse.hawkbit.repository.AutoAssignProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Scheduled;
/**
* Scheduler to check target filters for auto assignment of distribution sets
*/
// don't active the auto assign scheduler in test, otherwise it is hard to test
@Profile("!test")
@EnableConfigurationProperties(AutoAssignProperties.class)
public class AutoAssignScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignScheduler.class);
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:2000}";
private final TenantAware tenantAware;
private final SystemManagement systemManagement;
@@ -38,6 +35,8 @@ public class AutoAssignScheduler {
private final AutoAssignChecker autoAssignChecker;
private final LockRegistry lockRegistry;
/**
* Instantiates a new AutoAssignScheduler
*
@@ -49,13 +48,17 @@ public class AutoAssignScheduler {
* to run as system
* @param autoAssignChecker
* to run a check as tenant
* @param lockRegistry
* to acquire a lock per tenant
*/
public AutoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker) {
final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker,
final LockRegistry lockRegistry) {
this.tenantAware = tenantAware;
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
this.autoAssignChecker = autoAssignChecker;
this.lockRegistry = lockRegistry;
}
/**
@@ -64,29 +67,38 @@ public class AutoAssignScheduler {
* tenant the auto assignments defined in the target filter queries
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER)
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void autoAssignScheduler() {
LOGGER.debug("auto assign schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant separately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking target filter queries for tenants: {}", tenants.size());
for (final String tenant : tenants) {
systemSecurityContext.runAsSystem(() -> executeAutoAssign());
}
private Object executeAutoAssign() {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant separately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking target filter queries for tenants: {}", tenants.size());
for (final String tenant : tenants) {
final Lock lock = lockRegistry.obtain(tenant + "-autoassign");
if (!lock.tryLock()) {
return null;
}
try {
tenantAware.runAsTenant(tenant, () -> {
autoAssignChecker.check();
return null;
});
} finally {
lock.unlock();
}
return null;
});
}
return null;
}
}

View File

@@ -43,12 +43,12 @@ public class DistributionSetTypeElement implements Serializable {
@MapsId("dsType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_dstype"))
@JoinColumn(name = "distribution_set_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_element"))
private JpaDistributionSetType dsType;
@MapsId("smType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
@JoinColumn(name = "software_module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
private JpaSoftwareModuleType smType;
public DistributionSetTypeElement() {

View File

@@ -20,10 +20,10 @@ import javax.persistence.Embeddable;
public class DistributionSetTypeElementCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "distribution_set_type", nullable = false)
@Column(name = "distribution_set_type", nullable = false, updatable = false)
private Long dsType;
@Column(name = "software_module_type", nullable = false)
@Column(name = "software_module_type", nullable = false, updatable = false)
private Long smType;
/**

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
@@ -34,6 +33,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -58,13 +58,13 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds"))
@NotNull
private JpaDistributionSet distributionSet;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "target", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target"))
@NotNull
private JpaTarget target;
@@ -79,20 +79,20 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "forced_time")
private long forcedTime;
@Column(name = "status")
@Column(name = "status", nullable = false)
@NotNull
private Status status;
@CascadeOnDelete
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY, cascade = {
CascadeType.REMOVE })
private List<ActionStatus> actionStatus;
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY)
private List<JpaActionStatus> actionStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
@JoinColumn(name = "rolloutgroup", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
private JpaRolloutGroup rolloutGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
@JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
private JpaRollout rollout;
@Override
@@ -185,13 +185,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
.publishEvent(new ActionCreatedEvent(this, BaseEntity.getIdOrNull(rollout),
BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
.publishEvent(new ActionUpdatedEvent(this, BaseEntity.getIdOrNull(rollout),
BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override

View File

@@ -54,11 +54,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
private Long occurredAt;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "action", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
@JoinColumn(name = "action", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
@NotNull
private JpaAction action;
@Column(name = "status")
@Column(name = "status", nullable = false, updatable = false)
@NotNull
private Status status;

View File

@@ -12,6 +12,7 @@ import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
@@ -49,7 +50,7 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
@NotEmpty
private String filename;
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST })
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_assigned_sm"))
private JpaSoftwareModule softwareModule;

View File

@@ -16,7 +16,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
@@ -45,9 +44,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
@@ -82,40 +79,38 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
@CascadeOnDelete
@ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinTable(name = "sp_ds_module", joinColumns = {
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
private Set<SoftwareModule> modules;
@CascadeOnDelete
@ManyToMany(targetEntity = JpaDistributionSetTag.class)
@JoinTable(name = "sp_ds_dstag", joinColumns = {
@JoinColumn(name = "ds", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "TAG", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) })
@JoinColumn(name = "ds", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "TAG", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) })
private Set<DistributionSetTag> tags;
@Column(name = "deleted")
private boolean deleted;
@OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY)
private List<Target> assignedToTargets;
private List<JpaTarget> assignedToTargets;
@OneToMany(mappedBy = "autoAssignDistributionSet", targetEntity = JpaTargetFilterQuery.class, fetch = FetchType.LAZY)
private List<TargetFilterQuery> autoAssignFilters;
@OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY)
private List<TargetInfo> installedAtTargets;
private List<JpaTargetInfo> installedAtTargets;
@OneToMany(mappedBy = "distributionSet", targetEntity = JpaAction.class, fetch = FetchType.LAZY)
private List<Action> actions;
private List<JpaAction> actions;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class, cascade = {
CascadeType.REMOVE })
@JoinColumn(name = "ds_id", insertable = false, updatable = false)
@OneToMany(mappedBy = "distributionSet", fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class)
private List<DistributionSetMetadata> metadata;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetType.class)
@JoinColumn(name = "ds_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = JpaDistributionSetType.class)
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds"))
@NotNull
private DistributionSetType type;

View File

@@ -32,8 +32,8 @@ public class JpaDistributionSetMetadata extends JpaMetaData implements Distribut
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds"))
private JpaDistributionSet distributionSet;
public JpaDistributionSetMetadata() {

View File

@@ -19,7 +19,6 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@@ -29,6 +28,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.util.CollectionUtils;
@@ -49,12 +49,12 @@ import org.springframework.util.CollectionUtils;
public class JpaDistributionSetType extends AbstractJpaNamedEntity implements DistributionSetType {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = DistributionSetTypeElement.class, cascade = {
CascadeType.ALL }, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "distribution_set_type", insertable = false, updatable = false)
@CascadeOnDelete
@OneToMany(mappedBy = "dsType", targetEntity = DistributionSetTypeElement.class, cascade = {
CascadeType.PERSIST }, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DistributionSetTypeElement> elements;
@Column(name = "type_key", nullable = false, length = 64)
@Column(name = "type_key", nullable = false, updatable = false, length = 64)
@Size(max = 64)
@NotEmpty
private String key;

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
@@ -28,6 +29,7 @@ import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -35,7 +37,14 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
import org.hibernate.validator.constraints.NotEmpty;
/**
@@ -49,25 +58,41 @@ import org.hibernate.validator.constraints.NotEmpty;
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities
@SuppressWarnings("squid:S2160")
@ObjectTypeConverter(name = "rolloutstatus", objectType = Rollout.RolloutStatus.class, dataType = Integer.class, conversionValues = {
@ConversionValue(objectValue = "CREATING", dataValue = "0"),
@ConversionValue(objectValue = "READY", dataValue = "1"),
@ConversionValue(objectValue = "PAUSED", dataValue = "2"),
@ConversionValue(objectValue = "STARTING", dataValue = "3"),
@ConversionValue(objectValue = "STOPPED", dataValue = "4"),
@ConversionValue(objectValue = "RUNNING", dataValue = "5"),
@ConversionValue(objectValue = "FINISHED", dataValue = "6"),
@ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"),
@ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"),
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10") })
public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = JpaRolloutGroup.class)
@JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup"))
private List<RolloutGroup> rolloutGroups;
private static final String DELETED_PROPERTY = "deleted";
@CascadeOnDelete
@OneToMany(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, mappedBy = "rollout")
private List<JpaRolloutGroup> rolloutGroups;
@Column(name = "target_filter", length = 1024, nullable = false)
@Size(max = 1024)
@NotEmpty
private String targetFilterQuery;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds"))
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds"))
@NotNull
private JpaDistributionSet distributionSet;
@Column(name = "status")
@Column(name = "status", nullable = false)
@Convert("rolloutstatus")
@NotNull
private RolloutStatus status = RolloutStatus.CREATING;
@Column(name = "last_check")
@@ -87,6 +112,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Column(name = "rollout_groups_created")
private int rolloutGroupsCreated;
@Column(name = "deleted")
private boolean deleted;
@Column(name = "start_at")
private Long startAt;
@@ -142,7 +170,7 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return startAt;
}
public void setStartAt(Long startAt) {
public void setStartAt(final Long startAt) {
this.startAt = startAt;
}
@@ -196,9 +224,8 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Override
public String toString() {
return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery
+ ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck
+ ", getName()=" + getName() + ", getId()=" + getId() + "]";
return "Rollout [ targetFilterQuery=" + targetFilterQuery + ", distributionSet=" + distributionSet + ", status="
+ status + ", lastCheck=" + lastCheck + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override
@@ -211,11 +238,35 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList());
return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute())
&& Boolean.parseBoolean(record.getNewValue().toString())).count() > 0;
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
// there is no rollout deletion event
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public boolean isDeleted() {
return deleted;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
}

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.annotations.ConversionValue;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.ObjectTypeConverter;
@@ -60,18 +61,20 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout"))
@JoinColumn(name = "rollout", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout"))
private JpaRollout rollout;
@Column(name = "status")
@Column(name = "status", nullable = false)
@Convert("rolloutgroupstatus")
private RolloutGroupStatus status = RolloutGroupStatus.CREATING;
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
@JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
@CascadeOnDelete
@OneToMany(mappedBy = "rolloutGroup", fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
private List<RolloutTargetGroup> rolloutTargetGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rolloutgroup"))
private JpaRolloutGroup parent;
@Column(name = "success_condition", nullable = false)
@@ -287,8 +290,7 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutGroupUpdatedEvent(this,
EventPublisherHolder.getInstance().getApplicationId()));
this.getRollout().getId(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@@ -36,11 +37,13 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
/**
* Base Software Module that is supported by OS level provisioning mechanism on
@@ -60,8 +63,10 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implements SoftwareModule, EventAwareEntity {
private static final long serialVersionUID = 1L;
private static final String DELETED_PROPERTY = "deleted";
@ManyToOne
@JoinColumn(name = "module_type", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type"))
@JoinColumn(name = "module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type"))
@NotNull
private JpaSoftwareModuleType type;
@@ -77,13 +82,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, mappedBy = "softwareModule", cascade = {
CascadeType.ALL }, targetEntity = JpaArtifact.class)
private List<Artifact> artifacts;
CascadeType.PERSIST }, targetEntity = JpaArtifact.class, orphanRemoval = true)
private List<JpaArtifact> artifacts;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaSoftwareModuleMetadata.class)
@JoinColumn(name = "sw_id", insertable = false, updatable = false)
private List<SoftwareModuleMetadata> metadata;
@OneToMany(mappedBy = "softwareModule", fetch = FetchType.LAZY, targetEntity = JpaSoftwareModuleMetadata.class)
private List<JpaSoftwareModuleMetadata> metadata;
/**
* Default constructor.
@@ -116,12 +120,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
public void addArtifact(final Artifact artifact) {
if (null == artifacts) {
artifacts = new ArrayList<>(4);
artifacts.add(artifact);
artifacts.add((JpaArtifact) artifact);
return;
}
if (!artifacts.contains(artifact)) {
artifacts.add(artifact);
artifacts.add((JpaArtifact) artifact);
}
}
@@ -206,6 +210,21 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new SoftwareModuleUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new SoftwareModuleDeletedEvent(
getTenant(), getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList());
return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute())
&& Boolean.parseBoolean(record.getNewValue().toString())).count() > 0;
}
@Override

View File

@@ -32,8 +32,8 @@ public class JpaSoftwareModuleMetadata extends JpaMetaData implements SoftwareMo
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
@ManyToOne(optional = false, targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
private SoftwareModule softwareModule;
public JpaSoftwareModuleMetadata() {

View File

@@ -87,22 +87,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
@CascadeOnDelete
@ManyToMany(targetEntity = JpaTargetTag.class)
@JoinTable(name = "sp_target_target_tag", joinColumns = {
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = {
@JoinColumn(name = "tag", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) })
@JoinColumn(name = "target", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = {
@JoinColumn(name = "tag", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) })
private Set<TargetTag> tags;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = {
CascadeType.REMOVE }, targetEntity = JpaAction.class)
@JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ"))
private List<Action> actions;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaAction.class)
private List<JpaAction> actions;
@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class)
@JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds"))
private JpaDistributionSet assignedDistributionSet;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class)
@OneToOne(cascade = { CascadeType.PERSIST,
CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class)
@PrimaryKeyJoinColumn
private JpaTargetInfo targetInfo;
@@ -110,14 +109,13 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
* the security token of the target which allows if enabled to authenticate
* with this security token.
*/
@Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128)
@Size(max = 64)
@Column(name = "sec_token", updatable = true, nullable = false, length = 128)
@Size(max = 128)
@NotEmpty
private String securityToken;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
@JoinColumn(name = "target_Id", insertable = false, updatable = false)
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
private List<RolloutTargetGroup> rolloutTargetGroup;
/**
@@ -214,7 +212,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
actions = new ArrayList<>();
}
return actions.add(action);
return actions.add((JpaAction) action);
}
@Override

View File

@@ -79,8 +79,7 @@ public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
private boolean entityNew;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.MERGE,
CascadeType.REMOVE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class)
@OneToOne(cascade = { CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class)
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ"))
@MapsId
private JpaTarget target;
@@ -112,7 +111,7 @@ public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
@Column(name = "attribute_value", length = 128)
@MapKeyColumn(name = "attribute_key", nullable = false, length = 32)
@CollectionTable(name = "sp_target_attributes", joinColumns = {
@JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
@JoinColumn(name = "target_id", nullable = false, updatable = false) }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
private final Map<String, String> controllerAttributes = Collections.synchronizedMap(new HashMap<String, String>());
// set default request controller attributes to true, because we want to

View File

@@ -32,7 +32,7 @@ import org.hibernate.validator.constraints.NotEmpty;
public class JpaTenantConfiguration extends AbstractJpaTenantAwareBaseEntity implements TenantConfiguration {
private static final long serialVersionUID = 1L;
@Column(name = "conf_key", length = 128, nullable = false)
@Column(name = "conf_key", length = 128, nullable = false, updatable = false)
@Size(max = 128)
@NotEmpty
private String key;

View File

@@ -43,7 +43,7 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData;
public class JpaTenantMetaData extends AbstractJpaBaseEntity implements TenantMetaData {
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, length = 40)
@Column(name = "tenant", nullable = false, updatable = false, length = 40)
@Size(max = 40)
private String tenant;

View File

@@ -24,7 +24,6 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.persistence.annotations.ExistenceChecking;
@@ -44,19 +43,22 @@ public class RolloutTargetGroup implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private RolloutGroup rolloutGroup;
@ManyToOne(optional = false, targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private JpaRolloutGroup rolloutGroup;
@Id
@ManyToOne(targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
@ManyToOne(optional = false, targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST })
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
private JpaTarget target;
@OneToMany(targetEntity = JpaAction.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", referencedColumnName = "target_id") })
private List<Action> actions;
@JoinColumns(value = {
@JoinColumn(name = "rolloutgroup", nullable = false, updatable = false, referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", nullable = false, updatable = false, referencedColumnName = "target_id") })
private List<JpaAction> actions;
/**
* default constructor for JPA.
@@ -66,7 +68,7 @@ public class RolloutTargetGroup implements Serializable {
}
public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup;
this.rolloutGroup = (JpaRolloutGroup) rolloutGroup;
this.target = (JpaTarget) target;
}

View File

@@ -9,63 +9,78 @@
package org.eclipse.hawkbit.repository.jpa.rollout;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.google.common.base.Throwables;
/**
* Scheduler to schedule the
* {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the
* checks be be configured using the properties from {@link RolloutProperties}.
* Scheduler to schedule the {@link RolloutManagement#handleRollouts()}. The
* delay between the checks be be configured using the property from
* {#PROP_SCHEDULER_DELAY_PLACEHOLDER}.
*/
@Component
// don't active the rollout scheduler in test, otherwise it is hard to test
// rolloutmanagement and leads weird side-effects maybe.
@Profile("!test")
public class RolloutScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutScheduler.class);
@Autowired
private TenantAware tenantAware;
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:2000}";
@Autowired
private SystemManagement systemManagement;
private final TenantAware tenantAware;
@Autowired
private RolloutManagement rolloutManagement;
private final SystemManagement systemManagement;
@Autowired
private SystemSecurityContext systemSecurityContext;
private final RolloutManagement rolloutManagement;
@Autowired
private RolloutProperties rolloutProperties;
private final SystemSecurityContext systemSecurityContext;
private final ExecutorCompletionService<Void> completionService;
/**
* Constructor.
*
* @param tenantAware
* to run as specific tenant
* @param systemManagement
* to find all tenants
* @param rolloutManagement
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @param threadPoolExecutor
* to execute the handlers in parallel
*/
public RolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement,
final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext,
final Executor threadPoolExecutor) {
this.tenantAware = tenantAware;
this.systemManagement = systemManagement;
this.rolloutManagement = rolloutManagement;
this.systemSecurityContext = systemSecurityContext;
completionService = new ExecutorCompletionService<>(threadPoolExecutor);
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the
* tenant the {@link RolloutManagement#handleRollouts()} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER)
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void runningRolloutScheduler() {
if (!rolloutProperties.getScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
final int tasks = systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
@@ -75,119 +90,25 @@ public class RolloutScheduler {
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getScheduler().getFixedDelay();
rolloutManagement.checkRunningRollouts(fixedDelay);
completionService.submit(() -> tenantAware.runAsTenant(tenant, () -> {
rolloutManagement.handleRollouts();
return null;
});
}));
}
return null;
return tenants.size();
});
waitUntilHandlersAreComplete(tasks);
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkStartingRollouts(long)} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER)
public void startingRolloutScheduler() {
if (!rolloutProperties.getStartingScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout starting schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking starting rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getStartingScheduler().getFixedDelay();
rolloutManagement.checkStartingRollouts(fixedDelay);
return null;
});
private void waitUntilHandlersAreComplete(final int tasks) {
try {
for (int i = 0; i < tasks; i++) {
completionService.take().get();
}
return null;
});
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkCreatingRollouts(long)} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER)
public void creatingRolloutScheduler() {
if (!rolloutProperties.getCreatingScheduler().isEnabled()) {
return;
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw Throwables.propagate(e);
}
LOGGER.debug("rollout creating schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking creating rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getCreatingScheduler().getFixedDelay();
rolloutManagement.checkCreatingRollouts(fixedDelay);
return null;
});
}
return null;
});
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkReadyRollouts(long)} in the
* {@link SystemSecurityContext}. Used to auto start Rollouts as soon as
* their startAt time is reached.
*/
@Scheduled(initialDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER)
public void readyRolloutScheduler() {
if (!rolloutProperties.getReadyScheduler().isEnabled()) {
return;
}
LOGGER.debug("rollout ready schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
LOGGER.info("Checking ready rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
final long fixedDelay = rolloutProperties.getReadyScheduler().getFixedDelay();
rolloutManagement.checkReadyRollouts(fixedDelay);
return null;
});
}
return null;
});
}
}

View File

@@ -71,7 +71,7 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
// scheduled. If the group is empty now, we just finish the group if
// there are not actions available for this group.
final List<JpaRolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
.findByParentAndStatus((JpaRolloutGroup) rolloutGroup, RolloutGroupStatus.SCHEDULED);
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED);
findByRolloutGroupParent.forEach(nextGroup -> {
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
nextGroup.setStatus(RolloutGroupStatus.FINISHED);

View File

@@ -20,8 +20,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Target;
import org.springframework.data.jpa.domain.Specification;
/**
@@ -37,25 +37,50 @@ public final class ActionSpecifications {
/**
* Specification which joins all necessary tables to retrieve the dependency
* between a target and a local file assignment through the assigen action
* between a target and a local file assignment through the assigned action
* of the target. All actions are included, not only active actions.
*
* @param target
* the target to verfiy if the given artifact is currently
* @param controllerId
* the target to verify if the given artifact is currently
* assigned or had been assigned
* @param sha1Hash
* of the local artifact to check wherever the target had ever
* been assigned
* @return a specification to use with spring JPA
*/
public static Specification<JpaAction> hasTargetAssignedArtifact(final Target target, final String sha1Hash) {
public static Specification<JpaAction> hasTargetAssignedArtifact(final String controllerId, final String sha1Hash) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<JpaAction, JpaDistributionSet> dsJoin = actionRoot.join(JpaAction_.distributionSet);
final SetJoin<JpaDistributionSet, JpaSoftwareModule> modulesJoin = dsJoin.join(JpaDistributionSet_.modules);
final ListJoin<JpaSoftwareModule, JpaArtifact> artifactsJoin = modulesJoin
.join(JpaSoftwareModule_.artifacts);
return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash),
criteriaBuilder.equal(actionRoot.get(JpaAction_.target), target));
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.controllerId),
controllerId));
};
}
/**
* Specification which joins all necessary tables to retrieve the dependency
* between a target and a local file assignment through the assigned action
* of the target. All actions are included, not only active actions.
*
* @param targetId
* the target to verify if the given artifact is currently
* assigned or had been assigned
* @param sha1Hash
* of the local artifact to check wherever the target had ever
* been assigned
* @return a specification to use with spring JPA
*/
public static Specification<JpaAction> hasTargetAssignedArtifact(final Long targetId, final String sha1Hash) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<JpaAction, JpaDistributionSet> dsJoin = actionRoot.join(JpaAction_.distributionSet);
final SetJoin<JpaDistributionSet, JpaSoftwareModule> modulesJoin = dsJoin.join(JpaDistributionSet_.modules);
final ListJoin<JpaSoftwareModule, JpaArtifact> artifactsJoin = modulesJoin
.join(JpaSoftwareModule_.artifacts);
return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash),
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId));
};
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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.specifications;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link Rollout}s. The class provides Spring Data
* JPQL Specifications.
*
*/
public final class RolloutSpecification {
private RolloutSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link Rollout}s by its DELETED
* attribute.
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
* @return the {@link Rollout} {@link Specification}
*/
public static Specification<JpaRollout> isDeleted(final Boolean isDeleted) {
return (root, query, cb) -> cb.equal(root.<Boolean> get(JpaRollout_.deleted), isDeleted);
}
}

View File

@@ -0,0 +1,26 @@
ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN;
UPDATE sp_rollout SET deleted = 0;
ALTER TABLE sp_action MODIFY target BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_element;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_element
FOREIGN KEY (distribution_set_type)
REFERENCES sp_distribution_set_type (id)
ON DELETE CASCADE;
ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_smtype;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_smtype
FOREIGN KEY (software_module_type)
REFERENCES sp_software_module_type (id)
ON DELETE CASCADE;

View File

@@ -0,0 +1,26 @@
ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN;
UPDATE sp_rollout SET deleted = 0;
ALTER TABLE sp_action MODIFY target BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_action MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL;
ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL;
ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_element;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_element
FOREIGN KEY (distribution_set_type)
REFERENCES sp_distribution_set_type (id)
ON DELETE CASCADE;
ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_smtype;
ALTER TABLE sp_ds_type_element
ADD CONSTRAINT fk_ds_type_element_smtype
FOREIGN KEY (software_module_type)
REFERENCES sp_software_module_type (id)
ON DELETE CASCADE;

View File

@@ -65,6 +65,12 @@ public class RemoteIdEventTest extends AbstractRemoteEventTest {
assertAndCreateRemoteEvent(SoftwareModuleDeletedEvent.class);
}
@Test
@Description("Verifies that the rollout id is correct reloaded")
public void testRolloutDeletedEvent() {
assertAndCreateRemoteEvent(RolloutDeletedEvent.class);
}
protected void assertAndCreateRemoteEvent(final Class<? extends RemoteIdEvent> eventType) {
final Constructor<?> constructor = Arrays.stream(eventType.getDeclaredConstructors())

View File

@@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
@@ -46,6 +47,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
final Target target = testdataFactory.createTarget("Test");
generateAction.setTarget(target);
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
final Action action = actionRepository.save(generateAction);
final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent(action,

View File

@@ -8,9 +8,16 @@
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
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.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
@@ -35,7 +42,49 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
@Description("Verifies that the action entity reloading by remote updated works")
public void testActionUpdatedEvent() {
assertAndCreateRemoteEvent(ActionUpdatedEvent.class);
}
@Override
protected RemoteEntityEvent<?> createRemoteEvent(final Action baseEntity,
final Class<? extends RemoteEntityEvent<?>> eventType) {
Constructor<?> constructor = null;
for (final Constructor<?> constructors : eventType.getDeclaredConstructors()) {
if (constructors.getParameterCount() == 4) {
constructor = constructors;
}
}
if (constructor == null) {
throw new IllegalArgumentException("No suitable constructor foundes");
}
try {
return (RemoteEntityEvent<?>) constructor.newInstance(baseEntity, 1L, 2L, "Node");
} catch (final ReflectiveOperationException e) {
fail("Exception should not happen " + e.getMessage());
}
return null;
}
@Override
protected RemoteEntityEvent<?> assertEntity(final Action baseEntity, final RemoteEntityEvent<?> e) {
final AbstractActionEvent event = (AbstractActionEvent) e;
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractActionEvent underTestCreatedEvent = (AbstractActionEvent) createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);
underTestCreatedEvent = (AbstractActionEvent) createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);
return underTestCreatedEvent;
}
@Override
@@ -43,8 +92,10 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
final JpaAction generateAction = new JpaAction();
generateAction.setActionType(ActionType.FORCED);
final Target target = testdataFactory.createTarget("Test");
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
generateAction.setTarget(target);
generateAction.setDistributionSet(testdataFactory.createDistributionSet());
generateAction.setDistributionSet(distributionSet);
generateAction.setStatus(Status.RUNNING);
return actionRepository.save(generateAction);
}

View File

@@ -8,8 +8,10 @@
*/
package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.junit.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Constructor;
import java.util.UUID;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -44,6 +46,47 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
assertAndCreateRemoteEvent(RolloutGroupUpdatedEvent.class);
}
@Override
protected RemoteEntityEvent<?> createRemoteEvent(final RolloutGroup baseEntity,
final Class<? extends RemoteEntityEvent<?>> eventType) {
Constructor<?> constructor = null;
for (final Constructor<?> constructors : eventType.getDeclaredConstructors()) {
if (constructors.getParameterCount() == 3) {
constructor = constructors;
}
}
if (constructor == null) {
throw new IllegalArgumentException("No suitable constructor foundes");
}
try {
return (RemoteEntityEvent<?>) constructor.newInstance(baseEntity, 1L, "Node");
} catch (final ReflectiveOperationException e) {
fail("Exception should not happen " + e.getMessage());
}
return null;
}
@Override
protected RemoteEntityEvent<?> assertEntity(final RolloutGroup baseEntity, final RemoteEntityEvent<?> e) {
final AbstractRolloutGroupEvent event = (AbstractRolloutGroupEvent) e;
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractRolloutGroupEvent underTestCreatedEvent = (AbstractRolloutGroupEvent) createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
underTestCreatedEvent = (AbstractRolloutGroupEvent) createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
return underTestCreatedEvent;
}
@Override
protected RolloutGroup createEntity() {
testdataFactory.createTarget(UUID.randomUUID().toString());

View File

@@ -31,6 +31,8 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
@SpringApplicationConfiguration(classes = {
org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration.class })
public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest {
@@ -80,6 +82,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected RolloutGroupRepository rolloutGroupRepository;
@Autowired
protected RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
protected RolloutRepository rolloutRepository;
@@ -91,7 +96,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
protected List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutIdAndStatus(rollout.getId(), actionStatus);
return Lists.newArrayList(actionRepository.findByRolloutIdAndStatus(pageReq, rollout.getId(), actionStatus));
}
protected TargetTagAssignmentResult toggleTagAssignment(final Collection<Target> targets, final TargetTag tag) {

View File

@@ -9,23 +9,29 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.Test;
@@ -35,24 +41,47 @@ import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Test class for {@link ArtifactManagement} with running MongoDB instance..
*
*
*
*
* Test class for {@link ArtifactManagement}.
*/
@Features("Component Tests - Repository")
@Stories("Artifact Management")
public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
/**
* Test method for
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#createArtifact(java.io.InputStream)}
* .
*
* @throws IOException
* @throws NoSuchAlgorithmException
*/
@Test
@Description("Verifies that management queries react as specfied on calls for non existing entities.")
@ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
public void nonExistingEntityQueries() throws URISyntaxException {
final SoftwareModule module = testdataFactory.createSoftwareModuleOs();
assertThatThrownBy(() -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx",
null, null, false, null)).isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(
() -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", false))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(() -> artifactManagement.deleteArtifact(1234L)).isInstanceOf(EntityNotFoundException.class)
.hasMessageContaining("1234").hasMessageContaining("Artifact");
assertThat(artifactManagement.findArtifact(1234L).isPresent()).isFalse();
assertThatThrownBy(() -> artifactManagement.findArtifactBySoftwareModule(pageReq, 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThat(artifactManagement.findArtifactByFilename("1234").isPresent()).isFalse();
assertThatThrownBy(() -> artifactManagement.findByFilenameAndSoftwareModule("xxx", 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThat(artifactManagement.findByFilenameAndSoftwareModule("1234", module.getId()).isPresent()).isFalse();
assertThat(artifactManagement.findFirstArtifactBySHA1("1234").isPresent()).isFalse();
assertThat(artifactManagement.loadArtifactBinary("1234").isPresent()).isFalse();
}
@Test
@Description("Test if a local artifact can be created by API including metadata.")
public void createArtifact() throws NoSuchAlgorithmException, IOException {
@@ -205,7 +234,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final Artifact result = artifactManagement.createArtifact(new ByteArrayInputStream(random),
testdataFactory.createSoftwareModuleOs().getId(), "file1", false);
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash())
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get()
.getFileInputStream()) {
assertTrue("The stored binary matches the given binary",
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream));

View File

@@ -9,9 +9,12 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
@@ -29,10 +32,12 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -55,6 +60,75 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Autowired
private RepositoryProperties repositoryProperties;
@Test
@Description("Verifies that management queries react as specfied on calls for non existing entities.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
public void nonExistingEntityQueries() throws URISyntaxException {
final Target target = testdataFactory.createTarget();
final SoftwareModule module = testdataFactory.createSoftwareModuleOs();
assertThatThrownBy(() -> controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.RUNNING)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED)))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThat(controllerManagement.findActionWithDetails(1234L).isPresent()).isFalse();
assertThat(controllerManagement.findByControllerId("1234").isPresent()).isFalse();
assertThat(controllerManagement.findByTargetId(1234L).isPresent()).isFalse();
assertThatThrownBy(() -> controllerManagement.findOldestActiveActionByTarget("1234"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), 1234L))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("SoftwareModule");
assertThatThrownBy(
() -> controllerManagement.getActionForDownloadByTargetAndSoftwareModule("1234", module.getId()))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThat(controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module.getId()).isPresent())
.isFalse();
assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned(1234L, "XXX"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned("1234", "XXX"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse();
assertThatThrownBy(() -> controllerManagement.registerRetrieved(1234L, "test message"))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Action");
assertThatThrownBy(() -> controllerManagement.updateControllerAttributes("1234", Maps.newHashMap()))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
assertThatThrownBy(() -> controllerManagement.updateLastTargetQuery("1234", new URI("http://test.com")))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234")
.hasMessageContaining("Target");
}
@Test
@Description("Controller confirms successfull update with FINISHED status.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -68,7 +142,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnUpdate(actionId);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.FINISHED, Action.Status.FINISHED, false);
@@ -91,7 +165,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.FINISHED, Action.Status.FINISHED, false);
@@ -111,7 +185,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
try {
controllerManagament.addCancelActionStatus(
controllerManagement.addCancelActionStatus(
entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
fail("Expected " + CancelActionNotAllowedException.class.getName());
} catch (final CancelActionNotAllowedException e) {
@@ -144,7 +218,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.CANCELED, Action.Status.FINISHED, false);
@@ -171,7 +245,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.CANCELED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC,
Action.Status.CANCELED, Action.Status.CANCELED, false);
@@ -199,7 +273,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament.addCancelActionStatus(
controllerManagement.addCancelActionStatus(
entityFactory.actionStatus().create(actionId).status(Action.Status.CANCEL_REJECTED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.CANCEL_REJECTED, true);
@@ -227,7 +301,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnCancellation(actionId);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.ERROR, true);
@@ -249,22 +323,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Step
private void simulateIntermediateStatusOnCancellation(final Long actionId) {
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.DOWNLOAD, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.RETRIEVED, true);
controllerManagament
controllerManagement
.addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.CANCELING, Action.Status.WARNING, true);
@@ -272,22 +346,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Step
private void simulateIntermediateStatusOnUpdate(final Long actionId) {
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.DOWNLOAD, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RETRIEVED, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.WARNING, true);
@@ -305,7 +379,10 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final List<ActionStatus> actionStatusList = deploymentManagement.findActionStatusByAction(pageReq, actionId)
.getContent();
assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus);
if (actionActive) {
assertThat(controllerManagement.findOldestActiveActionByTarget(controllerId).get().getId())
.isEqualTo(actionId);
}
}
@Test
@@ -332,31 +409,31 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
.isFalse();
savedTarget = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getAssignedEntity().iterator()
.next();
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash()))
.isTrue();
assertThat(
controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash()))
controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash()))
.isTrue();
}
@Test
@Description("Register a controller which does not exist")
public void findOrRegisterTargetIfItDoesNotexist() {
final Target target = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null);
assertThat(target).as("target should not be null").isNotNull();
final Target sameTarget = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null);
final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null);
assertThat(target).as("Target should be the equals").isEqualTo(sameTarget);
assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L);
// throws exception
try {
controllerManagament.findOrRegisterTargetIfItDoesNotexist("", null);
controllerManagement.findOrRegisterTargetIfItDoesNotexist("", null);
fail("should fail as target does not exist");
} catch (final ConstraintViolationException e) {
@@ -375,19 +452,19 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs();
// test and verify
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING,
Action.Status.RUNNING, Action.Status.RUNNING, true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR));
assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR,
Action.Status.ERROR, Action.Status.ERROR, false);
// try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -397,7 +474,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with enabled late feedback - should not make a difference as it
// only allows intermediate feedbacks and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -422,7 +499,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -432,7 +509,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// try with enabled late feedback - should not make a difference as it
// only allows intermediate feedbacks and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagament
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test
@@ -458,7 +535,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Action action = prepareFinishedUpdate();
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING));
// nothing changed as "feedback after close" is disabled
@@ -483,7 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
repositoryProperties.setRejectActionStatusForClosedAction(false);
Action action = prepareFinishedUpdate();
action = controllerManagament.addUpdateActionStatus(
action = controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING));
// nothing changed as "feedback after close" is disabled
@@ -512,7 +589,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
private void addAttributeAndVerify(final String controllerId) {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(1);
testData.put("test1", "testdata1");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong")
@@ -523,7 +600,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
private void addSecondAttributeAndVerify(final String controllerId) {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
testData.put("test2", "testdata20");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
testData.put("test1", "testdata1");
@@ -536,7 +613,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
testData.put("test1", "testdata12");
controllerManagament.updateControllerAttributes(controllerId, testData);
controllerManagement.updateControllerAttributes(controllerId, testData);
final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get();
testData.put("test2", "testdata20");

View File

@@ -752,7 +752,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.as("Installed distribution set of action should be null").isNotNull();
final Page<Action> updAct = actionRepository.findByDistributionSetId(pageReq, dsA.getId());
controllerManagament.addUpdateActionStatus(
controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(updAct.getContent().get(0).getId()).status(Status.FINISHED));
targ = targetManagement.findTargetByControllerID(targ.getControllerId()).get();

View File

@@ -139,7 +139,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest {
final Target createTarget = testdataFactory.createTarget("t" + month);
final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet,
Lists.newArrayList(createTarget));
controllerManagament.registerRetrieved(result.getActions().get(0),
controllerManagement.registerRetrieved(result.getActions().get(0),
"Controller retrieved update action and should start now the download.");
}
DataReportSeries<LocalDate> feedbackReceivedOverTime = reportManagement
@@ -160,7 +160,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest {
final Target createTarget = testdataFactory.createTarget("t2" + month);
final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet,
Lists.newArrayList(createTarget));
controllerManagament.registerRetrieved(result.getActions().get(0),
controllerManagement.registerRetrieved(result.getActions().get(0),
"Controller retrieved update action and should start now the download.");
}
feedbackReceivedOverTime = reportManagement.feedbackReceivedOverTime(DateTypes.perMonth(), from, to);

Some files were not shown because too many files have changed in this diff Show More