Protection against misuse with system-wide quota definitions (#670)

* Added quota for meta data per software module

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Added unit test for "meta data per module" quota

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* quota test enhancements

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Verify enforcement of meta data quota via REST

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota for distribution set meta data

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Verify enforcement of distribution set meta data quota via REST

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* software modules per distribution set quota

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Integration test enhancements for Modules per DistSet quota

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota for software module types per distribution set type

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota for max artifacts per software module

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quotas for ActionStatus per Action and Messages per ActionStatus

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota attributes per target

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota targets per rollout group

Signed-off-by: stefbehl <stefan.behl@bosch-si.com>

* Quota max targets per rollout group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per rollout group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per rollout group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per rollout group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* quota max actions per target

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per rollout group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max actions per target

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max actions per target

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per auto assignment

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per manual assignment

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per auto assignment

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per auto assign

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per auto assignment

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max actions per target

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per manual assignment

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix issues caused by merge

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Improve JavaDoc

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar issues

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max artifact size

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Optimize quota configuration

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix test failures

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Improve test coverage

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max rollout groups per rollout

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Configure Rollout UI enhancements

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* UI enhancements

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Minor changes

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Quota max targets per group

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar finding

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix code review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* New approach for 'max artifact size' enforcement

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix failing tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Reduce max artifact size for tests

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Kai's review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
Stefan Behl
2018-05-02 12:09:29 +02:00
committed by Kai Zimmermann
parent fcc15a0484
commit 6dd98d2134
63 changed files with 2383 additions and 523 deletions

View File

@@ -29,7 +29,6 @@ import com.google.common.io.ByteStreams;
/** /**
* Abstract utility class for ArtifactRepository implementations with common * Abstract utility class for ArtifactRepository implementations with common
* functionality, e.g. computation of hashes. * functionality, e.g. computation of hashes.
*
*/ */
public abstract class AbstractArtifactRepository implements ArtifactRepository { public abstract class AbstractArtifactRepository implements ArtifactRepository {
@@ -71,7 +70,7 @@ public abstract class AbstractArtifactRepository implements ArtifactRepository {
} catch (final IOException e) { } catch (final IOException e) {
throw new ArtifactStoreException(e.getMessage(), e); throw new ArtifactStoreException(e.getMessage(), e);
} finally { } finally {
if (file.exists() && !file.delete()) { if (file != null && file.exists() && !file.delete()) {
LOG.error("Could not delete temp file {}", file); LOG.error("Could not delete temp file {}", file);
} }
} }

View File

@@ -60,8 +60,6 @@ import ru.yandex.qatools.allure.annotations.Stories;
@SpringApplicationConfiguration(classes = DownloadTestConfiguration.class) @SpringApplicationConfiguration(classes = DownloadTestConfiguration.class)
public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest { public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
private static final int ARTIFACT_SIZE = 5 * 1024 * 1024;
private static volatile int downLoadProgress = 0; private static volatile int downLoadProgress = 0;
private static volatile long shippedBytes = 0; private static volatile long shippedBytes = 0;
@@ -84,9 +82,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
assignDistributionSet(ds, targets); assignDistributionSet(ds, targets);
// create artifact // create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false); ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
// no artifact available // no artifact available
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/123455", mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/123455",
@@ -170,9 +169,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
// create artifact // create artifact
final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE); final int artifactSize = 5 * 1024 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false); ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
// download fails as artifact is not yet assigned // download fails as artifact is not yet assigned
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
@@ -180,11 +180,9 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// now assign and download successful // now assign and download successful
assignDistributionSet(ds, targets); assignDistributionSet(ds, targets);
final MvcResult result = mvc final MvcResult result = mvc.perform(get(
.perform( "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds),
artifact.getFilename()))
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Accept-Ranges", "bytes"))
.andExpect(header().string("Last-Modified", dateFormat.format(new Date(artifact.getCreatedAt())))) .andExpect(header().string("Last-Modified", dateFormat.format(new Date(artifact.getCreatedAt()))))
@@ -196,7 +194,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// download complete // download complete
assertThat(downLoadProgress).isEqualTo(10); assertThat(downLoadProgress).isEqualTo(10);
assertThat(shippedBytes).isEqualTo(ARTIFACT_SIZE); assertThat(shippedBytes).isEqualTo(artifactSize);
} }
@Test @Test
@@ -211,16 +209,15 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
assignDistributionSet(ds, target); assignDistributionSet(ds, target);
// create artifact // create artifact
final byte random[] = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1",
false); false, artifactSize);
// download // download
final MvcResult result = mvc final MvcResult result = mvc.perform(get(
.perform( "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}.MD5SUM", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), artifact.getFilename()))
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds),
artifact.getFilename()))
.andExpect(status().isOk()).andExpect(header().string("Content-Disposition", .andExpect(status().isOk()).andExpect(header().string("Content-Disposition",
"attachment;filename=" + artifact.getFilename() + ".MD5SUM")) "attachment;filename=" + artifact.getFilename() + ".MD5SUM"))
.andReturn(); .andReturn();
@@ -245,7 +242,7 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
// create artifact // create artifact
final byte random[] = RandomUtils.nextBytes(resultLength); final byte random[] = RandomUtils.nextBytes(resultLength);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "file1",
false); false, resultLength);
assertThat(random.length).isEqualTo(resultLength); assertThat(random.length).isEqualTo(resultLength);
@@ -259,11 +256,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
for (int i = 0; i < resultLength / range; i++) { for (int i = 0; i < resultLength / range; i++) {
final String rangeString = "" + i * range + "-" + ((i + 1) * range - 1); final String rangeString = "" + i * range + "-" + ((i + 1) * range - 1);
final MvcResult result = mvc final MvcResult result = mvc.perform(get(
.perform( "/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1").header("Range",
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") "bytes=" + rangeString))
.header("Range", "bytes=" + rangeString))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Accept-Ranges", "bytes"))
@@ -278,11 +274,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
assertThat(outputStream.toByteArray()).isEqualTo(random); assertThat(outputStream.toByteArray()).isEqualTo(random);
// return last 1000 Bytes // return last 1000 Bytes
MvcResult result = mvc MvcResult result = mvc.perform(
.perform( get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=-1000"))
.header("Range", "bytes=-1000"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Accept-Ranges", "bytes"))
@@ -296,11 +291,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
.isEqualTo(Arrays.copyOfRange(random, resultLength - 1000, resultLength)); .isEqualTo(Arrays.copyOfRange(random, resultLength - 1000, resultLength));
// skip first 1000 Bytes and return the rest // skip first 1000 Bytes and return the rest
result = mvc result = mvc.perform(
.perform( get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=1000-"))
.header("Range", "bytes=1000-"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Accept-Ranges", "bytes"))
@@ -326,11 +320,10 @@ public class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
.andExpect(header().string("Content-Disposition", "attachment;filename=file1")); .andExpect(header().string("Content-Disposition", "attachment;filename=file1"));
// multipart download - first 20 bytes in 2 parts // multipart download - first 20 bytes in 2 parts
result = mvc result = mvc.perform(
.perform( get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",
get("/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}", tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1")
tenantAware.getCurrentTenant(), target.getControllerId(), getOsModule(ds), "file1") .header("Range", "bytes=0-9,10-19"))
.header("Range", "bytes=0-9,10-19"))
.andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(status().isPartialContent()).andExpect(header().string("ETag", artifact.getSha1Hash()))
.andExpect(content().contentType("multipart/byteranges; boundary=THIS_STRING_SEPARATES_MULTIPART")) .andExpect(content().contentType("multipart/byteranges; boundary=THIS_STRING_SEPARATES_MULTIPART"))
.andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("Accept-Ranges", "bytes"))

View File

@@ -21,6 +21,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.rest.util.JsonBuilder; import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
@@ -109,13 +111,13 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
@Test @Test
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) " @Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
+ "upload limitation is inplace which is meant to protect the server from malicious attempts.") + "upload quota is enforced to protect the server from malicious attempts.")
public void putToMuchConfigData() throws Exception { public void putTooMuchConfigData() throws Exception {
testdataFactory.createTarget("4717"); testdataFactory.createTarget("4717");
// initial // initial
Map<String, String> attributes = new HashMap<>(); Map<String, String> attributes = new HashMap<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < quotaManagement.getMaxAttributeEntriesPerTarget(); i++) {
attributes.put("dsafsdf" + i, "sdsds" + i); attributes.put("dsafsdf" + i, "sdsds" + i);
} }
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant()) mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
@@ -126,7 +128,9 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
attributes.put("on too many", "sdsds"); attributes.put("on too many", "sdsds");
mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant()) mvc.perform(put("/{tenant}/controller/v1/4717/configData", tenantAware.getCurrentTenant())
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON)) .content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden()); .andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
} }
@@ -192,8 +196,7 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
} }
@Step @Step
private void putConfigDataWithInvalidUpdateMode(final String configDataPath) private void putConfigDataWithInvalidUpdateMode(final String configDataPath) throws Exception {
throws Exception {
// create some attriutes // create some attriutes
final Map<String, String> attributes = new HashMap<>(); final Map<String, String> attributes = new HashMap<>();

View File

@@ -110,11 +110,12 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds = testdataFactory.createDistributionSet("", true);
final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true);
final byte random[] = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false); false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false); "test1.signature", false, artifactSize);
final Target savedTarget = testdataFactory.createTarget("4712"); final Target savedTarget = testdataFactory.createTarget("4712");
@@ -198,10 +199,9 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash()))) contains(artifactSignature.getSha1Hash())))
.andExpect( .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href", contains(
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/4712/softwaremodules/"
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).get().getId() + findDistributionSetByAction.findFirstModuleByType(osType).get().getId()
+ "/artifacts/test1.signature"))) + "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href",
@@ -268,11 +268,12 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final String visibleMetadataOsKey = "metaDataVisible"; final String visibleMetadataOsKey = "metaDataVisible";
final String visibleMetadataOsValue = "withValue"; final String visibleMetadataOsValue = "withValue";
final byte random[] = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false); false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false); "test1.signature", false, artifactSize);
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(getOsModule(ds)) softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(getOsModule(ds))
.key(visibleMetadataOsKey).value(visibleMetadataOsValue).targetVisible(true)); .key(visibleMetadataOsKey).value(visibleMetadataOsValue).targetVisible(true));
@@ -391,11 +392,12 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds = testdataFactory.createDistributionSet("", true);
final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true); final DistributionSet ds2 = testdataFactory.createDistributionSet("2", true);
final byte random[] = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte random[] = RandomUtils.nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), "test1",
false); false, artifactSize);
final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds), final Artifact artifactSignature = artifactManagement.create(new ByteArrayInputStream(random), getOsModule(ds),
"test1.signature", false); "test1.signature", false, artifactSize);
final Target savedTarget = testdataFactory.createTarget("4712"); final Target savedTarget = testdataFactory.createTarget("4712");

View File

@@ -46,11 +46,13 @@ final class MgmtRestModelMapper {
} }
/** /**
* Convert a action rest type to a action repository type. * Convert the given {@link MgmtActionType} into a corresponding repository
* {@link ActionType}.
* *
* @param actionTypeRest * @param actionTypeRest
* the rest type * the REST representation of the action type
* @return <null> or the action repository type *
* @return <null> or the repository action type
*/ */
public static ActionType convertActionType(final MgmtActionType actionTypeRest) { public static ActionType convertActionType(final MgmtActionType actionTypeRest) {
if (actionTypeRest == null) { if (actionTypeRest == null) {
@@ -70,11 +72,13 @@ final class MgmtRestModelMapper {
} }
/** /**
* Convert a action repository type to rest type. * Converts the given repository {@link ActionType} into a corresponding
* {@link MgmtActionType}.
* *
* @param actionType * @param actionType
* the rest type * the repository representation of the action type
* @return <null> or the action repository type *
* @return <null> or the REST action type
*/ */
public static MgmtActionType convertActionType(final ActionType actionType) { public static MgmtActionType convertActionType(final ActionType actionType) {
if (actionType == null) { if (actionType == null) {

View File

@@ -83,7 +83,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
try (InputStream in = file.getInputStream()) { try (InputStream in = file.getInputStream()) {
final Artifact result = artifactManagement.create(in, softwareModuleId, fileName, final Artifact result = artifactManagement.create(in, softwareModuleId, fileName,
md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(), false, md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(), false,
file.getContentType()); file.getContentType(), file.getSize());
final MgmtArtifact reponse = MgmtSoftwareModuleMapper.toResponse(result); final MgmtArtifact reponse = MgmtSoftwareModuleMapper.toResponse(result);
MgmtSoftwareModuleMapper.addLinks(result, reponse); MgmtSoftwareModuleMapper.addLinks(result, reponse);

View File

@@ -27,9 +27,12 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.IntStream;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
@@ -44,9 +47,11 @@ import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.JsonPath;
@@ -179,6 +184,45 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")) mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(smIDs.size()))); .andExpect(jsonPath("$.size", equalTo(smIDs.size())));
// verify quota enforcement
final int maxSoftwareModules = quotaManagement.getMaxSoftwareModulesPerDistributionSet();
final List<Long> moduleIDs = Lists.newArrayList();
for (int i = 0; i < maxSoftwareModules + 1; ++i) {
moduleIDs.add(testdataFactory.createSoftwareModuleApp("sm" + i).getId());
}
// post assignment
final String jsonIDs = JsonBuilder.ids(moduleIDs.subList(0, maxSoftwareModules - smIDs.size()));
mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(jsonIDs)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// test if size corresponds with quota
mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId()
+ "/assignedSM?limit={limit}", maxSoftwareModules * 2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(maxSoftwareModules)));
// post one more to cause the quota to be exceeded
mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonBuilder.ids(Collections.singletonList(moduleIDs.get(moduleIDs.size() - 1)))))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
// verify quota is also enforced for bulk uploads
final DistributionSet disSet2 = testdataFactory.createDistributionSetWithNoSoftwareModules("Saturn", "4.0");
mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet2.getId() + "/assignedSM")
.contentType(MediaType.APPLICATION_JSON).content(JsonBuilder.ids(moduleIDs)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
// verify size is still 0
mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + disSet2.getId() + "/assignedSM"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.size", equalTo(0)));
} }
@Test @Test
@@ -229,6 +273,48 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
.as("Five targets in repository have DS assigned").hasSize(5); .as("Five targets in repository have DS assigned").hasSize(5);
} }
@Test
@Description("Ensures that multi target assignment is protected by our 'max targets per manual assignment' quota.")
public void assignMultipleTargetsToDistributionSetUntilQuotaIsExceeded() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerManualAssignment();
final List<Target> targets = testdataFactory.createTargets(maxTargets + 1);
final DistributionSet ds = testdataFactory.createDistributionSet();
final JSONArray payload = new JSONArray();
targets.forEach(trg -> payload.put(new JSONObject().put("id", trg.getId())));
mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + ds.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(payload.toString())).andExpect(status().isForbidden());
assertThat(targetManagement.findByAssignedDistributionSet(PAGE, ds.getId()).getContent()).isEmpty();
}
@Test
@Description("Ensures that the 'max actions per target' quota is enforced if the distribution set assignment of a target is changed permanently")
public void changeDistributionSetAssignmentForTargetUntilQuotaIsExceeded() throws Exception {
// create one target
final Target testTarget = testdataFactory.createTarget("trg1");
final int maxActions = quotaManagement.getMaxActionsPerTarget();
// create a set of distribution sets
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
IntStream.range(0, maxActions).forEach(i -> {
// toggle the distribution set
assignDistributionSet(i % 2 == 0 ? ds1 : ds2, testTarget);
});
// assign our test target to another distribution set and verify that
// the 'max actions per target' quota is exceeded
final String json = new JSONArray().put(new JSONObject().put("id", testTarget.getControllerId())).toString();
mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + ds3.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(json)).andExpect(status().isForbidden());
}
@Test @Test
@Description("Ensures that offline reported multi target assignment through API is reflected by the repository.") @Description("Ensures that offline reported multi target assignment through API is reflected by the repository.")
public void offlineAssignmentOfMultipleTargetsToDistributionSet() throws Exception { public void offlineAssignmentOfMultipleTargetsToDistributionSet() throws Exception {
@@ -433,14 +519,13 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
targetFilterQueryManagement.updateAutoAssignDS( targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
targetFilterQueryManagement .create(entityFactory.targetFilterQuery().create().name(knownFilterName).query("name==y")).getId(),
.create(entityFactory.targetFilterQuery().create().name(knownFilterName).query("x==y")).getId(),
createdDs.getId()); createdDs.getId());
// create some dummy target filter queries // create some dummy target filter queries
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("b").query("x==y")); targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("b").query("name==y"));
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("c").query("x==y")); targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("c").query("name==y"));
mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId()
+ "/autoAssignTargetFilters")).andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1))) + "/autoAssignTargetFilters")).andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1)))
@@ -495,16 +580,16 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
private void prepareTestFilters(final String filterNamePrefix, final DistributionSet createdDs) { private void prepareTestFilters(final String filterNamePrefix, final DistributionSet createdDs) {
// create target filter queries that should be found // create target filter queries that should be found
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1").query("x==y")).getId(), .create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "1").query("name==y"))
createdDs.getId()); .getId(), createdDs.getId());
targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement targetFilterQueryManagement.updateAutoAssignDS(targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2").query("x==y")).getId(), .create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "2").query("name==y"))
createdDs.getId()); .getId(), createdDs.getId());
// create some dummy target filter queries // create some dummy target filter queries
targetFilterQueryManagement targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "b").query("x==y")); .create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "b").query("name==y"));
targetFilterQueryManagement targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "c").query("x==y")); .create(entityFactory.targetFilterQuery().create().name(filterNamePrefix + "c").query("name==y"));
} }
@Test @Test
@@ -902,12 +987,12 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
final String knownValue1 = "knownValue1"; final String knownValue1 = "knownValue1";
final String knownValue2 = "knownValue2"; final String knownValue2 = "knownValue2";
final JSONArray jsonArray = new JSONArray(); final JSONArray metaData1 = new JSONArray();
jsonArray.put(new JSONObject().put("key", knownKey1).put("value", knownValue1)); metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1));
jsonArray.put(new JSONObject().put("key", knownKey2).put("value", knownValue2)); metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2));
mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON).content(jsonArray.toString())) .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("[0]key", equalTo(knownKey1))).andExpect(jsonPath("[0]value", equalTo(knownValue1))) .andExpect(jsonPath("[0]key", equalTo(knownKey1))).andExpect(jsonPath("[0]value", equalTo(knownValue1)))
@@ -921,6 +1006,25 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
assertThat(metaKey1.getValue()).isEqualTo(knownValue1); assertThat(metaKey1.getValue()).isEqualTo(knownValue1);
assertThat(metaKey2.getValue()).isEqualTo(knownValue2); assertThat(metaKey2.getValue()).isEqualTo(knownValue2);
// verify quota enforcement
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
final JSONArray metaData2 = new JSONArray();
for (int i = 0; i < maxMetaData - metaData1.length() + 1; ++i) {
metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i));
}
mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON).content(metaData2.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden());
// verify that the number of meta data entries has not changed
// (we cannot use the PAGE constant here as it tries to sort by ID)
assertThat(distributionSetManagement
.findMetaDataByDistributionSetId(new PageRequest(0, Integer.MAX_VALUE), testDS.getId())
.getTotalElements()).isEqualTo(metaData1.length());
} }
@Test @Test

View File

@@ -27,7 +27,11 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.util.Lists;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
@@ -239,6 +243,60 @@ public class MgmtDistributionSetTypeResourceTest extends AbstractManagementApiIn
} }
@Test
@WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Verifies quota enforcement for /rest/v1/distributionsettypes/{ID}/optionalmoduletypes POST requests.")
public void assignModuleTypesToDistributionSetTypeUntilQuotaExceeded() throws Exception {
// create software module types
final int maxSoftwareModuleTypes = quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType();
final List<Long> moduleTypeIds = Lists.newArrayList();
for (int i = 0; i < maxSoftwareModuleTypes + 1; ++i) {
final SoftwareModuleTypeCreate smCreate = entityFactory.softwareModuleType().create().name("smType_" + i)
.description("smType_" + i).maxAssignments(1).colour("blue").key("smType_" + i);
moduleTypeIds.add(softwareModuleTypeManagement.create(smCreate).getId());
}
// verify quota enforcement for optional module types
final DistributionSetType testType = distributionSetTypeManagement.create(entityFactory.distributionSetType()
.create().key("testType").name("testType").description("testType").colour("col12"));
assertThat(testType.getOptLockRevision()).isEqualTo(1);
for (int i = 0; i < moduleTypeIds.size() - 1; ++i) {
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes", testType.getId())
.content("{\"id\":" + moduleTypeIds.get(i) + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/optionalmoduletypes", testType.getId())
.content("{\"id\":" + moduleTypeIds.get(moduleTypeIds.size() - 1) + "}")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
// verify quota enforcement for mandatory module types
final DistributionSetType testType2 = distributionSetTypeManagement.create(entityFactory.distributionSetType()
.create().key("testType2").name("testType2").description("testType2").colour("col12"));
assertThat(testType2.getOptLockRevision()).isEqualTo(1);
for (int i = 0; i < moduleTypeIds.size() - 1; ++i) {
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes", testType2.getId())
.content("{\"id\":" + moduleTypeIds.get(i) + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
}
mvc.perform(post("/rest/v1/distributionsettypes/{dstID}/mandatorymoduletypes", testType2.getId())
.content("{\"id\":" + moduleTypeIds.get(moduleTypeIds.size() - 1) + "}")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test @Test
@WithUser(principal = "uploadTester", allSpPermissions = true) @WithUser(principal = "uploadTester", allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes GET requests.") @Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID}/mandatorymoduletypes GET requests.")

View File

@@ -27,9 +27,11 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
@@ -131,9 +133,45 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
postRollout("rollout1", 10, dsA.getId(), "id==target*", 20); postRollout("rollout1", 10, dsA.getId(), "id==target*", 20);
} }
@Test
@Description("Verifies that rollout cannot be created if too many rollout groups are specified.")
public void createRolloutWithTooManyRolloutGroups() throws Exception {
final int maxGroups = quotaManagement.getMaxRolloutGroupsPerRollout();
testdataFactory.createTargets(20, "target", "rollout");
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout1", "rollout1Desc", maxGroups + 1,
testdataFactory.createDistributionSet("ds").getId(), "id==target*",
new RolloutGroupConditionBuilder().withDefaults().build()))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test
@Description("Verifies that rollout cannot be created if the 'max targets per rollout group' quota would be violated for one of the groups.")
public void createRolloutFailsIfRolloutGroupQuotaIsViolated() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
testdataFactory.createTargets(maxTargets + 1, "target", "rollout");
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout1", "rollout1Desc", 1,
testdataFactory.createDistributionSet("ds").getId(), "id==target*",
new RolloutGroupConditionBuilder().withDefaults().build()))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test @Test
@Description("Testing that rollout can be created with groups") @Description("Testing that rollout can be created with groups")
public void createRolloutWithGroupsDefinitions() throws Exception { public void createRolloutWithGroupDefinitions() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro"); final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
final int amountTargets = 10; final int amountTargets = 10;
@@ -160,7 +198,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
@Test @Test
@Description("Testing that no rollout with groups that have illegal percentages can be created") @Description("Testing that no rollout with groups that have illegal percentages can be created")
public void createRolloutWithToLowlPercentage() throws Exception { public void createRolloutWithTooLowPercentage() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro2"); final DistributionSet dsA = testdataFactory.createDistributionSet("ro2");
final int amountTargets = 10; final int amountTargets = 10;
@@ -185,7 +223,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
@Test @Test
@Description("Testing that no rollout with groups that have illegal percentages can be created") @Description("Testing that no rollout with groups that have illegal percentages can be created")
public void createRolloutWithToHighPercentage() throws Exception { public void createRolloutWithTooHighPercentage() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro2"); final DistributionSet dsA = testdataFactory.createDistributionSet("ro2");
final int amountTargets = 10; final int amountTargets = 10;
@@ -217,7 +255,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes
} }
@Test @Test
@Description("Terives sinle rollout from management API includinfg extra data that is delieverd only for single rollout access.") @Description("Retrieves single rollout from management API including extra data that is delivered only for single rollout access.")
public void retrieveSingleRollout() throws Exception { public void retrieveSingleRollout() throws Exception {
testdataFactory.createTargets(20, "rollout", "rollout"); testdataFactory.createTargets(20, "rollout", "rollout");
final DistributionSet dsA = testdataFactory.createDistributionSet(""); final DistributionSet dsA = testdataFactory.createDistributionSet("");

View File

@@ -29,6 +29,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Random;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
@@ -36,6 +37,7 @@ import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact; import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.Constants; import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
@@ -49,8 +51,10 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -66,6 +70,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
*/ */
@Features("Component Tests - Management API") @Features("Component Tests - Management API")
@Stories("Software Module Resource") @Stories("Software Module Resource")
@TestPropertySource(properties = { "hawkbit.server.security.dos.maxArtifactSize=100000" })
public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTest { public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTest {
@Before @Before
@@ -147,7 +152,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
} }
@Test @Test
@Description("Tests the uppload of an artifact binary. The upload is executed and the content checked in the repository for completenes.") @Description("Tests the upload of an artifact binary. The upload is executed and the content checked in the repository for completeness.")
public void uploadArtifact() throws Exception { public void uploadArtifact() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
@@ -183,6 +188,24 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
assertArtifact(sm, random); assertArtifact(sm, random);
} }
@Test
@Description("Verifies that artifacts which exceed the configured maximum size cannot be uploaded.")
public void uploadArtifactFailsIfTooLarge() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModule("quota", "quota");
final long maxSize = quotaManagement.getMaxArtifactSize();
// create a file which exceeds the configured maximum size
final byte[] randomBytes = new byte[Math.toIntExact(maxSize) + 1024];
new Random().nextBytes(randomBytes);
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, randomBytes);
// try to upload
mvc.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
private void assertArtifact(final SoftwareModule sm, final byte[] random) throws IOException { private void assertArtifact(final SoftwareModule sm, final byte[] random) throws IOException {
// check result in db... // check result in db...
// repo // repo
@@ -312,17 +335,57 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
} }
@Test
@Description("Verifies that only a limited number of artifacts can be uploaded for one software module.")
public void uploadArtifactsUntilQuotaExceeded() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final long maxArtifacts = quotaManagement.getMaxArtifactsPerSoftwareModule();
for (int i = 0; i < maxArtifacts; ++i) {
// create test file
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final String md5sum = HashGeneratorUtils.generateMD5(random);
final String sha1sum = HashGeneratorUtils.generateSHA1(random);
final MockMultipartFile file = new MockMultipartFile("file", "origFilename" + i, null, random);
// upload
mvc.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.hashes.md5", equalTo(md5sum)))
.andExpect(jsonPath("$.hashes.sha1", equalTo(sha1sum)))
.andExpect(jsonPath("$.size", equalTo(random.length)))
.andExpect(jsonPath("$.providedFilename", equalTo("origFilename" + i))).andReturn();
}
// upload one more file to cause the quota to be exceeded
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
HashGeneratorUtils.generateMD5(random);
HashGeneratorUtils.generateSHA1(random);
final MockMultipartFile file = new MockMultipartFile("file", "origFilename_final", null, random);
// upload
mvc.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.exceptionClass", equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath("$.errorCode", equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test @Test
@Description("Tests binary download of an artifact including verfication that the downloaded binary is consistent and that the etag header is as expected identical to the SHA1 hash of the file.") @Description("Tests binary download of an artifact including verfication that the downloaded binary is consistent and that the etag header is as expected identical to the SHA1 hash of the file.")
public void downloadArtifact() throws Exception { public void downloadArtifact() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false); false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2", final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2",
false); false, artifactSize);
downloadAndVerify(sm, random, artifact); downloadAndVerify(sm, random, artifact);
downloadAndVerify(sm, random, artifact2); downloadAndVerify(sm, random, artifact2);
@@ -348,9 +411,11 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
// prepare data for test // prepare data for test
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false); false, artifactSize);
// perform test // perform test
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId()) mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId())
@@ -373,12 +438,13 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
public void getArtifacts() throws Exception { public void getArtifacts() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false); false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2", final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2",
false); false, artifactSize);
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).accept(MediaType.APPLICATION_JSON)) mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -402,7 +468,9 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
@Test @Test
@Description("Verfies that the system refuses unsupported request types and answers as defined to them, e.g. NOT FOUND on a non existing resource. Or a HTTP POST for updating a resource results in METHOD NOT ALLOWED etc.") @Description("Verfies that the system refuses unsupported request types and answers as defined to them, e.g. NOT FOUND on a non existing resource. Or a HTTP POST for updating a resource results in METHOD NOT ALLOWED etc.")
public void invalidRequestsOnArtifactResource() throws Exception { public void invalidRequestsOnArtifactResource() throws Exception {
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final MockMultipartFile file = new MockMultipartFile("file", "orig", null, random); final MockMultipartFile file = new MockMultipartFile("file", "orig", null, random);
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
@@ -415,7 +483,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
// SM does not exist // SM does not exist
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize);
mvc.perform(get("/rest/v1/softwaremodules/1234567890/artifacts")).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/rest/v1/softwaremodules/1234567890/artifacts")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
@@ -723,9 +791,10 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize);
assertThat(softwareModuleManagement.findAll(PAGE)).as("Softwaremoudle size is wrong").hasSize(1); assertThat(softwareModuleManagement.findAll(PAGE)).as("Softwaremoudle size is wrong").hasSize(1);
assertThat(artifactManagement.count()).isEqualTo(1); assertThat(artifactManagement.count()).isEqualTo(1);
@@ -743,11 +812,12 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
public void deleteAssignedSoftwareModule() throws Exception { public void deleteAssignedSoftwareModule() throws Exception {
final DistributionSet ds1 = testdataFactory.createDistributionSet("a"); final DistributionSet ds1 = testdataFactory.createDistributionSet("a");
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Long appTypeSmId = ds1.findFirstModuleByType(appType).get().getId(); final Long appTypeSmId = ds1.findFirstModuleByType(appType).get().getId();
artifactManagement.create(new ByteArrayInputStream(random), appTypeSmId, "file1", false); artifactManagement.create(new ByteArrayInputStream(random), appTypeSmId, "file1", false, artifactSize);
assertThat(softwareModuleManagement.count()).isEqualTo(3); assertThat(softwareModuleManagement.count()).isEqualTo(3);
assertThat(artifactManagement.count()).isEqualTo(1); assertThat(artifactManagement.count()).isEqualTo(1);
@@ -771,12 +841,13 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
// Create 1 SM // Create 1 SM
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
// Create 2 artifacts // Create 2 artifacts
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1",
false); false, artifactSize);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2", false); artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize);
// check repo before delete // check repo before delete
assertThat(softwareModuleManagement.findAll(PAGE)).hasSize(1); assertThat(softwareModuleManagement.findAll(PAGE)).hasSize(1);
@@ -797,7 +868,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
} }
@Test @Test
@Description("Verfies the successfull creation of metadata.") @Description("Verfies the successful creation of metadata and the enforcement of the meta data quota.")
public void createMetadata() throws Exception { public void createMetadata() throws Exception {
final String knownKey1 = "knownKey1"; final String knownKey1 = "knownKey1";
@@ -807,12 +878,12 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final JSONArray jsonArray = new JSONArray(); final JSONArray metaData1 = new JSONArray();
jsonArray.put(new JSONObject().put("key", knownKey1).put("value", knownValue1)); metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1));
jsonArray.put(new JSONObject().put("key", knownKey2).put("value", knownValue2).put("targetVisible", true)); metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2).put("targetVisible", true));
mvc.perform(post("/rest/v1/softwaremodules/{swId}/metadata", sm.getId()).accept(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/softwaremodules/{swId}/metadata", sm.getId()).accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON).content(jsonArray.toString())) .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("[0]key", equalTo(knownKey1))).andExpect(jsonPath("[0]value", equalTo(knownValue1))) .andExpect(jsonPath("[0]key", equalTo(knownKey1))).andExpect(jsonPath("[0]value", equalTo(knownValue1)))
@@ -827,6 +898,25 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
assertThat(metaKey1.getValue()).as("Metadata key is wrong").isEqualTo(knownValue1); assertThat(metaKey1.getValue()).as("Metadata key is wrong").isEqualTo(knownValue1);
assertThat(metaKey2.getValue()).as("Metadata key is wrong").isEqualTo(knownValue2); assertThat(metaKey2.getValue()).as("Metadata key is wrong").isEqualTo(knownValue2);
// verify quota enforcement
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
final JSONArray metaData2 = new JSONArray();
for (int i = 0; i < maxMetaData - metaData1.length() + 1; ++i) {
metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i));
}
mvc.perform(post("/rest/v1/softwaremodules/{swId}/metadata", sm.getId()).accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON).content(metaData2.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden());
// verify that the number of meta data entries has not changed
// (we cannot use the PAGE constant here as it tries to sort by ID)
assertThat(softwareModuleManagement
.findMetaDataBySoftwareModuleId(new PageRequest(0, Integer.MAX_VALUE), sm.getId()).getTotalElements())
.isEqualTo(metaData1.length());
} }
@Test @Test
@@ -930,4 +1020,5 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra
character++; character++;
} }
} }
} }

View File

@@ -21,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
@@ -53,6 +54,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
private static final String JSON_PATH_FIELD_SIZE = ".size"; private static final String JSON_PATH_FIELD_SIZE = ".size";
private static final String JSON_PATH_FIELD_TOTAL = ".total"; private static final String JSON_PATH_FIELD_TOTAL = ".total";
private static final String JSON_PATH_FIELD_AUTO_ASSIGN_DS = ".autoAssignDistributionSet"; private static final String JSON_PATH_FIELD_AUTO_ASSIGN_DS = ".autoAssignDistributionSet";
private static final String JSON_PATH_FIELD_EXCEPTION_CLASS = ".exceptionClass";
private static final String JSON_PATH_FIELD_ERROR_CODE = ".errorCode";
// target // target
// $.field // $.field
@@ -64,6 +67,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
private static final String JSON_PATH_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID; private static final String JSON_PATH_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID;
private static final String JSON_PATH_QUERY = JSON_PATH_ROOT + JSON_PATH_FIELD_QUERY; private static final String JSON_PATH_QUERY = JSON_PATH_ROOT + JSON_PATH_FIELD_QUERY;
private static final String JSON_PATH_AUTO_ASSIGN_DS = JSON_PATH_ROOT + JSON_PATH_FIELD_AUTO_ASSIGN_DS; private static final String JSON_PATH_AUTO_ASSIGN_DS = JSON_PATH_ROOT + JSON_PATH_FIELD_AUTO_ASSIGN_DS;
private static final String JSON_PATH_EXCEPTION_CLASS = JSON_PATH_ROOT + JSON_PATH_FIELD_EXCEPTION_CLASS;
private static final String JSON_PATH_ERROR_CODE = JSON_PATH_ROOT + JSON_PATH_FIELD_ERROR_CODE;
@Test @Test
@Description("Ensures that deletion is executed if permitted.") @Description("Ensures that deletion is executed if permitted.")
@@ -126,8 +131,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
final String body = new JSONObject().put("name", filterName2).toString(); final String body = new JSONObject().put("name", filterName2).toString();
// prepare // prepare
final TargetFilterQuery tfq = targetFilterQueryManagement.create( final TargetFilterQuery tfq = targetFilterQueryManagement
entityFactory.targetFilterQuery().create().name(filterName).query(filterQuery)); .create(entityFactory.targetFilterQuery().create().name(filterName).query(filterQuery));
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body) mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body)
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -230,6 +235,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
} }
@Test @Test
@Description("Ensures that a single target filter query can be retrieved via its id.")
public void getSingleTarget() throws Exception { public void getSingleTarget() throws Exception {
// create first a target which can be retrieved by rest interface // create first a target which can be retrieved by rest interface
final String knownQuery = "name=test01"; final String knownQuery = "name=test01";
@@ -247,6 +253,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
} }
@Test @Test
@Description("Ensures that the retrieval of a non-existing target filter query results in a HTTP Not found error (404).")
public void getSingleTargetNoExistsResponseNotFound() throws Exception { public void getSingleTargetNoExistsResponseNotFound() throws Exception {
final String targetIdNotExists = "546546"; final String targetIdNotExists = "546546";
// test // test
@@ -262,6 +269,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
} }
@Test @Test
@Description("Ensures that the creation of a target filter query based on an invalid request payload results in a HTTP Bad Request error (400).")
public void createTargetFilterQueryWithBadPayloadBadRequest() throws Exception { public void createTargetFilterQueryWithBadPayloadBadRequest() throws Exception {
final String notJson = "abc"; final String notJson = "abc";
@@ -279,22 +287,72 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()); assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey());
} }
@Test
@Description("Ensures that the assignment of an auto-assign distribution set results in a HTTP Forbidden error (403) "
+ "if the (existing) query addresses too many targets.")
public void setAutoAssignDistributionSetOnFilterQueryThatExceedsQuota() throws Exception {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
// create the filter query and the distribution set
final DistributionSet set = testdataFactory.createDistributionSet();
final TargetFilterQuery filterQuery = createSingleTargetFilterQuery("1", "controllerId==target*");
mvc.perform(
post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test
@Description("Ensures that the update of a target filter query results in a HTTP Forbidden error (403) "
+ "if the updated query addresses too many targets.")
public void updateTargetFilterQueryWithQueryThatExceedsQuota() throws Exception {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
// create the filter query and the distribution set
final DistributionSet set = testdataFactory.createDistributionSet();
final TargetFilterQuery filterQuery = createSingleTargetFilterQuery("1", "controllerId==target1");
// assign the auto-assign distribution set, this should work
mvc.perform(
post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetFilterQueryManagement.get(filterQuery.getId()).get().getAutoAssignDistributionSet())
.isEqualTo(set);
// update the query of the filter query to trigger a quota hit
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId())
.content("{\"query\":\"controllerId==target*\"}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(QuotaExceededException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
}
@Test @Test
public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception { public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception {
final String knownQuery = "name=test05"; final String knownQuery = "name==test05";
final String knownName = "filter05"; final String knownName = "filter05";
final DistributionSet set = testdataFactory.createDistributionSet("one"); final DistributionSet set = testdataFactory.createDistributionSet();
final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery);
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON)) .content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat( assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isEqualTo(set);
targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet())
.isEqualTo(set);
final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" final String hrefPrefix = "http://localhost" + MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"
+ tfq.getId(); + tfq.getId();
@@ -311,7 +369,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
@Test @Test
public void deleteAutoAssignDistributionSetOfTargetFilterQuery() throws Exception { public void deleteAutoAssignDistributionSetOfTargetFilterQuery() throws Exception {
final String knownQuery = "name=test06"; final String knownQuery = "name==test06";
final String knownName = "filter06"; final String knownName = "filter06";
final String dsName = "testDS"; final String dsName = "testDS";
@@ -319,9 +377,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery);
targetFilterQueryManagement.updateAutoAssignDS(tfq.getId(), set.getId()); targetFilterQueryManagement.updateAutoAssignDS(tfq.getId(), set.getId());
assertThat( assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isEqualTo(set);
targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet())
.isEqualTo(set);
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS"))
.andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(dsName))); .andExpect(status().isOk()).andExpect(jsonPath(JSON_PATH_NAME, equalTo(dsName)));
@@ -329,9 +385,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS"))
.andExpect(status().isNoContent()); .andExpect(status().isNoContent());
assertThat( assertThat(targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet()).isNull();
targetFilterQueryManagement.get(tfq.getId()).get().getAutoAssignDistributionSet())
.isNull();
mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")) mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS"))
.andExpect(status().isNoContent()); .andExpect(status().isNoContent());
@@ -339,8 +393,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
} }
private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) { private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) {
return targetFilterQueryManagement return targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name(name).query(query));
.create(entityFactory.targetFilterQuery().create().name(name).query(query));
} }
} }

View File

@@ -54,6 +54,8 @@ public interface ArtifactManagement {
* @param overrideExisting * @param overrideExisting
* to <code>true</code> if the artifact binary can be overridden * to <code>true</code> if the artifact binary can be overridden
* if it already exists * if it already exists
* @param filesize
* the size of the file in bytes.
* *
* @return uploaded {@link Artifact} * @return uploaded {@link Artifact}
* *
@@ -64,7 +66,7 @@ public interface ArtifactManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
Artifact create(@NotNull InputStream inputStream, long moduleId, final String filename, Artifact create(@NotNull InputStream inputStream, long moduleId, final String filename,
final boolean overrideExisting); final boolean overrideExisting, final long filesize);
/** /**
* Persists artifact binary as provided by given InputStream. assign the * Persists artifact binary as provided by given InputStream. assign the
@@ -85,6 +87,9 @@ public interface ArtifactManagement {
* if it already exists * if it already exists
* @param contentType * @param contentType
* the contentType of the file * the contentType of the file
* @param filesize
* the size of the file in bytes.
*
* @return uploaded {@link Artifact} * @return uploaded {@link Artifact}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -100,7 +105,7 @@ public interface ArtifactManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
Artifact create(@NotNull InputStream stream, long moduleId, @NotEmpty String filename, String providedMd5Sum, Artifact create(@NotNull InputStream stream, long moduleId, @NotEmpty String filename, String providedMd5Sum,
String providedSha1Sum, boolean overrideExisting, String contentType); String providedSha1Sum, boolean overrideExisting, String contentType, long filesize);
/** /**
* Garbage collects artifact binaries if only referenced by given * Garbage collects artifact binaries if only referenced by given

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEv
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
@@ -45,7 +46,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface DeploymentManagement { public interface DeploymentManagement {
/** /**
* method assigns the {@link DistributionSet} to all {@link Target}s by * Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}. * their IDs with a specific {@link ActionType} and {@code forcetime}.
* *
* @param dsID * @param dsID
@@ -66,13 +67,17 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotNull ActionType actionType, DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotNull ActionType actionType,
long forcedTimestamp, @NotEmpty Collection<String> controllerIDs); long forcedTimestamp, @NotEmpty Collection<String> controllerIDs);
/** /**
* method assigns the {@link DistributionSet} to all {@link Target}s by * Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}. * their IDs with a specific {@link ActionType} and {@code forcetime}.
* *
* @param dsID * @param dsID
@@ -88,13 +93,17 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, DistributionSetAssignmentResult assignDistributionSet(long dsID,
@NotEmpty Collection<TargetWithActionType> targets); @NotEmpty Collection<TargetWithActionType> targets);
/** /**
* method assigns the {@link DistributionSet} to all {@link Target}s by * Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and an action message. * their IDs with a specific {@link ActionType} and an action message.
* *
* @param dsID * @param dsID
@@ -112,16 +121,20 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotEmpty Collection<TargetWithActionType> targets, DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotEmpty Collection<TargetWithActionType> targets,
String actionMessage); String actionMessage);
/** /**
* Method registers an "offline" assignment, i.e. adds a completed action * Registers an "offline" assignment, i.e. adds a completed action for the
* for the given {@link DistributionSet} to the given {@link Target}s. * given {@link DistributionSet} to the given {@link Target}s.
* *
* The handling differs to hawkBit managed updates my means that:<br/> * The handling differs to hawkBit-managed updates by means that:<br/>
* *
* <ol type="A"> * <ol type="A">
* <li>it ignores targets completely that are in * <li>it ignores targets completely that are in
@@ -145,15 +158,18 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult offlineAssignedDistributionSet(Long dsID, Collection<String> controllerIDs); DistributionSetAssignmentResult offlineAssignedDistributionSet(Long dsID, Collection<String> controllerIDs);
/** /**
* Cancels given {@link Action} for given {@link Target}. The method will * Cancels the {@link Action} with the given ID. The method will immediately
* immediately add a {@link Status#CANCELED} status to the action. However, * add a {@link Status#CANCELED} status to the action. However, it might be
* it might be possible that the controller will continue to work on the * possible that the controller will continue to work on the cancellation.
* cancellation.
* *
* @param actionId * @param actionId
* to be canceled * to be canceled
@@ -170,7 +186,7 @@ public interface DeploymentManagement {
Action cancelAction(long actionId); Action cancelAction(long actionId);
/** /**
* counts all actions associated to a specific target. * Counts all actions associated to a specific target.
* *
* @param rsqlParam * @param rsqlParam
* rsql query string * rsql query string
@@ -202,7 +218,7 @@ public interface DeploymentManagement {
long countActionsAll(); long countActionsAll();
/** /**
* counts all actions associated to a specific target. * Counts all actions associated to a specific target.
* *
* @param controllerId * @param controllerId
* the target associated to the actions to count * the target associated to the actions to count

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
@@ -52,6 +53,7 @@ public interface DistributionSetManagement
* to assign and update * to assign and update
* @param moduleIds * @param moduleIds
* to get assigned * to get assigned
*
* @return the updated {@link DistributionSet}. * @return the updated {@link DistributionSet}.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -62,8 +64,12 @@ public interface DistributionSetManagement
* the DS is already in use. * the DS is already in use.
* *
* @throws UnsupportedSoftwareModuleForThisDistributionSetException * @throws UnsupportedSoftwareModuleForThisDistributionSetException
* is {@link SoftwareModule#getType()} is not supported by this * if {@link SoftwareModule#getType()} is not supported by this
* {@link DistributionSet#getType()}. * {@link DistributionSet#getType()}.
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModule}s is exceeded
* for the addressed {@link DistributionSet}.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSet assignSoftwareModules(long setId, @NotEmpty Collection<Long> moduleIds); DistributionSet assignSoftwareModules(long setId, @NotEmpty Collection<Long> moduleIds);
@@ -76,6 +82,7 @@ public interface DistributionSetManagement
* to assign for * to assign for
* @param tagId * @param tagId
* to assign * to assign
*
* @return list of assigned ds * @return list of assigned ds
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -86,7 +93,7 @@ public interface DistributionSetManagement
List<DistributionSet> assignTag(@NotEmpty Collection<Long> setIds, long tagId); List<DistributionSet> assignTag(@NotEmpty Collection<Long> setIds, long tagId);
/** /**
* creates a list of distribution set meta data entries. * Creates a list of distribution set meta data entries.
* *
* @param setId * @param setId
* if the {@link DistributionSet} the metadata has to be created * if the {@link DistributionSet} the metadata has to be created
@@ -97,15 +104,20 @@ public interface DistributionSetManagement
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given set does not exist * if given set does not exist
*
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the * in case one of the meta data entry already exists for the
* specific key * specific key
*
* @throws QuotaExceededException
* if the maximum number of {@link MetaData} entries is exceeded
* for the addressed {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<DistributionSetMetadata> createMetaData(long setId, @NotEmpty Collection<MetaData> metadata); List<DistributionSetMetadata> createMetaData(long setId, @NotEmpty Collection<MetaData> metadata);
/** /**
* deletes a distribution set meta data entry. * Deletes a distribution set meta data entry.
* *
* @param setId * @param setId
* where meta data has to be deleted * where meta data has to be deleted
@@ -119,7 +131,7 @@ public interface DistributionSetManagement
void deleteMetaData(long setId, @NotEmpty String key); void deleteMetaData(long setId, @NotEmpty String key);
/** /**
* retrieves the distribution set for a given action. * Retrieves the distribution set for a given action.
* *
* @param actionId * @param actionId
* the action associated with the distribution set * the action associated with the distribution set
@@ -140,6 +152,7 @@ public interface DistributionSetManagement
* *
* @param setId * @param setId
* to look for. * to look for.
*
* @return {@link DistributionSet} * @return {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -152,13 +165,14 @@ public interface DistributionSetManagement
* name of {@link DistributionSet}; case insensitive * name of {@link DistributionSet}; case insensitive
* @param version * @param version
* version of {@link DistributionSet} * version of {@link DistributionSet}
*
* @return the page with the found {@link DistributionSet} * @return the page with the found {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<DistributionSet> getByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version); Optional<DistributionSet> getByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version);
/** /**
* finds all meta data by the given distribution set id. * Finds all meta data by the given distribution set id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
@@ -175,7 +189,7 @@ public interface DistributionSetManagement
Page<DistributionSetMetadata> findMetaDataByDistributionSetId(@NotNull Pageable pageable, long setId); Page<DistributionSetMetadata> findMetaDataByDistributionSetId(@NotNull Pageable pageable, long setId);
/** /**
* finds all meta data by the given distribution set id. * Finds all meta data by the given distribution set id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
@@ -190,6 +204,7 @@ public interface DistributionSetManagement
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
* *
@@ -201,7 +216,7 @@ public interface DistributionSetManagement
@NotNull String rsqlParam); @NotNull String rsqlParam);
/** /**
* finds all {@link DistributionSet}s. * Finds all {@link DistributionSet}s.
* *
* @param pageable * @param pageable
* the pagination parameter * the pagination parameter
@@ -210,14 +225,13 @@ public interface DistributionSetManagement
* sets or <code>false</code> for only incomplete ones nor * sets or <code>false</code> for only incomplete ones nor
* <code>null</code> to return both. * <code>null</code> to return both.
* *
*
* @return all found {@link DistributionSet}s * @return all found {@link DistributionSet}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSet> findByCompleted(@NotNull Pageable pageable, Boolean complete); Page<DistributionSet> findByCompleted(@NotNull Pageable pageable, Boolean complete);
/** /**
* method retrieves all {@link DistributionSet}s from the repository in the * Method retrieves all {@link DistributionSet}s from the repository in the
* following order: * following order:
* <p> * <p>
* 1) {@link DistributionSet}s which have the given {@link Target} as * 1) {@link DistributionSet}s which have the given {@link Target} as
@@ -235,6 +249,7 @@ public interface DistributionSetManagement
* has details of filters to be applied * has details of filters to be applied
* @param assignedOrInstalled * @param assignedOrInstalled
* the id of the Target to be ordered by * the id of the Target to be ordered by
*
* @return {@link DistributionSet}s * @return {@link DistributionSet}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -242,7 +257,7 @@ public interface DistributionSetManagement
@NotNull DistributionSetFilterBuilder distributionSetFilterBuilder, @NotEmpty String assignedOrInstalled); @NotNull DistributionSetFilterBuilder distributionSetFilterBuilder, @NotEmpty String assignedOrInstalled);
/** /**
* retrieves {@link DistributionSet}s by filtering on the given parameters. * Retrieves {@link DistributionSet}s by filtering on the given parameters.
* *
* @param pageable * @param pageable
* page parameter * page parameter
@@ -255,7 +270,7 @@ public interface DistributionSetManagement
@NotNull DistributionSetFilter distributionSetFilter); @NotNull DistributionSetFilter distributionSetFilter);
/** /**
* retrieves {@link DistributionSet}s by filtering on the given parameters. * Retrieves {@link DistributionSet}s by filtering on the given parameters.
* *
* @param pageable * @param pageable
* page parameter * page parameter
@@ -266,6 +281,7 @@ public interface DistributionSetManagement
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
* *
@@ -276,7 +292,7 @@ public interface DistributionSetManagement
Page<DistributionSet> findByTag(@NotNull Pageable pageable, long tagId); Page<DistributionSet> findByTag(@NotNull Pageable pageable, long tagId);
/** /**
* retrieves {@link DistributionSet}s by filtering on the given parameters. * Retrieves {@link DistributionSet}s by filtering on the given parameters.
* *
* @param pageable * @param pageable
* page parameter * page parameter
@@ -293,7 +309,7 @@ public interface DistributionSetManagement
Page<DistributionSet> findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId); Page<DistributionSet> findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId);
/** /**
* finds a single distribution set meta data by its id. * Finds a single distribution set meta data by its id.
* *
* @param setId * @param setId
* of the {@link DistributionSet} * of the {@link DistributionSet}
@@ -375,7 +391,7 @@ public interface DistributionSetManagement
DistributionSet unAssignTag(long setId, long tagId); DistributionSet unAssignTag(long setId, long tagId);
/** /**
* updates a distribution set meta data value if corresponding entry exists. * Updates a distribution set meta data value if corresponding entry exists.
* *
* @param setId * @param setId
* {@link DistributionSet} of the meta data entry to be updated * {@link DistributionSet} of the meta data entry to be updated

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate; import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
@@ -63,6 +64,10 @@ public interface DistributionSetTypeManagement
* @throws EntityReadOnlyException * @throws EntityReadOnlyException
* if the {@link DistributionSetType} while it is already in use * if the {@link DistributionSetType} while it is already in use
* by a {@link DistributionSet} * by a {@link DistributionSet}
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleType}s is
* exceeded for the addressed {@link DistributionSetType}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSetType assignOptionalSoftwareModuleTypes(long dsTypeId, DistributionSetType assignOptionalSoftwareModuleTypes(long dsTypeId,
@@ -84,6 +89,10 @@ public interface DistributionSetTypeManagement
* @throws EntityReadOnlyException * @throws EntityReadOnlyException
* if the {@link DistributionSetType} while it is already in use * if the {@link DistributionSetType} while it is already in use
* by a {@link DistributionSet} * by a {@link DistributionSet}
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleType}s is
* exceeded for the addressed {@link DistributionSetType}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSetType assignMandatorySoftwareModuleTypes(long dsTypeId, DistributionSetType assignMandatorySoftwareModuleTypes(long dsTypeId,

View File

@@ -20,7 +20,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
public interface QuotaManagement { public interface QuotaManagement {
/** /**
* @return Maximum number of {@link ActionStatus} entries that the * @return maximum number of {@link ActionStatus} entries that the
* controller can report for an {@link Action}. * controller can report for an {@link Action}.
*/ */
int getMaxStatusEntriesPerAction(); int getMaxStatusEntriesPerAction();
@@ -43,4 +43,57 @@ public interface QuotaManagement {
*/ */
int getMaxMessagesPerActionStatus(); int getMaxMessagesPerActionStatus();
/**
* @return maximum number of meta data entries per software module
*/
int getMaxMetaDataEntriesPerSoftwareModule();
/**
* @return maximum number of meta data entries per distribution set
*/
int getMaxMetaDataEntriesPerDistributionSet();
/**
* @return maximum number of software modules per distribution set
*/
int getMaxSoftwareModulesPerDistributionSet();
/**
* @return the maximum number of software module types per distribution set
* type
*/
int getMaxSoftwareModuleTypesPerDistributionSetType();
/**
* @return the maximum number of artifacts per software module
*/
int getMaxArtifactsPerSoftwareModule();
/**
* @return the maximum number of targets per rollout group
*/
int getMaxTargetsPerRolloutGroup();
/**
* @return the maximum number of targets which for a manual distribution set
* assignment
*/
int getMaxTargetsPerManualAssignment();
/**
* @return the maximum number of targets for an automatic distribution set
* assignment
*/
int getMaxTargetsPerAutoAssignment();
/**
* @return the maximum number of actions per target
*/
int getMaxActionsPerTarget();
/**
* @return the maximum size of software artifacts in bytes
*/
long getMaxArtifactSize();
} }

View File

@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate; import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -123,6 +124,9 @@ public interface RolloutManagement {
* if given {@link DistributionSet} does not exist * if given {@link DistributionSet} does not exist
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if rollout or group parameters are invalid. * if rollout or group parameters are invalid.
* @throws QuotaExceededException
* if the maximum number of allowed targets per rollout group is
* exceeded.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull RolloutCreate create, int amountGroup, @NotNull RolloutGroupConditions conditions); Rollout create(@NotNull RolloutCreate create, int amountGroup, @NotNull RolloutGroupConditions conditions);
@@ -157,6 +161,9 @@ public interface RolloutManagement {
* if given {@link DistributionSet} does not exist * if given {@link DistributionSet} does not exist
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if rollout or group parameters are invalid * if rollout or group parameters are invalid
* @throws QuotaExceededException
* if the maximum number of allowed targets per rollout group is
* exceeded.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull @Valid RolloutCreate rollout, @NotNull @Valid List<RolloutGroupCreate> groups, Rollout create(@NotNull @Valid RolloutCreate rollout, @NotNull @Valid List<RolloutGroupCreate> groups,

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleUpdate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule; import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
@@ -60,37 +61,51 @@ public interface SoftwareModuleManagement
long countByTextAndType(String searchText, Long typeId); long countByTextAndType(String searchText, Long typeId);
/** /**
* creates a list of software module meta data entries. * Creates a list of software module meta data entries.
* *
* @param metadata * @param metadata
* the meta data entries to create * the meta data entries to create
*
* @return the updated or created software module meta data entries * @return the updated or created software module meta data entries
*
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the * in case one of the meta data entry already exists for the
* specific key * specific key
*
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * if software module with given ID does not exist
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<SoftwareModuleMetadata> createMetaData(@NotNull @Valid Collection<SoftwareModuleMetadataCreate> metadata); List<SoftwareModuleMetadata> createMetaData(@NotNull @Valid Collection<SoftwareModuleMetadataCreate> metadata);
/** /**
* creates or updates a single software module meta data entry. * Creates or updates a single software module meta data entry.
* *
* @param metadata * @param metadata
* the meta data entry to create * the meta data entry to create
*
* @return the updated or created software module meta data entry * @return the updated or created software module meta data entry
*
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case the meta data entry already exists for the specific * in case the meta data entry already exists for the specific
* key * key
*
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * if software module with given ID does not exist
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
SoftwareModuleMetadata createMetaData(@NotNull @Valid SoftwareModuleMetadataCreate metadata); SoftwareModuleMetadata createMetaData(@NotNull @Valid SoftwareModuleMetadataCreate metadata);
/** /**
* deletes a software module meta data entry. * Deletes a software module meta data entry.
* *
* @param moduleId * @param moduleId
* where meta data has to be deleted * where meta data has to be deleted
@@ -104,12 +119,13 @@ public interface SoftwareModuleManagement
void deleteMetaData(long moduleId, @NotEmpty String key); void deleteMetaData(long moduleId, @NotEmpty String key);
/** /**
* returns all modules assigned to given {@link DistributionSet}. * Returns all modules assigned to given {@link DistributionSet}.
* *
* @param pageable * @param pageable
* the page request to page the result set * the page request to page the result set
* @param setId * @param setId
* to search for * to search for
*
* @return all {@link SoftwareModule}s that are assigned to given * @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}. * {@link DistributionSet}.
* *
@@ -130,6 +146,7 @@ public interface SoftwareModuleManagement
* to be filtered as "like" on {@link SoftwareModule#getName()} * to be filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId * @param typeId
* to be filtered as "like" on {@link SoftwareModule#getType()} * to be filtered as "like" on {@link SoftwareModule#getType()}
*
* @return the page of found {@link SoftwareModule} * @return the page of found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -139,7 +156,7 @@ public interface SoftwareModuleManagement
Slice<SoftwareModule> findByTextAndType(@NotNull Pageable pageable, String searchText, Long typeId); Slice<SoftwareModule> findByTextAndType(@NotNull Pageable pageable, String searchText, Long typeId);
/** /**
* retrieves {@link SoftwareModule} by their name AND version AND type.. * Retrieves {@link SoftwareModule} by their name AND version AND type..
* *
* @param name * @param name
* of the {@link SoftwareModule} * of the {@link SoftwareModule}
@@ -147,6 +164,7 @@ public interface SoftwareModuleManagement
* of the {@link SoftwareModule} * of the {@link SoftwareModule}
* @param typeId * @param typeId
* of the {@link SoftwareModule} * of the {@link SoftwareModule}
*
* @return the found {@link SoftwareModule} * @return the found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -156,7 +174,7 @@ public interface SoftwareModuleManagement
Optional<SoftwareModule> getByNameAndVersionAndType(@NotEmpty String name, @NotEmpty String version, long typeId); Optional<SoftwareModule> getByNameAndVersionAndType(@NotEmpty String name, @NotEmpty String version, long typeId);
/** /**
* finds a single software module meta data by its id. * Finds a single software module meta data by its id.
* *
* @param moduleId * @param moduleId
* where meta data has to be found * where meta data has to be found
@@ -171,7 +189,7 @@ public interface SoftwareModuleManagement
Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(long moduleId, @NotEmpty String key); Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(long moduleId, @NotEmpty String key);
/** /**
* finds all meta data by the given software module id. * Finds all meta data by the given software module id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
@@ -188,7 +206,7 @@ public interface SoftwareModuleManagement
Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long moduleId); Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long moduleId);
/** /**
* finds all meta data by the given software module id where * Finds all meta data by the given software module id where
* {@link SoftwareModuleMetadata#isTargetVisible()}. * {@link SoftwareModuleMetadata#isTargetVisible()}.
* *
* @param pageable * @param pageable
@@ -207,7 +225,7 @@ public interface SoftwareModuleManagement
long moduleId); long moduleId);
/** /**
* finds all meta data by the given software module id. * Finds all meta data by the given software module id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
@@ -222,8 +240,10 @@ public interface SoftwareModuleManagement
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
*
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if software module with given ID does not exist * if software module with given ID does not exist
*/ */
@@ -249,6 +269,7 @@ public interface SoftwareModuleManagement
* filtered as "like" on {@link SoftwareModule#getName()} * filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId * @param typeId
* filtered as "equal" on {@link SoftwareModule#getType()} * filtered as "equal" on {@link SoftwareModule#getType()}
*
* @return the page of found {@link SoftwareModule} * @return the page of found {@link SoftwareModule}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -259,13 +280,14 @@ public interface SoftwareModuleManagement
@NotNull Pageable pageable, long orderByDistributionId, String searchText, Long typeId); @NotNull Pageable pageable, long orderByDistributionId, String searchText, Long typeId);
/** /**
* retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType} * Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType}
* . * .
* *
* @param pageable * @param pageable
* page parameters * page parameters
* @param typeId * @param typeId
* to be filtered on * to be filtered on
*
* @return the found {@link SoftwareModule}s * @return the found {@link SoftwareModule}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -275,7 +297,7 @@ public interface SoftwareModuleManagement
Slice<SoftwareModule> findByType(@NotNull Pageable pageable, long typeId); Slice<SoftwareModule> findByType(@NotNull Pageable pageable, long typeId);
/** /**
* updates a distribution set meta data value if corresponding entry exists. * Updates a distribution set meta data value if corresponding entry exists.
* *
* @param update * @param update
* the meta data entry to be updated * the meta data entry to be updated

View File

@@ -18,8 +18,10 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -32,20 +34,26 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface TargetFilterQueryManagement { public interface TargetFilterQueryManagement {
/** /**
* creating new {@link TargetFilterQuery}. * Creates a new {@link TargetFilterQuery}.
* *
* @param create * @param create
* to create * to create
* @return the created {@link TargetFilterQuery} *
* @return the new {@link TargetFilterQuery}
*
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if fields are not filled as specified. Check * if fields are not filled as specified. Check
* {@link TargetFilterQueryCreate} for field constraints. * {@link TargetFilterQueryCreate} for field constraints.
*
* @throws QuotaExceededException
* if the maximum number of targets that is addressed by the
* given query is exceeded (auto-assignments only)
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
TargetFilterQuery create(@NotNull @Valid TargetFilterQueryCreate create); TargetFilterQuery create(@NotNull @Valid TargetFilterQueryCreate create);
/** /**
* Delete target filter query. * Deletes the {@link TargetFilterQuery} with the given ID.
* *
* @param targetFilterQueryId * @param targetFilterQueryId
* IDs of target filter query to be deleted * IDs of target filter query to be deleted
@@ -57,7 +65,7 @@ public interface TargetFilterQueryManagement {
void delete(long targetFilterQueryId); void delete(long targetFilterQueryId);
/** /**
* Verifies provided filter syntax. * Verifies the provided filter syntax.
* *
* @param query * @param query
* to verify * to verify
@@ -67,6 +75,7 @@ public interface TargetFilterQueryManagement {
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
*/ */
@@ -75,7 +84,7 @@ public interface TargetFilterQueryManagement {
/** /**
* *
* Retrieves all target filter query{@link TargetFilterQuery}. * Retrieves all {@link TargetFilterQuery}s.
* *
* @param pageable * @param pageable
* pagination parameter * pagination parameter
@@ -85,7 +94,7 @@ public interface TargetFilterQueryManagement {
Page<TargetFilterQuery> findAll(@NotNull Pageable pageable); Page<TargetFilterQuery> findAll(@NotNull Pageable pageable);
/** /**
* Counts all target filter queries * Counts all {@link TargetFilterQuery}s.
* *
* @return the number of all target filter queries * @return the number of all target filter queries
*/ */
@@ -93,46 +102,46 @@ public interface TargetFilterQueryManagement {
long count(); long count();
/** /**
* Retrieves all target filter query which {@link TargetFilterQuery}. * Retrieves all {@link TargetFilterQuery}s which match the given name
* * filter.
* *
* @param pageable * @param pageable
* pagination parameter * pagination parameter
* @param name * @param name
* name filter * name filter
* @return the page with the found {@link TargetFilterQuery} * @return the page with the found {@link TargetFilterQuery}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByName(@NotNull Pageable pageable, @NotNull String name); Page<TargetFilterQuery> findByName(@NotNull Pageable pageable, @NotNull String name);
/** /**
* Retrieves all target filter query which {@link TargetFilterQuery}. * Retrieves all {@link TargetFilterQuery} which match the given RSQL
* * filter.
* *
* @param pageable * @param pageable
* pagination parameter * pagination parameter
* @param rsqlFilter * @param rsqlFilter
* RSQL filter string * RSQL filter string
* @return the page with the found {@link TargetFilterQuery} * @return the page with the found {@link TargetFilterQuery}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByRsql(@NotNull Pageable pageable, @NotNull String rsqlFilter); Page<TargetFilterQuery> findByRsql(@NotNull Pageable pageable, @NotNull String rsqlFilter);
/** /**
* Retrieves all target filter query which have exactly the provided query. * Retrieves all {@link TargetFilterQuery}s which match the given query.
* *
* @param pageable * @param pageable
* pagination parameter * pagination parameter
* @param query * @param query
* the query saved in the target filter query * the query saved in the target filter query
* @return the page with the found {@link TargetFilterQuery} * @return the page with the found {@link TargetFilterQuery}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByQuery(@NotNull Pageable pageable, @NotNull String query); Page<TargetFilterQuery> findByQuery(@NotNull Pageable pageable, @NotNull String query);
/** /**
* Retrieves all target filter query which {@link TargetFilterQuery}. * Retrieves all {@link TargetFilterQuery}s which match the given
* * auto-assign distribution set and RSQL filter.
* *
* @param pageable * @param pageable
* pagination parameter * pagination parameter
@@ -140,28 +149,26 @@ public interface TargetFilterQueryManagement {
* the auto assign distribution set * the auto assign distribution set
* @param rsqlParam * @param rsqlParam
* RSQL filter * RSQL filter
* @return the page with the found {@link TargetFilterQuery} * @return the page with the found {@link TargetFilterQuery}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if DS with given ID does not exist * if DS with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByAutoAssignDSAndRsql(@NotNull Pageable pageable, long setId, Page<TargetFilterQuery> findByAutoAssignDSAndRsql(@NotNull Pageable pageable, long setId, String rsqlParam);
String rsqlParam);
/** /**
* Retrieves all target filter query with auto assign DS which * Retrieves all {@link TargetFilterQuery}s with an auto-assign distribution
* {@link TargetFilterQuery}. * set.
* *
* * @return the page with the found {@link TargetFilterQuery}s
* @return the page with the found {@link TargetFilterQuery}
* @param pageable * @param pageable
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findWithAutoAssignDS(@NotNull Pageable pageable); Page<TargetFilterQuery> findWithAutoAssignDS(@NotNull Pageable pageable);
/** /**
* Find target filter query by id. * Finds the {@link TargetFilterQuery} by id.
* *
* @param targetFilterQueryId * @param targetFilterQueryId
* Target filter query id * Target filter query id
@@ -172,7 +179,7 @@ public interface TargetFilterQueryManagement {
Optional<TargetFilterQuery> get(long targetFilterQueryId); Optional<TargetFilterQuery> get(long targetFilterQueryId);
/** /**
* Find target filter query by name. * Finds the {@link TargetFilterQuery} that matches the given name.
* *
* @param targetFilterQueryName * @param targetFilterQueryName
* Target filter query name * Target filter query name
@@ -183,7 +190,7 @@ public interface TargetFilterQueryManagement {
Optional<TargetFilterQuery> getByName(@NotNull String targetFilterQueryName); Optional<TargetFilterQuery> getByName(@NotNull String targetFilterQueryName);
/** /**
* updates the {@link TargetFilterQuery}. * Updates the {@link TargetFilterQuery}.
* *
* @param update * @param update
* to be updated * to be updated
@@ -193,25 +200,36 @@ public interface TargetFilterQueryManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are * if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found * provided but not found
*
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if fields are not filled as specified. Check * if fields are not filled as specified. Check
* {@link TargetFilterQueryUpdate} for field constraints. * {@link TargetFilterQueryUpdate} for field constraints.
*
* @throws QuotaExceededException
* if the update contains a new query which addresses too many
* targets (auto-assignments only)
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update); TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update);
/** /**
* updates the {@link TargetFilterQuery#getAutoAssignDistributionSet()}. * Updates the the auto-assign {@link DistributionSet} of the addressed
* {@link TargetFilterQuery}.
* *
* @param queryId * @param queryId
* to be updated * of the target filter query to be updated
* @param dsId * @param dsId
* to be updated or <code>null</code> in order to remove it * to be updated or <code>null</code> in order to remove it
*
* @return the updated {@link TargetFilterQuery} * @return the updated {@link TargetFilterQuery}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are * if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found * provided but not found
*
* @throws QuotaExceededException
* if the query that is already associated with this filter
* query addresses too many targets (auto-assignments only)
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery updateAutoAssignDS(long queryId, Long dsId); TargetFilterQuery updateAutoAssignDS(long queryId, Long dsId);

View File

@@ -17,8 +17,11 @@ import org.eclipse.hawkbit.repository.model.BaseEntity;
* *
*/ */
public final class QuotaExceededException extends AbstractServerRtException { public final class QuotaExceededException extends AbstractServerRtException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE = "Quota exceeded: Cannot assign %s more %s entities to %s '%s'. The maximum is %s.";
/** /**
* Creates a new QuotaExceededException with * Creates a new QuotaExceededException with
* {@link SpServerError#SP_QUOTA_EXCEEDED} error. * {@link SpServerError#SP_QUOTA_EXCEEDED} error.
@@ -28,11 +31,26 @@ public final class QuotaExceededException extends AbstractServerRtException {
} }
/** /**
* Creates a new QuotaExceededException with a custom error message.
*
* @param message
* The custom error message.
*/
public QuotaExceededException(final String message) {
super(message, SpServerError.SP_QUOTA_EXCEEDED);
}
/**
* Creates a QuotaExceededException with a custom error message and a root
* cause.
*
* @param message
* The custom error message.
* @param cause * @param cause
* for the exception * for the exception
*/ */
public QuotaExceededException(final Throwable cause) { public QuotaExceededException(final String message, final Throwable cause) {
super(SpServerError.SP_QUOTA_EXCEEDED, cause); super(message, SpServerError.SP_QUOTA_EXCEEDED, cause);
} }
/** /**
@@ -57,7 +75,55 @@ public final class QuotaExceededException extends AbstractServerRtException {
* that is defined by the repository * that is defined by the repository
*/ */
public QuotaExceededException(final String type, final long inserted, final int quota) { public QuotaExceededException(final String type, final long inserted, final int quota) {
super("Request contains too many entries of {" + type + "}. {" + inserted + "} is bejond the permitted {" super("Request contains too many entries of {" + type + "}. {" + inserted + "} is beyond the permitted {"
+ quota + "}.", SpServerError.SP_QUOTA_EXCEEDED); + quota + "}.", SpServerError.SP_QUOTA_EXCEEDED);
} }
/**
* Creates a QuotaExceededException which is to be thrown when an assignment
* quota is exceeded.
*
* @param type
* The type of the entities that shall be assigned to the
* specified parent entity.
* @param parentType
* The type of the parent entity.
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the specified
* parent entity.
* @param quota
* The maximum number of entities that can be assigned to the
* parent entity.
*/
public QuotaExceededException(final Class<?> type, final Class<?> parentType, final Long parentId,
final long requested, final long quota) {
this(type.getSimpleName(), parentType.getSimpleName(), parentId, requested, quota);
}
/**
* Creates a QuotaExceededException which is to be thrown when an assignment
* quota is exceeded.
*
* @param type
* The type of the entities that shall be assigned to the
* specified parent entity.
* @param parentType
* The type of the parent entity.
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the specified
* parent entity.
* @param quota
* The maximum number of entities that can be assigned to the
* parent entity.
*/
public QuotaExceededException(final String type, final String parentType, final Long parentId, final long requested,
final long quota) {
super(String.format(ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE, requested, type, parentType,
parentId != null ? String.valueOf(parentId) : "<new>", quota), SpServerError.SP_QUOTA_EXCEEDED);
}
} }

View File

@@ -21,12 +21,12 @@ public class RolloutGroupsValidation {
/** /**
* The total amount of targets in a {@link Rollout} * The total amount of targets in a {@link Rollout}
*/ */
private long totalTargets; private final long totalTargets;
/** /**
* A list containing the count of targets for each {@link RolloutGroup} * A list containing the count of targets for each {@link RolloutGroup}
*/ */
private List<Long> targetsPerGroup; private final List<Long> targetsPerGroup;
/** /**
* Instantiates a new validation result * Instantiates a new validation result

View File

@@ -28,8 +28,8 @@ public final class DurationHelper {
* *
*/ */
public static final class DurationRangeValidator { public static final class DurationRangeValidator {
final Duration min; private final Duration min;
final Duration max; private final Duration max;
private DurationRangeValidator(final Duration min, final Duration max) { private DurationRangeValidator(final Duration min, final Duration max) {
this.min = min; this.min = min;

View File

@@ -48,4 +48,54 @@ public class PropertiesQuotaManagement implements QuotaManagement {
return securityProperties.getDos().getMaxMessagesPerActionStatus(); return securityProperties.getDos().getMaxMessagesPerActionStatus();
} }
@Override
public int getMaxMetaDataEntriesPerSoftwareModule() {
return securityProperties.getDos().getMaxMetaDataEntriesPerSoftwareModule();
}
@Override
public int getMaxMetaDataEntriesPerDistributionSet() {
return securityProperties.getDos().getMaxMetaDataEntriesPerDistributionSet();
}
@Override
public int getMaxSoftwareModulesPerDistributionSet() {
return securityProperties.getDos().getMaxSoftwareModulesPerDistributionSet();
}
@Override
public int getMaxSoftwareModuleTypesPerDistributionSetType() {
return securityProperties.getDos().getMaxSoftwareModuleTypesPerDistributionSetType();
}
@Override
public int getMaxArtifactsPerSoftwareModule() {
return securityProperties.getDos().getMaxArtifactsPerSoftwareModule();
}
@Override
public int getMaxTargetsPerRolloutGroup() {
return securityProperties.getDos().getMaxTargetsPerRolloutGroup();
}
@Override
public int getMaxActionsPerTarget() {
return securityProperties.getDos().getMaxActionsPerTarget();
}
@Override
public int getMaxTargetsPerManualAssignment() {
return securityProperties.getDos().getMaxTargetsPerManualAssignment();
}
@Override
public int getMaxTargetsPerAutoAssignment() {
return securityProperties.getDos().getMaxTargetsPerAutoAssignment();
}
@Override
public long getMaxArtifactSize() {
return securityProperties.getDos().getMaxArtifactSize();
}
} }

View File

@@ -14,6 +14,7 @@ import java.util.stream.Collectors;
import javax.validation.ValidationException; import javax.validation.ValidationException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -75,9 +76,11 @@ public final class RolloutHelper {
*/ */
public static void verifyRolloutGroupParameter(final int amountGroup, final QuotaManagement quotaManagement) { public static void verifyRolloutGroupParameter(final int amountGroup, final QuotaManagement quotaManagement) {
if (amountGroup <= 0) { if (amountGroup <= 0) {
throw new ValidationException("the amount of groups cannot be lower than zero"); throw new ValidationException("The amount of groups cannot be lower than zero");
} else if (amountGroup > quotaManagement.getMaxRolloutGroupsPerRollout()) { } else if (amountGroup > quotaManagement.getMaxRolloutGroupsPerRollout()) {
throw new ValidationException("the amount of groups cannot be greater than 500"); throw new QuotaExceededException(
"The amount of groups cannot be greater than " + quotaManagement.getMaxRolloutGroupsPerRollout());
} }
} }
@@ -89,9 +92,9 @@ public final class RolloutHelper {
*/ */
public static void verifyRolloutGroupTargetPercentage(final float percentage) { public static void verifyRolloutGroupTargetPercentage(final float percentage) {
if (percentage <= 0) { if (percentage <= 0) {
throw new ValidationException("the percentage must be greater than zero"); throw new ValidationException("The percentage must be greater than zero");
} else if (percentage > 100) { } else if (percentage > 100) {
throw new ValidationException("the percentage must not be greater than 100"); throw new ValidationException("The percentage must not be greater than 100");
} }
} }

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -46,17 +48,19 @@ public abstract class AbstractDsAssignmentStrategy {
protected final ApplicationContext applicationContext; protected final ApplicationContext applicationContext;
private final ActionRepository actionRepository; private final ActionRepository actionRepository;
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
private final QuotaManagement quotaManagement;
AbstractDsAssignmentStrategy(final TargetRepository targetRepository, AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher, final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository, final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) { final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.afterCommit = afterCommit; this.afterCommit = afterCommit;
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository; this.actionStatusRepository = actionStatusRepository;
this.quotaManagement = quotaManagement;
} }
/** /**
@@ -213,6 +217,11 @@ public abstract class AbstractDsAssignmentStrategy {
JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final JpaTarget target, JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final JpaTarget target,
final JpaDistributionSet set) { final JpaDistributionSet set) {
// enforce the 'max actions per target' quota
assertActionsPerTargetQuota(target, 1);
// create the action
final JpaAction actionForTarget = new JpaAction(); final JpaAction actionForTarget = new JpaAction();
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId()); final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
actionForTarget.setActionType(targetWithActionType.getActionType()); actionForTarget.setActionType(targetWithActionType.getActionType());
@@ -237,4 +246,11 @@ public abstract class AbstractDsAssignmentStrategy {
return actionStatus; return actionStatus;
} }
private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId);
}
} }

View File

@@ -39,6 +39,16 @@ public interface ActionStatusRepository
*/ */
Long countByAction(JpaAction action); Long countByAction(JpaAction action);
/**
* Counts {@link ActionStatus} entries of given {@link Action} in
* repository.
*
* @param actionId
* of the action to count status entries for
* @return number of actions in repository
*/
long countByActionId(Long actionId);
/** /**
* Retrieves all {@link ActionStatus} entries from repository of given * Retrieves all {@link ActionStatus} entries from repository of given
* ActionId. * ActionId.

View File

@@ -13,6 +13,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
@@ -23,4 +24,14 @@ public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>, extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<JpaDistributionSetMetadata> { JpaSpecificationExecutor<JpaDistributionSetMetadata> {
/**
* Counts the meta data entries that match the given distribution set ID.
*
* @param id
* of the distribution set.
*
* @return The number of matching meta data entries.
*/
long countByDistributionSetId(@Param("id") Long id);
} }

View File

@@ -15,6 +15,7 @@ import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -40,6 +41,7 @@ public interface DistributionSetTypeRepository
* @param isDeleted * @param isDeleted
* to <code>true</code> if only soft deleted entries of * to <code>true</code> if only soft deleted entries of
* <code>false</code> if undeleted ones * <code>false</code> if undeleted ones
*
* @return list of found {@link DistributionSetType}s * @return list of found {@link DistributionSetType}s
*/ */
Page<JpaDistributionSetType> findByDeleted(Pageable pageable, boolean isDeleted); Page<JpaDistributionSetType> findByDeleted(Pageable pageable, boolean isDeleted);
@@ -47,7 +49,8 @@ public interface DistributionSetTypeRepository
/** /**
* @param isDeleted * @param isDeleted
* to <code>true</code> if only marked as deleted have to be * to <code>true</code> if only marked as deleted have to be
* count or all undeleted. * counted or all undeleted.
*
* @return number of {@link DistributionSetType}s in the repository. * @return number of {@link DistributionSetType}s in the repository.
*/ */
long countByDeleted(boolean isDeleted); long countByDeleted(boolean isDeleted);
@@ -59,6 +62,7 @@ public interface DistributionSetTypeRepository
* @param softwareModuleType * @param softwareModuleType
* the software module type to count the distribution set type * the software module type to count the distribution set type
* which has this software module type assigned * which has this software module type assigned
*
* @return the number of {@link DistributionSetType}s in the repository * @return the number of {@link DistributionSetType}s in the repository
* assigned to the given software module type * assigned to the given software module type
*/ */
@@ -78,8 +82,29 @@ public interface DistributionSetTypeRepository
@Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant") @Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant); void deleteByTenant(@Param("tenant") String tenant);
/**
* Retrieves the {@link DistributionSetType}s for the given IDs. Workaround
* for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
*
* @param ids
* of the types to be located
*
* @return a list of distribution set types
*/
@Override @Override
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT d FROM JpaDistributionSetType d WHERE d.id IN ?1") @Query("SELECT d FROM JpaDistributionSetType d WHERE d.id IN ?1")
List<JpaDistributionSetType> findAll(Iterable<Long> ids); List<JpaDistributionSetType> findAll(Iterable<Long> ids);
/**
* Counts the {@link SoftwareModuleType}s which are associated with the
* addressed {@link DistributionSetType}.
*
* @param id
* of the distribution set type
*
* @return the number of associated software module types
*/
@Query("SELECT COUNT (e.smType) FROM DistributionSetTypeElement e WHERE e.dsType.id = :id")
long countSmTypesById(@Param("id") Long id);
} }

View File

@@ -17,15 +17,18 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException; import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
@@ -49,6 +52,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class); private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class);
private static final String MAX_ARTIFACT_SIZE_EXCEEDED = "Quota exceeded: The artifact '%s' (%s bytes) which has been uploaded for software module '%s' exceeds the maximum artifact size of %s bytes.";
private final LocalArtifactRepository localArtifactRepository; private final LocalArtifactRepository localArtifactRepository;
private final SoftwareModuleRepository softwareModuleRepository; private final SoftwareModuleRepository softwareModuleRepository;
@@ -57,12 +62,15 @@ public class JpaArtifactManagement implements ArtifactManagement {
private final TenantAware tenantAware; private final TenantAware tenantAware;
private final QuotaManagement quotaManagement;
JpaArtifactManagement(final LocalArtifactRepository localArtifactRepository, JpaArtifactManagement(final LocalArtifactRepository localArtifactRepository,
final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository, final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) { final QuotaManagement quotaManagement, final TenantAware tenantAware) {
this.localArtifactRepository = localArtifactRepository; this.localArtifactRepository = localArtifactRepository;
this.softwareModuleRepository = softwareModuleRepository; this.softwareModuleRepository = softwareModuleRepository;
this.artifactRepository = artifactRepository; this.artifactRepository = artifactRepository;
this.quotaManagement = quotaManagement;
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
} }
@@ -87,13 +95,16 @@ public class JpaArtifactManagement implements ArtifactManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Artifact create(final InputStream stream, final long moduleId, final String filename, public Artifact create(final InputStream stream, final long moduleId, final String filename,
final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting, final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting,
final String contentType) { final String contentType, final long filesize) {
AbstractDbArtifact result = null; AbstractDbArtifact result = null;
final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId); final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId);
final Artifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule); final Artifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
assertArtifactQuota(moduleId, 1);
assertMaxArtifactSizeQuota(filename, moduleId, filesize);
try { try {
result = artifactRepository.store(tenantAware.getCurrentTenant(), stream, filename, contentType, result = artifactRepository.store(tenantAware.getCurrentTenant(), stream, filename, contentType,
new DbArtifactHash(providedSha1Sum, providedMd5Sum)); new DbArtifactHash(providedSha1Sum, providedMd5Sum));
@@ -113,6 +124,23 @@ public class JpaArtifactManagement implements ArtifactManagement {
return storeArtifactMetadata(softwareModule, filename, result, existing); return storeArtifactMetadata(softwareModule, filename, result, existing);
} }
private void assertArtifactQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(),
Artifact.class, SoftwareModule.class, localArtifactRepository::countBySoftwareModuleId);
}
private void assertMaxArtifactSizeQuota(final String filename, final long id, final long artifactSize) {
final long maxArtifactSize = quotaManagement.getMaxArtifactSize();
if (maxArtifactSize <= 0) {
return;
}
if (artifactSize > maxArtifactSize) {
final String msg = String.format(MAX_ARTIFACT_SIZE_EXCEEDED, filename, artifactSize, id, maxArtifactSize);
LOG.warn(msg);
throw new QuotaExceededException(msg);
}
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -207,8 +235,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Artifact create(final InputStream inputStream, final long moduleId, final String filename, public Artifact create(final InputStream inputStream, final long moduleId, final String filename,
final boolean overrideExisting) { final boolean overrideExisting, final long filesize) {
return create(inputStream, moduleId, filename, null, null, overrideExisting, null); return create(inputStream, moduleId, filename, null, null, overrideExisting, null, filesize);
} }
@Override @Override

View File

@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -56,6 +55,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -535,8 +535,8 @@ public class JpaControllerManagement implements ControllerManagement {
break; break;
default: default:
// information status entry - check for a potential DOS attack // information status entry - check for a potential DOS attack
checkForTooManyStatusEntries(action); assertActionStatusQuota(action);
checkForTooManyStatusMessages(actionStatus); assertActionStatusMessageQuota(actionStatus);
break; break;
} }
@@ -546,12 +546,9 @@ public class JpaControllerManagement implements ControllerManagement {
return action; return action;
} }
private void checkForTooManyStatusMessages(final JpaActionStatus actionStatus) { private void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
if (actionStatus.getMessages().size() > quotaManagement.getMaxMessagesPerActionStatus()) { QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
throw new QuotaExceededException("ActionStatus messages", actionStatus.getMessages().size(), quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
quotaManagement.getMaxStatusEntriesPerAction());
}
} }
private void handleFinishedCancelation(final JpaActionStatus actionStatus, final JpaAction action) { private void handleFinishedCancelation(final JpaActionStatus actionStatus, final JpaAction action) {
@@ -607,8 +604,8 @@ public class JpaControllerManagement implements ControllerManagement {
break; break;
default: default:
// information status entry - check for a potential DOS attack // information status entry - check for a potential DOS attack
checkForTooManyStatusEntries(action); assertActionStatusQuota(action);
checkForTooManyStatusMessages(actionStatus); assertActionStatusMessageQuota(actionStatus);
break; break;
} }
@@ -628,16 +625,9 @@ public class JpaControllerManagement implements ControllerManagement {
targetRepository.save(mergedTarget); targetRepository.save(mergedTarget);
} }
private void checkForTooManyStatusEntries(final JpaAction action) { private void assertActionStatusQuota(final JpaAction action) {
if (quotaManagement.getMaxStatusEntriesPerAction() > 0) { QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
final Long statusCount = actionStatusRepository.countByAction(action);
if (statusCount >= quotaManagement.getMaxStatusEntriesPerAction()) {
throw new QuotaExceededException(ActionStatus.class, statusCount + 1,
quotaManagement.getMaxStatusEntriesPerAction());
}
}
} }
private void handleFinishedAndStoreInTargetStatus(final JpaAction action) { private void handleFinishedAndStoreInTargetStatus(final JpaAction action) {
@@ -694,16 +684,17 @@ public class JpaControllerManagement implements ControllerManagement {
// unknown update mode // unknown update mode
throw new IllegalStateException("The update mode " + updateMode + " is not supported."); throw new IllegalStateException("The update mode " + updateMode + " is not supported.");
} }
assertTargetAttributesQuota(target);
final int attributeCount = controllerAttributes.size();
if (attributeCount > quotaManagement.getMaxAttributeEntriesPerTarget()) {
throw new QuotaExceededException("Controller attributes", attributeCount,
quotaManagement.getMaxAttributeEntriesPerTarget());
}
return targetRepository.save(target); return targetRepository.save(target);
} }
private void assertTargetAttributesQuota(final JpaTarget target) {
final int limit = quotaManagement.getMaxAttributeEntriesPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), target.getControllerAttributes().size(), limit, "Attribute",
Target.class.getSimpleName(), null);
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -775,8 +766,8 @@ public class JpaControllerManagement implements ControllerManagement {
final JpaActionStatus statusMessage = create.build(); final JpaActionStatus statusMessage = create.build();
statusMessage.setAction(action); statusMessage.setAction(action);
checkForTooManyStatusEntries(action); assertActionStatusQuota(action);
checkForTooManyStatusMessages(statusMessage); assertActionStatusMessageQuota(statusMessage);
return actionStatusRepository.save(statusMessage); return actionStatusRepository.save(statusMessage);
} }

View File

@@ -28,6 +28,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
@@ -46,6 +47,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -115,6 +117,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy; private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy; private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy;
private final TenantConfigurationManagement tenantConfigurationManagement; private final TenantConfigurationManagement tenantConfigurationManagement;
private final QuotaManagement quotaManagement;
private final SystemSecurityContext systemSecurityContext; private final SystemSecurityContext systemSecurityContext;
private final Database database; private final Database database;
@@ -124,7 +127,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AuditorAware<String> auditorProvider, final ApplicationEventPublisher eventPublisher, final AuditorAware<String> auditorProvider, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final AfterTransactionCommitExecutor afterCommit, final ApplicationContext applicationContext, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final Database database) { final SystemSecurityContext systemSecurityContext, final Database database) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
@@ -139,10 +142,11 @@ public class JpaDeploymentManagement implements DeploymentManagement {
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.txManager = txManager; this.txManager = txManager;
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
applicationContext, actionRepository, actionStatusRepository); applicationContext, actionRepository, actionStatusRepository, quotaManagement);
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
applicationContext, actionRepository, actionStatusRepository); applicationContext, actionRepository, actionStatusRepository, quotaManagement);
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
this.quotaManagement = quotaManagement;
this.systemSecurityContext = systemSecurityContext; this.systemSecurityContext = systemSecurityContext;
this.database = database; this.database = database;
} }
@@ -215,8 +219,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
* a list of all targets and their action type * a list of all targets and their action type
* @param actionMessage * @param actionMessage
* an optional message to be written into the action status * an optional message to be written into the action status
* @param offline * @param assignmentStrategy
* to <code>true</code> in offline case * the assignment strategy (online /offline)
* @return the assignment result * @return the assignment result
* *
* @throw IncompleteDistributionSetException if mandatory * @throw IncompleteDistributionSetException if mandatory
@@ -240,6 +244,11 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getControllerId) final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.collect(Collectors.toList()); .collect(Collectors.toList());
// enforce the 'max targets per manual assignment' quota
if (!controllerIDs.isEmpty()) {
assertMaxTargetsPerManualAssignmentQuota(set.getId(), controllerIDs.size());
}
LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size()); LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size());
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream() final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
@@ -318,6 +327,20 @@ public class JpaDeploymentManagement implements DeploymentManagement {
targetManagement); targetManagement);
} }
/**
* Enforces the quota defining the maximum number of {@link Target}s per
* manual {@link DistributionSet} assignment.
*
* @param id
* of the distribution set
* @param requested
* number of targets to check
*/
private void assertMaxTargetsPerManualAssignmentQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxTargetsPerManualAssignment(),
Target.class, DistributionSet.class, null);
}
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = { @Retryable(include = {

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.builder.DistributionSetCreate; import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate; import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
@@ -43,6 +44,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
@@ -91,6 +93,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private final DistributionSetTypeManagement distributionSetTypeManagement; private final DistributionSetTypeManagement distributionSetTypeManagement;
private final QuotaManagement quotaManagement;
private final DistributionSetMetadataRepository distributionSetMetadataRepository; private final DistributionSetMetadataRepository distributionSetMetadataRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetFilterQueryRepository targetFilterQueryRepository;
@@ -118,7 +122,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
JpaDistributionSetManagement(final EntityManager entityManager, JpaDistributionSetManagement(final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository, final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher, final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher,
@@ -132,6 +136,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
this.distributionSetTagManagement = distributionSetTagManagement; this.distributionSetTagManagement = distributionSetTagManagement;
this.systemManagement = systemManagement; this.systemManagement = systemManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement; this.distributionSetTypeManagement = distributionSetTypeManagement;
this.quotaManagement = quotaManagement;
this.distributionSetMetadataRepository = distributionSetMetadataRepository; this.distributionSetMetadataRepository = distributionSetMetadataRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
@@ -317,6 +322,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
assertDistributionSetIsNotAssignedToTargets(setId); assertDistributionSetIsNotAssignedToTargets(setId);
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId); final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId);
assertSoftwareModuleQuota(setId, modules.size());
modules.forEach(set::addModule); modules.forEach(set::addModule);
return distributionSetRepository.save(set); return distributionSetRepository.save(set);
@@ -452,6 +460,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
md.forEach(meta -> checkAndThrowAlreadyIfDistributionSetMetadataExists( md.forEach(meta -> checkAndThrowAlreadyIfDistributionSetMetadataExists(
new DsMetadataCompositeKey(dsId, meta.getKey()))); new DsMetadataCompositeKey(dsId, meta.getKey())));
assertMetaDataQuota(dsId, md.size());
final JpaDistributionSet set = touch(dsId); final JpaDistributionSet set = touch(dsId);
return Collections.unmodifiableList(md.stream() return Collections.unmodifiableList(md.stream()
@@ -460,6 +470,18 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private void assertMetaDataQuota(final Long dsId, final int requested) {
QuotaHelper.assertAssignmentQuota(dsId, requested, quotaManagement.getMaxMetaDataEntriesPerDistributionSet(),
DistributionSetMetadata.class, DistributionSet.class,
distributionSetMetadataRepository::countByDistributionSetId);
}
private void assertSoftwareModuleQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
SoftwareModule.class, DistributionSet.class,
softwareModuleRepository::countByAssignedToId);
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -500,9 +522,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private JpaDistributionSet touch(final DistributionSet ds) { private JpaDistributionSet touch(final DistributionSet ds) {
// merge base distribution set so optLockRevision gets updated and audit // merge base distribution set so optLockRevision gets updated and audit
// log written because // log written because modifying metadata is modifying the base
// modifying metadata is modifying the base distribution set itself for // distribution set itself for auditing purposes.
// auditing purposes.
final JpaDistributionSet result = entityManager.merge((JpaDistributionSet) ds); final JpaDistributionSet result = entityManager.merge((JpaDistributionSet) ds);
result.setLastModifiedAt(0L); result.setLastModifiedAt(0L);

View File

@@ -17,11 +17,13 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields; import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate; import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate; import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.builder.GenericDistributionSetTypeUpdate; import org.eclipse.hawkbit.repository.builder.GenericDistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
@@ -29,6 +31,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -64,17 +67,20 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
private final NoCountPagingRepository criteriaNoCountDao; private final NoCountPagingRepository criteriaNoCountDao;
private final Database database; private final Database database;
private final QuotaManagement quotaManagement;
JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository, JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao, final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao,
final Database database) { final Database database, final QuotaManagement quotaManagement) {
this.distributionSetTypeRepository = distributionSetTypeRepository; this.distributionSetTypeRepository = distributionSetTypeRepository;
this.softwareModuleTypeRepository = softwareModuleTypeRepository; this.softwareModuleTypeRepository = softwareModuleTypeRepository;
this.distributionSetRepository = distributionSetRepository; this.distributionSetRepository = distributionSetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.criteriaNoCountDao = criteriaNoCountDao; this.criteriaNoCountDao = criteriaNoCountDao;
this.database = database; this.database = database;
this.quotaManagement = quotaManagement;
} }
@Override @Override
@@ -116,6 +122,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId); final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId);
checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId); checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId);
assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size());
modules.forEach(type::addMandatoryModuleType); modules.forEach(type::addMandatoryModuleType);
@@ -138,11 +145,31 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId); final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId);
checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId); checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId);
assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size());
modules.forEach(type::addOptionalModuleType); modules.forEach(type::addOptionalModuleType);
return distributionSetTypeRepository.save(type); return distributionSetTypeRepository.save(type);
} }
/**
* Enforces the quota specifiying the maximum number of
* {@link SoftwareModuleType}s per {@link DistributionSetType}.
*
* @param id
* of the distribution set type
* @param requested
* number of software module types to check
*
* @throws QuotaExceededException
* if the software module type quota is exceeded
*/
private void assertSoftwareModuleTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested,
quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType(), SoftwareModuleType.class,
DistributionSetType.class, distributionSetTypeRepository::countSmTypesById);
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {

View File

@@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditio
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -226,11 +227,15 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
} }
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions, private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final Rollout rollout) { final JpaRollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
RolloutHelper.verifyRolloutGroupConditions(conditions); RolloutHelper.verifyRolloutGroupConditions(conditions);
final JpaRollout savedRollout = (JpaRollout) rollout; final JpaRollout savedRollout = rollout;
// we can enforce the 'max targets per group' quota right here because
// we want to distribute the targets equally to the different groups
assertTargetsPerRolloutGroupQuota(rollout.getTotalTargets() / amountOfGroups);
RolloutGroup lastSavedGroup = null; RolloutGroup lastSavedGroup = null;
for (int i = 0; i < amountOfGroups; i++) { for (int i = 0; i < amountOfGroups; i++) {
@@ -269,7 +274,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout savedRollout = (JpaRollout) rollout; final JpaRollout savedRollout = (JpaRollout) rollout;
// Preparing the groups // prepare the groups
final List<RolloutGroup> groups = groupList.stream() final List<RolloutGroup> groups = groupList.stream()
.map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions)) .map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -278,7 +283,13 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
RolloutHelper.verifyRemainingTargets( RolloutHelper.verifyRemainingTargets(
calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())); calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()));
// Persisting the groups // check if we need to enforce the 'max targets per group' quota
if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) {
validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())
.getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota);
}
// create and persist the groups (w/o filling them with targets)
RolloutGroup lastSavedGroup = null; RolloutGroup lastSavedGroup = null;
for (final RolloutGroup srcGroup : groups) { for (final RolloutGroup srcGroup : groups) {
final JpaRolloutGroup group = new JpaRolloutGroup(); final JpaRolloutGroup group = new JpaRolloutGroup();
@@ -346,7 +357,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
// When all groups are ready the rollout status can be changed to be // When all groups are ready the rollout status can be changed to be
// ready, too. // ready, too.
if (readyGroups == rolloutGroups.size()) { if (readyGroups == rolloutGroups.size()) {
LOGGER.debug("rollout {} creatin done. Switch to READY.", rollout.getId()); LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
rollout.setStatus(RolloutStatus.READY); rollout.setStatus(RolloutStatus.READY);
rollout.setLastCheck(0); rollout.setLastCheck(0);
rollout.setTotalTargets(totalTargets); rollout.setTotalTargets(totalTargets);
@@ -383,9 +394,10 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rolloutGroupRepository.save(group); return rolloutGroupRepository.save(group);
} }
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
try { try {
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
do { do {
// Add up to TRANSACTION_TARGETS of the left targets // Add up to TRANSACTION_TARGETS of the left targets
// In case a TransactionException is thrown this loop aborts // In case a TransactionException is thrown this loop aborts
@@ -558,6 +570,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED); actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
targets.forEach(target -> { targets.forEach(target -> {
assertActionsPerTargetQuota(target, 1);
final JpaAction action = new JpaAction(); final JpaAction action = new JpaAction();
action.setTarget(target); action.setTarget(target);
action.setActive(false); action.setActive(false);
@@ -1006,6 +1021,11 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rollout; return rollout;
} }
@Override
public boolean exists(final long rolloutId) {
return rolloutRepository.exists(rolloutId);
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rollouts) { private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rollouts) {
if (rollouts.isEmpty()) { if (rollouts.isEmpty()) {
return null; return null;
@@ -1044,8 +1064,33 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
} }
} }
@Override /**
public boolean exists(final long rolloutId) { * Enforces the quota defining the maximum number of {@link Target}s per
return rolloutRepository.exists(rolloutId); * {@link RolloutGroup}.
*
* @param group
* The rollout group
* @param requested
* number of targets to check
*/
private void assertTargetsPerRolloutGroupQuota(final long requested) {
final int quota = quotaManagement.getMaxTargetsPerRolloutGroup();
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
} }
/**
* Enforces the quota defining the maximum number of {@link Action}s per
* {@link Target}.
*
* @param target
* The target
* @param requested
* number of actions to check
*/
private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId);
}
} }

View File

@@ -14,6 +14,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -28,6 +29,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
@@ -53,6 +55,7 @@ import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule; import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -103,7 +106,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
private final ArtifactManagement artifactManagement; private final ArtifactManagement artifactManagement;
private final QuotaManagement quotaManagement;
private final VirtualPropertyReplacer virtualPropertyReplacer; private final VirtualPropertyReplacer virtualPropertyReplacer;
private final Database database; private final Database database;
JpaSoftwareModuleManagement(final EntityManager entityManager, JpaSoftwareModuleManagement(final EntityManager entityManager,
@@ -112,8 +118,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
final SoftwareModuleMetadataRepository softwareModuleMetadataRepository, final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider, final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider,
final ArtifactManagement artifactManagement, final VirtualPropertyReplacer virtualPropertyReplacer, final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement,
final Database database) { final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository; this.distributionSetRepository = distributionSetRepository;
this.softwareModuleRepository = softwareModuleRepository; this.softwareModuleRepository = softwareModuleRepository;
@@ -122,6 +128,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
this.criteriaNoCountDao = criteriaNoCountDao; this.criteriaNoCountDao = criteriaNoCountDao;
this.auditorProvider = auditorProvider; this.auditorProvider = auditorProvider;
this.artifactManagement = artifactManagement; this.artifactManagement = artifactManagement;
this.quotaManagement = quotaManagement;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.database = database; this.database = database;
} }
@@ -461,19 +468,13 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) { public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c; final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long moduleId = create.getSoftwareModuleId();
assertSoftwareModuleExists(moduleId);
assertMetaDataQuota(moduleId, 1);
checkAndThrowAlreadyIfSoftwareModuleMetadataExists(create.getSoftwareModuleId(), create); return saveMetadata(create);
touch(create.getSoftwareModuleId());
return softwareModuleMetadataRepository.save(create.build());
}
private void checkAndThrowAlreadyIfSoftwareModuleMetadataExists(final Long moduleId,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.exists(new SwMetadataCompositeKey(moduleId, md.getKey()))) {
throwMetadataKeyAlreadyExists(md.getKey());
}
} }
@Override @Override
@@ -482,7 +483,71 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModuleMetadata> createMetaData(final Collection<SoftwareModuleMetadataCreate> create) { public List<SoftwareModuleMetadata> createMetaData(final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(this::createMetaData).collect(Collectors.toList()); if (!create.isEmpty()) {
// check if all meta data entries refer to the same software module
final Long moduleId = ((JpaSoftwareModuleMetadataCreate) create.iterator().next()).getSoftwareModuleId();
if (createJpaMetadataCreateStream(create).allMatch(c -> moduleId.equals(c.getSoftwareModuleId()))) {
assertSoftwareModuleExists(moduleId);
assertMetaDataQuota(moduleId, create.size());
return createJpaMetadataCreateStream(create).map(this::saveMetadata).collect(Collectors.toList());
} else {
// group by software module id to minimize database access
final Map<Long, List<JpaSoftwareModuleMetadataCreate>> groups = createJpaMetadataCreateStream(create)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId));
return groups.entrySet().stream().flatMap(e -> {
final Long id = e.getKey();
final List<JpaSoftwareModuleMetadataCreate> group = e.getValue();
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, group.size());
return group.stream().map(this::saveMetadata);
}).collect(Collectors.toList());
}
}
return Collections.emptyList();
}
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(c -> (JpaSoftwareModuleMetadataCreate) c);
}
private SoftwareModuleMetadata saveMetadata(final JpaSoftwareModuleMetadataCreate create) {
assertSoftwareModuleMetadataDoesNotExist(create.getSoftwareModuleId(), create);
return softwareModuleMetadataRepository.save(create.build());
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long moduleId,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.exists(new SwMetadataCompositeKey(moduleId, md.getKey()))) {
throwMetadataKeyAlreadyExists(md.getKey());
}
}
private void assertSoftwareModuleExists(final Long moduleId) {
touch(moduleId);
}
/**
* Asserts the meta data quota for the software module with the given ID.
*
* @param moduleId
* The software module ID.
* @param requested
* Number of meta data entries to be created.
*/
private void assertMetaDataQuota(final Long moduleId, final int requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(moduleId, requested, maxMetaData, SoftwareModuleMetadata.class,
SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId);
} }
@Override @Override
@@ -514,9 +579,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
*/ */
private JpaSoftwareModule touch(final SoftwareModule latestModule) { private JpaSoftwareModule touch(final SoftwareModule latestModule) {
// merge base distribution set so optLockRevision gets updated and audit // merge base distribution set so optLockRevision gets updated and audit
// log written because // log written because modifying metadata is modifying the base
// modifying metadata is modifying the base distribution set itself for // distribution set itself for auditing purposes.
// auditing purposes.
final JpaSoftwareModule result = entityManager.merge((JpaSoftwareModule) latestModule); final JpaSoftwareModule result = entityManager.merge((JpaSoftwareModule) latestModule);
result.setLastModifiedAt(0L); result.setLastModifiedAt(0L);

View File

@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
@@ -28,7 +29,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
@@ -56,18 +59,24 @@ import com.google.common.collect.Lists;
public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement { public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement {
private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetRepository targetRepository;
private final VirtualPropertyReplacer virtualPropertyReplacer; private final VirtualPropertyReplacer virtualPropertyReplacer;
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement;
private final Database database; private final Database database;
JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository, JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final Database database) { final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final Database database) {
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetRepository = targetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.database = database; this.database = database;
} }
@@ -78,6 +87,10 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
public TargetFilterQuery create(final TargetFilterQueryCreate c) { public TargetFilterQuery create(final TargetFilterQueryCreate c) {
final JpaTargetFilterQueryCreate create = (JpaTargetFilterQueryCreate) c; final JpaTargetFilterQueryCreate create = (JpaTargetFilterQueryCreate) c;
// enforce the 'max targets per auto assign' quota right here even if
// the result of the filter query can vary over time
create.getSet().flatMap(set -> create.getQuery()).ifPresent(this::assertMaxTargetsQuota);
return targetFilterQueryRepository.save(create.build()); return targetFilterQueryRepository.save(create.build());
} }
@@ -187,7 +200,18 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(update.getId()); final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(update.getId());
update.getName().ifPresent(targetFilterQuery::setName); update.getName().ifPresent(targetFilterQuery::setName);
update.getQuery().ifPresent(targetFilterQuery::setQuery); update.getQuery().ifPresent(query -> {
// enforce the 'max targets per auto assignment'-quota only if the
// query is going to change
if (targetFilterQuery.getAutoAssignDistributionSet() != null
&& !query.equals(targetFilterQuery.getQuery())) {
assertMaxTargetsQuota(query);
}
// set the new query
targetFilterQuery.setQuery(query);
});
return targetFilterQueryRepository.save(targetFilterQuery); return targetFilterQueryRepository.save(targetFilterQuery);
} }
@@ -200,6 +224,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetFilterQuery.setAutoAssignDistributionSet( targetFilterQuery.setAutoAssignDistributionSet(
Optional.ofNullable(dsId).map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null)); Optional.ofNullable(dsId).map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null));
// we cannot be sure that the quota was enforced at creation time
// because the Target Filter Query REST API does not allow to specify an
// auto-assign distribution set when creating a target filter query
if (dsId != null) {
assertMaxTargetsQuota(targetFilterQuery.getQuery());
}
return targetFilterQueryRepository.save(targetFilterQuery); return targetFilterQueryRepository.save(targetFilterQuery);
} }
@@ -219,4 +250,10 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return true; return true;
} }
private void assertMaxTargetsQuota(final String query) {
QuotaHelper.assertAssignmentQuota(
targetRepository.count(RSQLUtility.parse(query, TargetFields.class, virtualPropertyReplacer, database)),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
}
} }

View File

@@ -93,12 +93,23 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
* *
* @param pageReq * @param pageReq
* Pageable * Pageable
* @param swId * @param softwareModuleId
* software module id * software module id
* *
* @return Page<Artifact> * @return Page<Artifact>
*/ */
Page<Artifact> findBySoftwareModuleId(Pageable pageReq, final Long swId); Page<Artifact> findBySoftwareModuleId(Pageable pageReq, Long softwareModuleId);
/**
* Count the artifacts that are associated with the given software module.
*
* @param softwareModuleId
* software module ID
*
* @return the current number of artifacts associated with the software
* module.
*/
long countBySoftwareModuleId(Long softwareModuleId);
/** /**
* Searches for a {@link Artifact} based user provided filename at upload * Searches for a {@link Artifact} based user provided filename at upload
@@ -108,8 +119,9 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
* to search * to search
* @param softwareModuleId * @param softwareModuleId
* selected software module id * selected software module id
*
* @return list of {@link Artifact}. * @return list of {@link Artifact}.
*/ */
Optional<Artifact> findFirstByFilenameAndSoftwareModuleId(final String filename, final Long softwareModuleId); Optional<Artifact> findFirstByFilenameAndSoftwareModuleId(String filename, Long softwareModuleId);
} }

View File

@@ -15,6 +15,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -43,9 +44,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OfflineDsAssignmentStrategy(final TargetRepository targetRepository, OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher, final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository, final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) { final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository, super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
actionStatusRepository); actionStatusRepository, quotaManagement);
} }
@Override @Override

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -41,9 +42,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OnlineDsAssignmentStrategy(final TargetRepository targetRepository, OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher, final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository, final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) { final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository, super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
actionStatusRepository); actionStatusRepository, quotaManagement);
} }
@Override @Override

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.PropertiesQuotaManagement; import org.eclipse.hawkbit.repository.PropertiesQuotaManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration; import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration;
import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutGroupManagement;
@@ -69,6 +70,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule; 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.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder; import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
@@ -376,7 +378,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
DistributionSetManagement distributionSetManagement(final EntityManager entityManager, DistributionSetManagement distributionSetManagement(final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository, final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher, final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher,
@@ -386,7 +388,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetTagRepository distributionSetTagRepository, final DistributionSetTagRepository distributionSetTagRepository,
final AfterTransactionCommitExecutor afterCommit, final JpaProperties properties) { final AfterTransactionCommitExecutor afterCommit, final JpaProperties properties) {
return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement, return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement,
systemManagement, distributionSetTypeManagement, distributionSetMetadataRepository, systemManagement, distributionSetTypeManagement, quotaManagement, distributionSetMetadataRepository,
targetFilterQueryRepository, actionRepository, criteriaNoCountDao, eventPublisher, applicationContext, targetFilterQueryRepository, actionRepository, criteriaNoCountDao, eventPublisher, applicationContext,
tenantAware, virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository, tenantAware, virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository,
afterCommit, properties.getDatabase()); afterCommit, properties.getDatabase());
@@ -405,9 +407,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final SoftwareModuleTypeRepository softwareModuleTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao, final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao,
final JpaProperties properties) { final JpaProperties properties, final QuotaManagement quotaManagement) {
return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository, return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
distributionSetRepository, virtualPropertyReplacer, criteriaNoCountDao, properties.getDatabase()); distributionSetRepository, virtualPropertyReplacer, criteriaNoCountDao, properties.getDatabase(),
quotaManagement);
} }
/** /**
@@ -457,22 +460,29 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* {@link JpaTargetFilterQueryManagement} bean. * {@link JpaTargetFilterQueryManagement} bean.
* *
* @param targetFilterQueryRepository * @param targetFilterQueryRepository
* to query entity access * holding {@link TargetFilterQuery} entities
* @param targetRepository
* holding {@link Target} entities
* @param virtualPropertyReplacer * @param virtualPropertyReplacer
* for RSQL handling * for RSQL handling
* @param distributionSetManagement * @param distributionSetManagement
* for auto assign DS access * for auto assign DS access
* * @param quotaManagement
* to access quotas
* @param properties
* JPA properties
*
* @return a new {@link TargetFilterQueryManagement} * @return a new {@link TargetFilterQueryManagement}
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
TargetFilterQueryManagement targetFilterQueryManagement( TargetFilterQueryManagement targetFilterQueryManagement(
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final TargetRepository targetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final JpaProperties properties) { final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, virtualPropertyReplacer, final JpaProperties properties) {
distributionSetManagement, properties.getDatabase()); return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository,
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase());
} }
/** /**
@@ -518,11 +528,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final SoftwareModuleMetadataRepository softwareModuleMetadataRepository, final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider, final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider,
final ArtifactManagement artifactManagement, final VirtualPropertyReplacer virtualPropertyReplacer, final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement,
final JpaProperties properties) { final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
return new JpaSoftwareModuleManagement(entityManager, distributionSetRepository, softwareModuleRepository, return new JpaSoftwareModuleManagement(entityManager, distributionSetRepository, softwareModuleRepository,
softwareModuleMetadataRepository, softwareModuleTypeRepository, criteriaNoCountDao, auditorProvider, softwareModuleMetadataRepository, softwareModuleTypeRepository, criteriaNoCountDao, auditorProvider,
artifactManagement, virtualPropertyReplacer, properties.getDatabase()); artifactManagement, quotaManagement, virtualPropertyReplacer, properties.getDatabase());
} }
/** /**
@@ -585,12 +595,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext, final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer, final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final JpaProperties properties) { final SystemSecurityContext systemSecurityContext, final JpaProperties properties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository, return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
actionStatusRepository, targetManagement, auditorProvider, eventPublisher, applicationContext, actionStatusRepository, targetManagement, auditorProvider, eventPublisher, applicationContext,
afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement, systemSecurityContext, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement, quotaManagement,
properties.getDatabase()); systemSecurityContext, properties.getDatabase());
} }
/** /**
@@ -609,9 +619,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
ArtifactManagement artifactManagement(final LocalArtifactRepository localArtifactRepository, ArtifactManagement artifactManagement(final LocalArtifactRepository localArtifactRepository,
final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository, final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) { final QuotaManagement quotaManagement, final TenantAware tenantAware) {
return new JpaArtifactManagement(localArtifactRepository, softwareModuleRepository, artifactRepository, return new JpaArtifactManagement(localArtifactRepository, softwareModuleRepository, artifactRepository,
tenantAware); quotaManagement, tenantAware);
} }
/** /**

View File

@@ -30,11 +30,49 @@ public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>, extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> { JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**
* Locates the meta data entries that match the given software module ID and
* target visibility flag.
*
* @param page
* The pagination parameters.
* @param moduleId
* The ID of the software module.
* @param targetVisible
* The target visibility flag.
*
* @return A {@link Page} with the matching meta data entries.
*/
Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId, Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId,
boolean targetVisible); boolean targetVisible);
/**
* Locates the meta data entries that match the given software module IDs
* and target visibility flag.
*
* @param page
* The pagination parameters.
* @param moduleId
* List of software module IDs.
* @param targetVisible
* The target visibility flag.
*
* @return A {@link Page} with the matching meta data entries.
*/
@Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible") @Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible")
Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection<Long> moduleId, Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection<Long> moduleId,
@Param("targetVisible") boolean targetVisible); @Param("targetVisible") boolean targetVisible);
/**
* Counts the meta data entries that are associated with the addressed
* software module.
*
* @param moduleId
* The ID of the software module.
*
* @return The number of meta data entries associated with the software
* module.
*/
long countBySoftwareModuleId(@Param("moduleId") Long moduleId);
} }

View File

@@ -87,6 +87,18 @@ public interface SoftwareModuleRepository
*/ */
Page<SoftwareModule> findByAssignedToId(Pageable pageable, Long setId); Page<SoftwareModule> findByAssignedToId(Pageable pageable, Long setId);
/**
* Count the software modules which are assigned to the distribution set
* with the given ID.
*
* @param setId
* the distribution set ID
*
* @return the number of software modules matching the given distribution
* set ID.
*/
long countByAssignedToId(Long setId);
/** /**
* @param pageable * @param pageable
* the page request to page the result set * the page request to page the result set

View File

@@ -65,9 +65,10 @@ public class AutoAssignScheduler {
LOGGER.debug("auto assign schedule checker has been triggered."); LOGGER.debug("auto assign schedule checker has been triggered.");
// run this code in system code privileged to have the necessary // run this code in system code privileged to have the necessary
// permission to query and create entities. // permission to query and create entities.
systemSecurityContext.runAsSystem(() -> executeAutoAssign()); systemSecurityContext.runAsSystem(this::executeAutoAssign);
} }
@SuppressWarnings("squid:S3516")
private Object executeAutoAssign() { private Object executeAutoAssign() {
// workaround eclipselink that is currently not possible to // workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant // execute a query without multitenancy if MultiTenant

View File

@@ -216,7 +216,7 @@ public final class RSQLUtility {
private final SimpleTypeConverter simpleTypeConverter; private final SimpleTypeConverter simpleTypeConverter;
final Database database; private final Database database;
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType, private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2018 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.utils;
import java.util.function.Function;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
/**
* Helper class to check assignment quotas.
*/
@Validated
public final class QuotaHelper {
/**
* Class logger
*/
private static final Logger LOG = LoggerFactory.getLogger(QuotaHelper.class);
private QuotaHelper() {
// no need to instantiate this class
}
/**
* Asserts the specified assignment quota.
*
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class<?> type,
@NotNull final Class<?> parentType) {
assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null);
}
/**
* Asserts the specified assignment quota.
*
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
* @param countFct
* Function to count the entities that are currently assigned to
* the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final Function<Long, Long> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
}
/**
* Asserts the specified assignment quota.
*
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
* @param countFct
* Function to count the entities that are currently assigned to
* the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final Function<Long, Long> countFct) {
// check if the quota is unlimited
if (limit <= 0) {
LOG.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType);
return;
}
if (requested > limit) {
final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>";
LOG.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested,
type, parentType, parentIdStr, limit);
throw new QuotaExceededException(type, parentType, parentId, requested, limit);
}
if (parentId != null && countFct != null) {
final long currentCount = countFct.apply(parentId);
if (currentCount + requested > limit) {
LOG.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.",
requested, type, parentType, parentId, limit, currentCount, type);
throw new QuotaExceededException(type, parentType, parentId, requested, limit);
}
}
}
}

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@@ -17,6 +18,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
@@ -25,6 +27,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
@@ -35,6 +38,8 @@ import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.Test; import org.junit.Test;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories; import ru.yandex.qatools.allure.annotations.Stories;
@@ -67,12 +72,13 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@ExpectEvents({ @Expect(type = SoftwareModuleDeletedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = SoftwareModuleDeletedEvent.class, count = 0) })
public void entityQueriesReferringToNotExistingEntitiesThrowsException() throws URISyntaxException { public void entityQueriesReferringToNotExistingEntitiesThrowsException() throws URISyntaxException {
verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream("test", "UTF-8"), NOT_EXIST_IDL, final String artifactData = "test";
"xxx", null, null, false, null), "SoftwareModule"); final int artifactSize = artifactData.length();
verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, false, null, artifactSize), "SoftwareModule");
verifyThrownExceptionBy( verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream(artifactData, "UTF-8"), 1234L,
() -> artifactManagement.create(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", false), "xxx", false, artifactSize), "SoftwareModule");
"SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact"); verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact");
@@ -86,59 +92,128 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Test if a local artifact can be created by API including metadata.") @Description("Test if a local artifact can be created by API including metadata.")
public void createArtifact() throws NoSuchAlgorithmException, IOException { public void createArtifact() throws NoSuchAlgorithmException, IOException {
// checkbaseline
// check baseline
assertThat(softwareModuleRepository.findAll()).hasSize(0); assertThat(softwareModuleRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0); assertThat(artifactRepository.findAll()).hasSize(0);
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null); final JpaSoftwareModule sm = softwareModuleRepository
sm = softwareModuleRepository.save(sm); .save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3", null, null));
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null); final int artifactSize = 5 * 1024;
sm2 = softwareModuleRepository.save(sm2); final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
JpaSoftwareModule sm3 = new JpaSoftwareModule(osType, "name 3", "version 3", null, null); try (final InputStream inputStream1 = new ByteArrayInputStream(random);
sm3 = softwareModuleRepository.save(sm3); final InputStream inputStream2 = new ByteArrayInputStream(random);
final InputStream inputStream3 = new ByteArrayInputStream(random);
final InputStream inputStream4 = new ByteArrayInputStream(random);) {
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
artifactManagement.create(inputStream2, sm.getId(), "file11", false, artifactSize);
artifactManagement.create(inputStream3, sm.getId(), "file12", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream4, sm2.getId(), "file2", false, artifactSize);
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); assertThat(result).isInstanceOf(Artifact.class);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file11", false); assertThat(result.getSoftwareModule().getId()).isEqualTo(sm.getId());
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file12", false); assertThat(result2.getSoftwareModule().getId()).isEqualTo(sm2.getId());
final Artifact result2 = artifactManagement.create(new ByteArrayInputStream(random), sm2.getId(), "file2", assertThat(((JpaArtifact) result).getFilename()).isEqualTo("file1");
false); assertThat(((JpaArtifact) result).getSha1Hash()).isNotNull();
assertThat(result).isNotEqualTo(result2);
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(result).isInstanceOf(Artifact.class); assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash())
assertThat(result.getSoftwareModule().getId()).isEqualTo(sm.getId()); .isEqualTo(HashGeneratorUtils.generateSHA1(random));
assertThat(result2.getSoftwareModule().getId()).isEqualTo(sm2.getId()); assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash())
assertThat(((JpaArtifact) result).getFilename()).isEqualTo("file1"); .isEqualTo(HashGeneratorUtils.generateMD5(random));
assertThat(((JpaArtifact) result).getSha1Hash()).isNotNull();
assertThat(result).isNotEqualTo(result2);
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash()) assertThat(artifactRepository.findAll()).hasSize(4);
.isEqualTo(HashGeneratorUtils.generateSHA1(random)); assertThat(softwareModuleRepository.findAll()).hasSize(3);
assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash())
.isEqualTo(HashGeneratorUtils.generateMD5(random));
assertThat(artifactRepository.findAll()).hasSize(4); assertThat(softwareModuleManagement.get(sm.getId()).get().getArtifacts()).hasSize(3);
assertThat(softwareModuleRepository.findAll()).hasSize(3); }
assertThat(softwareModuleManagement.get(sm.getId()).get().getArtifacts()).hasSize(3); }
@Test
@Description("Verifies that the quota specifying the maximum number of artifacts per software module is enforced.")
public void createArtifactsUntilQuotaIsExceeded() throws NoSuchAlgorithmException, IOException {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
// now create artifacts for this module until the quota is exceeded
final long maxArtifacts = quotaManagement.getMaxArtifactsPerSoftwareModule();
final List<Long> artifactIds = Lists.newArrayList();
final int artifactSize = 5 * 1024;
for (int i = 0; i < maxArtifacts; ++i) {
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactIds.add(
artifactManagement.create(inputStream, sm1.getId(), "file" + i, false, artifactSize).getId());
}
}
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts);
// create one mode to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> {
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm1.getId(), "file" + maxArtifacts, false, artifactSize);
}
});
// delete one of the artifacts
artifactManagement.delete(artifactIds.get(0));
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts - 1);
// now we should be able to create an artifact again
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm1.getId(), "fileXYZ", false, artifactSize);
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts);
}
}
@Test
@Description("Verifies that you cannot create artifacts which exceed the configured maximum size.")
public void createArtifactFailsIfTooLarge() throws NoSuchAlgorithmException, IOException {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
// create an artifact that exceeds the configured quota
final long maxSize = quotaManagement.getMaxArtifactSize();
final int artifactSize = Math.toIntExact(maxSize) + 8;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> artifactManagement.create(inputStream, sm1.getId(), "file", false, artifactSize));
}
} }
@Test @Test
@Description("Tests hard delete directly on repository.") @Description("Tests hard delete directly on repository.")
public void hardDeleteSoftwareModule() throws NoSuchAlgorithmException, IOException { public void hardDeleteSoftwareModule() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm.getId(), "file1", false, artifactSize);
assertThat(artifactRepository.findAll()).hasSize(1);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); softwareModuleRepository.deleteAll();
assertThat(artifactRepository.findAll()).hasSize(1); assertThat(artifactRepository.findAll()).hasSize(0);
}
softwareModuleRepository.deleteAll();
assertThat(artifactRepository.findAll()).hasSize(0);
} }
/** /**
@@ -152,97 +227,109 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Tests the deletion of a local artifact including metadata.") @Description("Tests the deletion of a local artifact including metadata.")
public void deleteArtifact() throws NoSuchAlgorithmException, IOException { public void deleteArtifact() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null); final JpaSoftwareModule sm = softwareModuleRepository
sm2 = softwareModuleRepository.save(sm2); .save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
assertThat(artifactRepository.findAll()).isEmpty(); assertThat(artifactRepository.findAll()).isEmpty();
final Artifact result = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", final int artifactSize = 5 * 1024;
false); try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize);
final Artifact result2 = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm2.getId(), final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) {
"file2", false);
assertThat(artifactRepository.findAll()).hasSize(2); final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream2, sm2.getId(), "file2", false, artifactSize);
assertThat(result.getId()).isNotNull(); assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) assertThat(result.getId()).isNotNull();
.isNotNull(); assertThat(result2.getId()).isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash())) assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash());
.isNotNull();
artifactManagement.delete(result.getId()); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) artifactManagement.delete(result.getId());
.isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
artifactManagement.delete(result2.getId()); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash())) .isNull();
.isNull(); assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
assertThat(artifactRepository.findAll()).hasSize(0); artifactManagement.delete(result2.getId());
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNull();
assertThat(artifactRepository.findAll()).hasSize(0);
}
} }
@Test @Test
@Description("Test the deletion of an artifact metadata where the binary is still linked to another " @Description("Test the deletion of an artifact metadata where the binary is still linked to another "
+ "metadata element. The expected result is that the metadata is deleted but the binary kept.") + "metadata element. The expected result is that the metadata is deleted but the binary kept.")
public void deleteDuplicateArtifacts() throws NoSuchAlgorithmException, IOException { public void deleteDuplicateArtifacts() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null); final JpaSoftwareModule sm = softwareModuleRepository
sm2 = softwareModuleRepository.save(sm2); .save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); try (final InputStream inputStream1 = new ByteArrayInputStream(random);
final Artifact result2 = artifactManagement.create(new ByteArrayInputStream(random), sm2.getId(), "file2", final InputStream inputStream2 = new ByteArrayInputStream(random)) {
false); final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream2, sm2.getId(), "file2", false, artifactSize);
assertThat(artifactRepository.findAll()).hasSize(2); assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(result.getId()).isNotNull(); assertThat(result.getId()).isNotNull();
assertThat(result2.getId()).isNotNull(); assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash()); assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull(); .isNotNull();
artifactManagement.delete(result.getId()); artifactManagement.delete(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull(); .isNotNull();
artifactManagement.delete(result2.getId()); artifactManagement.delete(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash())) assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull(); .isNull();
}
} }
@Test @Test
@Description("Loads an local artifact based on given ID.") @Description("Loads an local artifact based on given ID.")
public void findArtifact() throws NoSuchAlgorithmException, IOException { public void findArtifact() throws NoSuchAlgorithmException, IOException {
final Artifact result = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), final int artifactSize = 5 * 1024;
testdataFactory.createSoftwareModuleOs().getId(), "file1", false); try (final InputStream inputStream = new RandomGeneratedInputStream(artifactSize)) {
final Artifact result = artifactManagement.create(inputStream,
assertThat(artifactManagement.get(result.getId()).get()).isEqualTo(result); testdataFactory.createSoftwareModuleOs().getId(), "file1", false, artifactSize);
assertThat(artifactManagement.get(result.getId()).get()).isEqualTo(result);
}
} }
@Test @Test
@Description("Loads an artifact binary based on given ID.") @Description("Loads an artifact binary based on given ID.")
public void loadStreamOfArtifact() throws NoSuchAlgorithmException, IOException { public void loadStreamOfArtifact() throws NoSuchAlgorithmException, IOException {
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random), try (final InputStream input = new ByteArrayInputStream(random)) {
testdataFactory.createSoftwareModuleOs().getId(), "file1", false); final Artifact result = artifactManagement.create(input, testdataFactory.createSoftwareModuleOs().getId(),
"file1", false, artifactSize);
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get() try (final InputStream inputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get()
.getFileInputStream()) { .getFileInputStream()) {
assertTrue("The stored binary matches the given binary", assertTrue("The stored binary matches the given binary",
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream)); IOUtils.contentEquals(new ByteArrayInputStream(random), inputStream));
}
} }
} }
@@ -260,27 +347,31 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Searches an artifact through the relations of a software module.") @Description("Searches an artifact through the relations of a software module.")
public void findArtifactBySoftwareModule() { public void findArtifactBySoftwareModule() throws IOException {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).isEmpty(); assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).isEmpty();
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", false); final int artifactSize = 5 * 1024;
try (final InputStream input = new RandomGeneratedInputStream(artifactSize)) {
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).hasSize(1); artifactManagement.create(input, sm.getId(), "file1", false, artifactSize);
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).hasSize(1);
}
} }
@Test @Test
@Description("Searches an artifact through the relations of a software module and the filename.") @Description("Searches an artifact through the relations of a software module and the filename.")
public void findByFilenameAndSoftwareModule() { public void findByFilenameAndSoftwareModule() throws IOException {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isNotPresent(); assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isNotPresent();
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", false); final int artifactSize = 5 * 1024;
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file2", false); try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize);
final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) {
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isPresent(); artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
artifactManagement.create(inputStream2, sm.getId(), "file2", false, artifactSize);
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isPresent();
}
} }
} }

View File

@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException;
@@ -430,7 +431,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6), @Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 2) }) @Expect(type = SoftwareModuleUpdatedEvent.class, count = 2) })
public void hasTargetArtifactAssignedIsTrueWithMultipleArtifacts() { public void hasTargetArtifactAssignedIsTrueWithMultipleArtifacts() {
final byte[] random = RandomUtils.nextBytes(5 * 1024); final int artifactSize = 5 * 1024;
final byte[] random = RandomUtils.nextBytes(artifactSize);
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
final DistributionSet ds2 = testdataFactory.createDistributionSet("2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
@@ -438,9 +440,9 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// create two artifacts with identical SHA1 hash // create two artifacts with identical SHA1 hash
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random), final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false); ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random), final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random),
ds2.findFirstModuleByType(osType).get().getId(), "file1", false); ds2.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash()); assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat( assertThat(
@@ -793,7 +795,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetUpdatedEvent.class, count = 2) }) @Expect(type = TargetUpdatedEvent.class, count = 2) })
public void updateTargetAttributesFailsIfTooManyEntries() throws Exception { public void updateTargetAttributesFailsIfTooManyEntries() throws Exception {
final String controllerId = "test123"; final String controllerId = "test123";
final int allowedAttributes = 10; final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
testdataFactory.createTarget(controllerId); testdataFactory.createTarget(controllerId);
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> securityRule assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> securityRule
@@ -880,4 +882,69 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(messages.get(0)).as("Message of action-status").isEqualTo("proceeding message 2"); assertThat(messages.get(0)).as("Message of action-status").isEqualTo("proceeding message 2");
assertThat(messages.get(1)).as("Message of action-status").isEqualTo("proceeding message 1"); assertThat(messages.get(1)).as("Message of action-status").isEqualTo("proceeding message 1");
} }
@Test
@Description("Verifies that the quota specifying the maximum number of status entries per action is enforced.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void addActionStatusUpdatesUntilQuotaIsExceeded() {
// any distribution set assignment causes 1 status entity to be created
final int maxStatusEntries = quotaManagement.getMaxStatusEntriesPerAction() - 1;
// test for informational status
final Long actionId1 = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1, "t1")).getActions().get(0);
assertThat(actionId1).isNotNull();
for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1)
.status(Status.WARNING).message("Msg " + i).occurredAt(System.currentTimeMillis()));
}
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1).status(Status.WARNING)));
// test for update status (and mixed case)
final Long actionId2 = assignDistributionSet(testdataFactory.createDistributionSet("ds2"),
testdataFactory.createTargets(1, "t2")).getActions().get(0);
assertThat(actionId2).isNotEqualTo(actionId1);
for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId2)
.status(Status.WARNING).message("Msg " + i).occurredAt(System.currentTimeMillis()));
}
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(actionId2).status(Status.WARNING)));
}
@Test
@Description("Verifies that the quota specifying the maximum number of messages per action status is enforced.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void createActionStatusWithTooManyMessages() {
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
final Long actionId = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1)).getActions().get(0);
assertThat(actionId).isNotNull();
final List<String> messages = Lists.newArrayList();
IntStream.range(0, maxMessages).forEach(i -> messages.add(i, "msg"));
assertThat(controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(actionId).messages(messages).status(Status.WARNING))).isNotNull();
messages.add("msg");
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(actionId).messages(messages).status(Status.WARNING)));
}
} }

View File

@@ -22,6 +22,7 @@ import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
@@ -35,6 +36,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
@@ -171,6 +173,37 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(actions.getContent().get(0).getId()).as("Action of target").isEqualTo(actionId); assertThat(actions.getContent().get(0).getId()).as("Action of target").isEqualTo(actionId);
} }
@Test
@Description("Test verifies that the 'max actions per target' quota is enforced if the assigned distribution set is changed permanently.")
public void changeDistributionSetAssignmentUntilMaxActionsPerTargetQuotaIsExceeded() {
final int maxActions = quotaManagement.getMaxActionsPerTarget();
final List<Target> testTargets = testdataFactory.createTargets(1);
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
IntStream.range(0, maxActions).forEach(i -> {
assignDistributionSet(i % 2 == 0 ? ds1 : ds2, testTargets);
});
// change the distribution set one last time to trigger a quota hit
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds3, testTargets));
}
@Test
@Description("Assigns the same distribution set to many targets until the 'max targets per manual assignment' quota is exceeded.")
public void assignDistributionSetUntilQuotaIsExceeded() {
final int maxTargets = quotaManagement.getMaxTargetsPerManualAssignment();
final DistributionSet ds = testdataFactory.createDistributionSet();
assignDistributionSet(ds, testdataFactory.createTargets(maxTargets, "ok"));
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds, testdataFactory.createTargets(maxTargets + 1, "fail")));
}
@Test @Test
@Description("Test verifies that action-states of an action are found by using id-based search.") @Description("Test verifies that action-states of an action are found by using id-based search.")
public void findActionStatusByActionId() { public void findActionStatusByActionId() {

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
@@ -42,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSe
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -333,6 +335,51 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(createdMetadata.getValue()).isEqualTo(knownValue); assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
} }
@Test
@Description("Verifies the enforcement of the metadata quota per distribution set.")
public void createDistributionSetMetadataUntilQuotaIsExceeded() {
// add meta data one by one
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
for (int i = 0; i < maxMetaData; ++i) {
assertThat((JpaDistributionSetMetadata) createDistributionSetMetadata(ds1.getId(),
new JpaDistributionSetMetadata("k" + i, ds1, "v" + i))).isNotNull();
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds1.getId(), createDistributionSetMetadata(ds1.getId(),
new JpaDistributionSetMetadata("k" + maxMetaData, ds1, "v" + maxMetaData))));
// add multiple meta data entries at once
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final List<MetaData> metaData2 = new ArrayList<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
metaData2.add(new JpaDistributionSetMetadata("k" + i, ds2, "v" + i));
}
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds2.getId(), metaData2));
// add some meta data entries
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
final int firstHalf = Math.round(maxMetaData / 2);
for (int i = 0; i < firstHalf; ++i) {
createDistributionSetMetadata(ds3.getId(), new JpaDistributionSetMetadata("k" + i, ds3, "v" + i));
}
// add too many data entries
final int secondHalf = maxMetaData - firstHalf;
final List<MetaData> metaData3 = new ArrayList<>();
for (int i = 0; i < secondHalf + 1; ++i) {
metaData3.add(new JpaDistributionSetMetadata("kk" + i, ds3, "vv" + i));
}
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds3.getId(), metaData3));
}
@Test @Test
@Description("Ensures that distribution sets can assigned and unassigned to a distribution set tag.") @Description("Ensures that distribution sets can assigned and unassigned to a distribution set tag.")
public void assignAndUnassignDistributionSetToTag() { public void assignAndUnassignDistributionSetToTag() {
@@ -400,8 +447,8 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Description("Ensures that it is not possible to add a software module that is not defined of the DS's type.") @Description("Ensures that it is not possible to add a software module that is not defined of the DS's type.")
public void updateDistributionSetUnsupportedModuleFails() { public void updateDistributionSetUnsupportedModuleFails() {
final DistributionSet set = distributionSetManagement final DistributionSet set = distributionSetManagement
.create(entityFactory.distributionSet().create().name("agent-hub2") .create(entityFactory
.version( .distributionSet().create().name("agent-hub2").version(
"1.0.5") "1.0.5")
.type(distributionSetTypeManagement.create(entityFactory.distributionSetType().create() .type(distributionSetTypeManagement.create(entityFactory.distributionSetType().create()
.key("test").name("test").mandatory(Arrays.asList(osType.getId()))).getKey())); .key("test").name("test").mandatory(Arrays.asList(osType.getId()))).getKey()));
@@ -443,6 +490,46 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(ds.isRequiredMigrationStep()).isTrue(); assertThat(ds.isRequiredMigrationStep()).isTrue();
} }
@Test
@Description("Verifies the enforcement of the software module quota per distribution set.")
public void assignSoftwareModulesUntilQuotaIsExceeded() {
// create some software modules
final int maxModules = quotaManagement.getMaxSoftwareModulesPerDistributionSet();
final List<Long> modules = Lists.newArrayList();
for (int i = 0; i < maxModules + 1; ++i) {
modules.add(testdataFactory.createSoftwareModuleApp("sm" + i).getId());
}
// assign software modules one by one
final DistributionSet ds1 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds1", "1.0");
for (int i = 0; i < maxModules; ++i) {
distributionSetManagement.assignSoftwareModules(ds1.getId(), Collections.singletonList(modules.get(i)));
}
// add one more to cause the quota to be exceeded
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> {
distributionSetManagement.assignSoftwareModules(ds1.getId(),
Collections.singletonList(modules.get(maxModules)));
});
// assign all software modules at once
final DistributionSet ds2 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds2", "1.0");
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(ds2.getId(), modules));
// assign some software modules
final DistributionSet ds3 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds3", "1.0");
final int firstHalf = Math.round(maxModules / 2);
for (int i = 0; i < firstHalf; ++i) {
distributionSetManagement.assignSoftwareModules(ds3.getId(), Collections.singletonList(modules.get(i)));
}
// assign the remaining modules to cause the quota to be exceeded
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> distributionSetManagement
.assignSoftwareModules(ds3.getId(), modules.subList(firstHalf, modules.size())));
}
@Test @Test
@WithUser(allSpPermissions = true) @WithUser(allSpPermissions = true)
@Description("Checks that metadata for a distribution set can be updated.") @Description("Checks that metadata for a distribution set can be updated.")
@@ -491,7 +578,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator(); final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator();
final Iterator<Target> tIterator = buildTargetFixtures.iterator(); final Iterator<Target> tIterator = buildTargetFixtures.iterator();
final DistributionSet dsFirst = dsIterator.next(); dsIterator.next();
final DistributionSet dsSecond = dsIterator.next(); final DistributionSet dsSecond = dsIterator.next();
final DistributionSet dsThree = dsIterator.next(); final DistributionSet dsThree = dsIterator.next();
final DistributionSet dsFour = dsIterator.next(); final DistributionSet dsFour = dsIterator.next();
@@ -775,7 +862,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
new JpaDistributionSetMetadata("key" + index, ds1, "value" + index)); new JpaDistributionSetMetadata("key" + index, ds1, "value" + index));
} }
for (int index = 0; index < 20; index++) { for (int index = 0; index < 8; index++) {
createDistributionSetMetadata(ds2.getId(), createDistributionSetMetadata(ds2.getId(),
new JpaDistributionSetMetadata("key" + index, ds2, "value" + index)); new JpaDistributionSetMetadata("key" + index, ds2, "value" + index));
} }
@@ -789,8 +876,8 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(metadataOfDs1.getNumberOfElements()).isEqualTo(10); assertThat(metadataOfDs1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfDs1.getTotalElements()).isEqualTo(10); assertThat(metadataOfDs1.getTotalElements()).isEqualTo(10);
assertThat(metadataOfDs2.getNumberOfElements()).isEqualTo(20); assertThat(metadataOfDs2.getNumberOfElements()).isEqualTo(8);
assertThat(metadataOfDs2.getTotalElements()).isEqualTo(20); assertThat(metadataOfDs2.getTotalElements()).isEqualTo(8);
} }
@Test @Test

View File

@@ -8,17 +8,26 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.util.Lists;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -28,10 +37,6 @@ import org.junit.Test;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Step;
@@ -193,7 +198,7 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes
} }
@Test @Test
@Description("Tests the successfull module update of unused distribution set type which is in fact allowed.") @Description("Tests the successful module update of unused distribution set type which is in fact allowed.")
public void updateUnassignedDistributionSetTypeModules() { public void updateUnassignedDistributionSetTypeModules() {
final DistributionSetType updatableType = distributionSetTypeManagement final DistributionSetType updatableType = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("updatableType").name("to be deleted")); .create(entityFactory.distributionSetType().create().key("updatableType").name("to be deleted"));
@@ -217,6 +222,55 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes
.containsOnly(runtimeType); .containsOnly(runtimeType);
} }
@Test
@Description("Verifies that the quota for software module types per distribution set type is enforced as expected.")
public void quotaMaxSoftwareModuleTypes() {
final int quota = quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType();
// create software module types
final List<Long> moduleTypeIds = Lists.newArrayList();
for (int i = 0; i < quota + 1; ++i) {
final SoftwareModuleTypeCreate smCreate = entityFactory.softwareModuleType().create().name("smType_" + i)
.description("smType_" + i).maxAssignments(1).colour("blue").key("smType_" + i);
moduleTypeIds.add(softwareModuleTypeManagement.create(smCreate).getId());
}
// assign all types at once
final DistributionSetType dsType1 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst1").name("dst1"));
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(
() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType1.getId(), moduleTypeIds));
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(
() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType1.getId(), moduleTypeIds));
// assign as many mandatory modules as possible
final DistributionSetType dsType2 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst2").name("dst2"));
distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2.getId(),
moduleTypeIds.subList(0, quota));
assertThat(distributionSetTypeManagement.get(dsType2.getId())).isNotEmpty();
assertThat(distributionSetTypeManagement.get(dsType2.getId()).get().getMandatoryModuleTypes().size())
.isEqualTo(quota);
// assign one more to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2.getId(),
Collections.singletonList(moduleTypeIds.get(quota))));
// assign as many optional modules as possible
final DistributionSetType dsType3 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst3").name("dst3"));
distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3.getId(),
moduleTypeIds.subList(0, quota));
assertThat(distributionSetTypeManagement.get(dsType3.getId())).isNotEmpty();
assertThat(distributionSetTypeManagement.get(dsType3.getId()).get().getOptionalModuleTypes().size())
.isEqualTo(quota);
// assign one more to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3.getId(),
Collections.singletonList(moduleTypeIds.get(quota))));
}
@Test @Test
@Description("Tests the successfull update of used distribution set type meta data which is in fact allowed.") @Description("Tests the successfull update of used distribution set type meta data which is in fact allowed.")
public void updateAssignedDistributionSetTypeMetaData() { public void updateAssignedDistributionSetTypeMetaData() {

View File

@@ -8,6 +8,9 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -39,6 +42,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -70,9 +74,6 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Step;
@@ -1257,6 +1258,87 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus); validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
} }
@Test
@Description("Verify that a rollout cannot be created if the 'max targets per rollout group' quota is violated.")
public void createRolloutFailsIfQuotaGroupQuotaIsViolated() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
final int amountTargetsForRollout = maxTargets + 1;
final int amountGroups = 1;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTest";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
amountGroups, conditions));
}
@Test
@Description("Verify that a rollout cannot be created based on group definitions if the 'max targets per rollout group' quota is violated for one of the groups.")
public void createRolloutWithGroupDefinitionsFailsIfQuotaGroupQuotaIsViolated() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
final int amountTargetsForRollout = maxTargets * 2 + 2;
final String rolloutName = "rolloutTest";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
// create group definitions
final RolloutGroupCreate group1 = entityFactory.rolloutGroup().create().conditions(conditions).name("group1")
.targetPercentage(50.0F);
final RolloutGroupCreate group2 = entityFactory.rolloutGroup().create().conditions(conditions).name("group2")
.targetPercentage(100.0F);
// group1 exceeds the quota
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group1, group2), conditions));
// create group definitions
final RolloutGroupCreate group3 = entityFactory.rolloutGroup().create().conditions(conditions).name("group3")
.targetPercentage(1.0F);
final RolloutGroupCreate group4 = entityFactory.rolloutGroup().create().conditions(conditions).name("group4")
.targetPercentage(100.0F);
// group4 exceeds the quota
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group3, group4), conditions));
// create group definitions
final RolloutGroupCreate group5 = entityFactory.rolloutGroup().create().conditions(conditions).name("group5")
.targetPercentage(33.3F);
final RolloutGroupCreate group6 = entityFactory.rolloutGroup().create().conditions(conditions).name("group6")
.targetPercentage(66.6F);
final RolloutGroupCreate group7 = entityFactory.rolloutGroup().create().conditions(conditions).name("group7")
.targetPercentage(100.0F);
// should work fine
assertThat(rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group5, group6, group7), conditions)).isNotNull();
}
@Test @Test
@Description("Verify the creation and the automatic start of a rollout.") @Description("Verify the creation and the automatic start of a rollout.")
public void createAndAutoStartRollout() throws Exception { public void createAndAutoStartRollout() throws Exception {
@@ -1308,7 +1390,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify the creation of a Rollout with a groups definition.") @Description("Verify the creation of a rollout with a groups definition.")
public void createRolloutWithGroupDefinition() throws Exception { public void createRolloutWithGroupDefinition() throws Exception {
final String rolloutName = "rolloutTest3"; final String rolloutName = "rolloutTest3";
@@ -1355,7 +1437,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1); assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1);
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent(); final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent();
;
assertThat(groups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.READY); assertThat(groups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(0).getTotalTargets()).isEqualTo(amountTargetsInGroup1); assertThat(groups.get(0).getTotalTargets()).isEqualTo(amountTargetsInGroup1);
@@ -1368,7 +1450,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify Exception when a Rollout with Group definition is created that does not address all targets") @Description("Verify rollout creation fails if group definition does not address all targets")
public void createRolloutWithGroupsNotMatchingTargets() throws Exception { public void createRolloutWithGroupsNotMatchingTargets() throws Exception {
final String rolloutName = "rolloutTest4"; final String rolloutName = "rolloutTest4";
final int amountTargetsForRollout = 500; final int amountTargetsForRollout = 500;
@@ -1389,7 +1471,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify Exception when a Rollout with Group definition is created that contains an illegal percentage") @Description("Verify rollout creation fails if group definition specifies illegal target percentage")
public void createRolloutWithIllegalPercentage() throws Exception { public void createRolloutWithIllegalPercentage() throws Exception {
final String rolloutName = "rolloutTest6"; final String rolloutName = "rolloutTest6";
final int amountTargetsForRollout = 10; final int amountTargetsForRollout = 10;
@@ -1410,18 +1492,18 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify Exception when a Rollout is created with too much groups") @Description("Verify rollout creation fails if the 'max rollout groups per rollout' quota is violated.")
public void createRolloutWithIllegalAmountOfGroups() throws Exception { public void createRolloutWithIllegalAmountOfGroups() throws Exception {
final String rolloutName = "rolloutTest5"; final String rolloutName = "rolloutTest5";
final int amountTargetsForRollout = 10; final int targets = 10;
final int illegalGroupAmount = 501; final int maxGroups = quotaManagement.getMaxRolloutGroupsPerRollout();
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build(); final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
final RolloutCreate myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout); final RolloutCreate rollout = generateTargetsAndRollout(rolloutName, targets);
assertThatExceptionOfType(ValidationException.class) assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> rolloutManagement.create(myRollout, illegalGroupAmount, conditions)) .isThrownBy(() -> rolloutManagement.create(rollout, maxGroups + 1, conditions))
.withMessageContaining("not be greater than " + quotaManagement.getMaxRolloutGroupsPerRollout()); .withMessageContaining("not be greater than " + maxGroups);
} }

View File

@@ -15,6 +15,7 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -23,6 +24,7 @@ import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
@@ -361,13 +363,14 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void deleteSoftwareModulesWithSharedArtifact() throws IOException { public void deleteSoftwareModulesWithSharedArtifact() throws IOException {
// Init artifact binary data, target and DistributionSets // Init artifact binary data, target and DistributionSets
final byte[] source = RandomUtils.nextBytes(1024); final int artifactSize = 1024;
final byte[] source = RandomUtils.nextBytes(artifactSize);
// [STEP1]: Create SoftwareModuleX and add a new ArtifactX // [STEP1]: Create SoftwareModuleX and add a new ArtifactX
SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0); SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0);
// [STEP2]: Create newArtifactX and add it to SoftwareModuleX // [STEP2]: Create newArtifactX and add it to SoftwareModuleX
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false); artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false, artifactSize);
moduleX = softwareModuleManagement.get(moduleX.getId()).get(); moduleX = softwareModuleManagement.get(moduleX.getId()).get();
final Artifact artifactX = moduleX.getArtifacts().iterator().next(); final Artifact artifactX = moduleX.getArtifacts().iterator().next();
@@ -375,7 +378,7 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0); SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0);
// [STEP4]: Assign the same ArtifactX to SoftwareModuleY // [STEP4]: Assign the same ArtifactX to SoftwareModuleY
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false); artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false, artifactSize);
moduleY = softwareModuleManagement.get(moduleY.getId()).get(); moduleY = softwareModuleManagement.get(moduleY.getId()).get();
final Artifact artifactY = moduleY.getArtifacts().iterator().next(); final Artifact artifactY = moduleY.getArtifacts().iterator().next();
@@ -403,20 +406,21 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void deleteMultipleSoftwareModulesWhichShareAnArtifact() throws IOException { public void deleteMultipleSoftwareModulesWhichShareAnArtifact() throws IOException {
// Init artifact binary data, target and DistributionSets // Init artifact binary data, target and DistributionSets
final byte[] source = RandomUtils.nextBytes(1024); final int artifactSize = 1024;
final byte[] source = RandomUtils.nextBytes(artifactSize);
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
// [STEP1]: Create SoftwareModuleX and add a new ArtifactX // [STEP1]: Create SoftwareModuleX and add a new ArtifactX
SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0); SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0);
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false); artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false, artifactSize);
moduleX = softwareModuleManagement.get(moduleX.getId()).get(); moduleX = softwareModuleManagement.get(moduleX.getId()).get();
final Artifact artifactX = moduleX.getArtifacts().iterator().next(); final Artifact artifactX = moduleX.getArtifacts().iterator().next();
// [STEP2]: Create SoftwareModuleY and add the same ArtifactX // [STEP2]: Create SoftwareModuleY and add the same ArtifactX
SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0); SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0);
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false); artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false, artifactSize);
moduleY = softwareModuleManagement.get(moduleY.getId()).get(); moduleY = softwareModuleManagement.get(moduleY.getId()).get();
final Artifact artifactY = moduleY.getArtifacts().iterator().next(); final Artifact artifactY = moduleY.getArtifacts().iterator().next();
@@ -462,9 +466,10 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
SoftwareModule softwareModule = softwareModuleManagement.create(entityFactory.softwareModule().create() SoftwareModule softwareModule = softwareModuleManagement.create(entityFactory.softwareModule().create()
.type(type).name(name).version(version).description("description of artifact " + name)); .type(type).name(name).version(version).description("description of artifact " + name));
final int artifactSize = 5 * 1024;
for (int i = 0; i < numberArtifacts; i++) { for (int i = 0; i < numberArtifacts; i++) {
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), softwareModule.getId(), artifactManagement.create(new RandomGeneratedInputStream(artifactSize), softwareModule.getId(),
"file" + (i + 1), false); "file" + (i + 1), false, artifactSize);
} }
// Verify correct Creation of SoftwareModule and corresponding artifacts // Verify correct Creation of SoftwareModule and corresponding artifacts
@@ -652,14 +657,58 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(softwareModuleMetadata.get(0).getEntityId()).isEqualTo(ah.getId()); assertThat(softwareModuleMetadata.get(0).getEntityId()).isEqualTo(ah.getId());
} }
@Test
@Description("Verifies the enforcement of the metadata quota per software module.")
public void createSoftwareModuleMetadataUntilQuotaIsExceeded() {
// add meta data one by one
final SoftwareModule module = testdataFactory.createSoftwareModuleApp("m1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
for (int i = 0; i < maxMetaData; ++i) {
softwareModuleManagement.createMetaData(
entityFactory.softwareModuleMetadata().create(module.getId()).key("k" + i).value("v" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata()
.create(module.getId()).key("k" + maxMetaData).value("v" + maxMetaData)));
// add multiple meta data entries at once
final SoftwareModule module2 = testdataFactory.createSoftwareModuleApp("m2");
final List<SoftwareModuleMetadataCreate> create = new ArrayList<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
create.add(entityFactory.softwareModuleMetadata().create(module2.getId()).key("k" + i).value("v" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(create));
// add some meta data entries
final SoftwareModule module3 = testdataFactory.createSoftwareModuleApp("m3");
final int firstHalf = Math.round(maxMetaData / 2);
for (int i = 0; i < firstHalf; ++i) {
softwareModuleManagement.createMetaData(
entityFactory.softwareModuleMetadata().create(module3.getId()).key("k" + i).value("v" + i));
}
// add too many data entries
final int secondHalf = maxMetaData - firstHalf;
final List<SoftwareModuleMetadataCreate> create2 = new ArrayList<>();
for (int i = 0; i < secondHalf + 1; ++i) {
create2.add(entityFactory.softwareModuleMetadata().create(module3.getId()).key("kk" + i).value("vv" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(create2));
}
@Test @Test
@Description("Checks that metadata for a software module cannot be created for an existing key.") @Description("Checks that metadata for a software module cannot be created for an existing key.")
public void createSoftwareModuleMetadataFailsIfKeyExists() { public void createSoftwareModuleMetadataFailsIfKeyExists() {
final String knownKey1 = "myKnownKey1"; final String knownKey1 = "myKnownKey1";
final String knownValue1 = "myKnownValue1"; final String knownValue1 = "myKnownValue1";
final String knownValue2 = "myKnownValue2";
final SoftwareModule ah = testdataFactory.createSoftwareModuleApp(); final SoftwareModule ah = testdataFactory.createSoftwareModuleApp();
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey1) softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey1)
@@ -765,41 +814,43 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void findAllSoftwareModuleMetadataBySwId() { public void findAllSoftwareModuleMetadataBySwId() {
final SoftwareModule sw1 = testdataFactory.createSoftwareModuleApp(); final SoftwareModule sw1 = testdataFactory.createSoftwareModuleApp();
final int metadataCountSw1 = 8;
final SoftwareModule sw2 = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sw2 = testdataFactory.createSoftwareModuleOs();
final int metadataCountSw2 = 10;
for (int index = 0; index < 10; index++) { for (int index = 0; index < metadataCountSw1; index++) {
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw1.getId()) softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw1.getId())
.key("key" + index).value("value" + index).targetVisible(true)); .key("key" + index).value("value" + index).targetVisible(true));
} }
for (int index = 0; index < 20; index++) { for (int index = 0; index < metadataCountSw2; index++) {
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw2.getId()) softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw2.getId())
.key("key" + index).value("value" + index).targetVisible(false)); .key("key" + index).value("value" + index).targetVisible(false));
} }
Page<SoftwareModuleMetadata> metadataOfSw1 = softwareModuleManagement Page<SoftwareModuleMetadata> metadataSw1 = softwareModuleManagement
.findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw1.getId()); .findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw1.getId());
Page<SoftwareModuleMetadata> metadataOfSw2 = softwareModuleManagement Page<SoftwareModuleMetadata> metadataSw2 = softwareModuleManagement
.findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw2.getId()); .findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw2.getId());
assertThat(metadataOfSw1.getNumberOfElements()).isEqualTo(10); assertThat(metadataSw1.getNumberOfElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw1.getTotalElements()).isEqualTo(10); assertThat(metadataSw1.getTotalElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw2.getNumberOfElements()).isEqualTo(20); assertThat(metadataSw2.getNumberOfElements()).isEqualTo(metadataCountSw2);
assertThat(metadataOfSw2.getTotalElements()).isEqualTo(20); assertThat(metadataSw2.getTotalElements()).isEqualTo(metadataCountSw2);
metadataOfSw1 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100), metadataSw1 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
sw1.getId()); sw1.getId());
metadataOfSw2 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100), metadataSw2 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
sw2.getId()); sw2.getId());
assertThat(metadataOfSw1.getNumberOfElements()).isEqualTo(10); assertThat(metadataSw1.getNumberOfElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw1.getTotalElements()).isEqualTo(10); assertThat(metadataSw1.getTotalElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw2.getNumberOfElements()).isEqualTo(0); assertThat(metadataSw2.getNumberOfElements()).isEqualTo(0);
assertThat(metadataOfSw2.getTotalElements()).isEqualTo(0); assertThat(metadataSw2.getTotalElements()).isEqualTo(0);
} }
} }

View File

@@ -134,13 +134,13 @@ public class SystemManagementTest extends AbstractJpaIntegrationTest {
private void createTestArtifact(final byte[] random) { private void createTestArtifact(final byte[] random) {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false); artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, random.length);
} }
private void createDeletedTestArtifact(final byte[] random) { private void createDeletedTestArtifact(final byte[] random) {
final DistributionSet ds = testdataFactory.createDistributionSet("deleted garbage", true); final DistributionSet ds = testdataFactory.createDistributionSet("deleted garbage", true);
ds.getModules().stream().forEach(module -> { ds.getModules().stream().forEach(module -> {
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file1", false); artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file1", false, random.length);
softwareModuleManagement.delete(module.getId()); softwareModuleManagement.delete(module.getId());
}); });
} }

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@@ -24,6 +26,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -34,8 +37,6 @@ import org.junit.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import static org.assertj.core.api.Assertions.assertThat;
import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories; import ru.yandex.qatools.allure.annotations.Stories;
@@ -97,6 +98,20 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
targetFilterQueryManagement.getByName(filterName).get()); targetFilterQueryManagement.getByName(filterName).get());
} }
@Test
@Description("Create a target filter query with an auto-assign distribution set and a query string that addresses too many targets.")
public void createTargetFilterQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet set = testdataFactory.createDistributionSet();
// creation is supposed to work as there is no distribution set
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==target*")));
}
@Test @Test
@Description("Test searching a target filter query.") @Description("Test searching a target filter query.")
public void searchTargetFilterQuery() { public void searchTargetFilterQuery() {
@@ -181,6 +196,43 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
} }
@Test
@Description("Assigns a distribution set to an existing filter query and verifies that the quota 'max targets per auto assignment' is enforced.")
public void assignDistributionSetToTargetFilterQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
// creation is supposed to work as there is no distribution set
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("testfilter").query("name==target*"));
// assigning a distribution set is supposed to fail as the query
// addresses too many targets
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()));
}
@Test
@Description("Updates an existing filter query with a query string that addresses too many targets.")
public void updateTargetFilterQueryWithQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet set = testdataFactory.createDistributionSet();
// creation is supposed to work as the query does not exceed the quota
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==foo"));
// update with a query string that addresses too many targets
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement
.update(entityFactory.targetFilterQuery().update(targetFilterQuery.getId()).query("name==target*")));
}
@Test @Test
@Description("Test removing distribution set while it has a relation to a target filter query") @Description("Test removing distribution set while it has a relation to a target filter query")
public void removeAssignDistributionSet() { public void removeAssignDistributionSet() {

View File

@@ -10,6 +10,15 @@
# Quota - START # Quota - START
hawkbit.server.security.dos.maxStatusEntriesPerAction=10 hawkbit.server.security.dos.maxStatusEntriesPerAction=10
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10 hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
hawkbit.server.security.dos.maxMetaDataEntriesPerSoftwareModule=10
hawkbit.server.security.dos.maxRolloutGroupsPerRollout=20
hawkbit.server.security.dos.maxMessagesPerActionStatus=10
hawkbit.server.security.dos.maxMetaDataEntriesPerDistributionSet=10
hawkbit.server.security.dos.maxSoftwareModuleTypesPerDistributionSetType=10
hawkbit.server.security.dos.maxSoftwareModulesPerDistributionSet=10
hawkbit.server.security.dos.maxArtifactsPerSoftwareModule=10
hawkbit.server.security.dos.maxTargetsPerRolloutGroup=1000
hawkbit.server.security.dos.maxArtifactSize=1000000
# Quota - END # Quota - END
# Debug utility functions - START # Debug utility functions - START

View File

@@ -276,7 +276,11 @@ public abstract class AbstractIntegrationTest {
} }
protected DistributionSetMetadata createDistributionSetMetadata(final Long dsId, final MetaData md) { protected DistributionSetMetadata createDistributionSetMetadata(final Long dsId, final MetaData md) {
return distributionSetManagement.createMetaData(dsId, Collections.singletonList(md)).get(0); return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0);
}
protected List<DistributionSetMetadata> createDistributionSetMetadata(final Long dsId, final List<MetaData> md) {
return distributionSetManagement.createMetaData(dsId, md);
} }
protected Long getOsModule(final DistributionSet ds) { protected Long getOsModule(final DistributionSet ds) {

View File

@@ -463,8 +463,10 @@ public class TestdataFactory {
public List<Artifact> createArtifacts(final Long moduleId) { public List<Artifact> createArtifacts(final Long moduleId) {
final List<Artifact> artifacts = new ArrayList<>(); final List<Artifact> artifacts = new ArrayList<>();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
final InputStream stubInputStream = IOUtils.toInputStream("some test data" + i, Charset.forName("UTF-8")); final String artifactData = "some test data" + i;
artifacts.add(artifactManagement.create(stubInputStream, moduleId, "filename" + i, false)); final InputStream stubInputStream = IOUtils.toInputStream(artifactData, Charset.forName("UTF-8"));
artifacts.add(
artifactManagement.create(stubInputStream, moduleId, "filename" + i, false, artifactData.length()));
} }

View File

@@ -114,6 +114,58 @@ public class HawkbitSecurityProperties {
*/ */
private int maxMessagesPerActionStatus = 50; private int maxMessagesPerActionStatus = 50;
/**
* Maximum number of meta data entries per software module
*/
private int maxMetaDataEntriesPerSoftwareModule = 100;
/**
* Maximum number of meta data entries per distribution set
*/
private int maxMetaDataEntriesPerDistributionSet = 100;
/**
* Maximum number of software modules per distribution set
*/
private int maxSoftwareModulesPerDistributionSet = 100;
/**
* Maximum number of software modules per distribution set
*/
private int maxSoftwareModuleTypesPerDistributionSetType = 50;
/**
* Maximum number of artifacts per software module
*/
private int maxArtifactsPerSoftwareModule = 50;
/**
* Maximum number of targets per rollout group
*/
private int maxTargetsPerRolloutGroup = 20000;
/**
* Maximum number of targets per rollout group
*/
private int maxActionsPerTarget = 500;
/**
* Maximum number of targets for a manual distribution set assignment.
* Must be greater than 1000.
*/
private int maxTargetsPerManualAssignment = 5000;
/**
* Maximum number of targets for an automatic distribution set
* assignment
*/
private int maxTargetsPerAutoAssignment = 5000;
/**
* Maximum size of artifacts in bytes.
*/
private long maxArtifactSize = 1_000_000_000;
private final Filter filter = new Filter(); private final Filter filter = new Filter();
private final Filter uiFilter = new Filter(); private final Filter uiFilter = new Filter();
@@ -157,6 +209,87 @@ public class HawkbitSecurityProperties {
this.maxRolloutGroupsPerRollout = maxRolloutGroupsPerRollout; this.maxRolloutGroupsPerRollout = maxRolloutGroupsPerRollout;
} }
public int getMaxMetaDataEntriesPerSoftwareModule() {
return maxMetaDataEntriesPerSoftwareModule;
}
public void setMaxMetaDataEntriesPerSoftwareModule(final int maxMetaDataEntriesPerSoftwareModule) {
this.maxMetaDataEntriesPerSoftwareModule = maxMetaDataEntriesPerSoftwareModule;
}
public int getMaxMetaDataEntriesPerDistributionSet() {
return maxMetaDataEntriesPerDistributionSet;
}
public void setMaxMetaDataEntriesPerDistributionSet(final int maxMetaDataEntriesPerDistributionSet) {
this.maxMetaDataEntriesPerDistributionSet = maxMetaDataEntriesPerDistributionSet;
}
public int getMaxSoftwareModulesPerDistributionSet() {
return maxSoftwareModulesPerDistributionSet;
}
public void setMaxSoftwareModulesPerDistributionSet(final int maxSoftwareModulesPerDistributionSet) {
this.maxSoftwareModulesPerDistributionSet = maxSoftwareModulesPerDistributionSet;
}
public int getMaxSoftwareModuleTypesPerDistributionSetType() {
return maxSoftwareModuleTypesPerDistributionSetType;
}
public void setMaxSoftwareModuleTypesPerDistributionSetType(
final int maxSoftwareModuleTypesPerDistributionSetType) {
this.maxSoftwareModuleTypesPerDistributionSetType = maxSoftwareModuleTypesPerDistributionSetType;
}
public int getMaxArtifactsPerSoftwareModule() {
return maxArtifactsPerSoftwareModule;
}
public void setMaxArtifactsPerSoftwareModule(final int maxArtifactsPerSoftwareModule) {
this.maxArtifactsPerSoftwareModule = maxArtifactsPerSoftwareModule;
}
public int getMaxTargetsPerRolloutGroup() {
return maxTargetsPerRolloutGroup;
}
public void setMaxTargetsPerRolloutGroup(final int maxTargetsPerRolloutGroup) {
this.maxTargetsPerRolloutGroup = maxTargetsPerRolloutGroup;
}
public int getMaxActionsPerTarget() {
return maxActionsPerTarget;
}
public void setMaxActionsPerTarget(final int maxActionsPerTarget) {
this.maxActionsPerTarget = maxActionsPerTarget;
}
public int getMaxTargetsPerManualAssignment() {
return maxTargetsPerManualAssignment;
}
public void setMaxTargetsPerManualAssignment(final int maxTargetsPerManualAssignment) {
this.maxTargetsPerManualAssignment = maxTargetsPerManualAssignment;
}
public int getMaxTargetsPerAutoAssignment() {
return maxTargetsPerAutoAssignment;
}
public void setMaxTargetsPerAutoAssignment(final int maxTargetsPerAutoAssignment) {
this.maxTargetsPerAutoAssignment = maxTargetsPerAutoAssignment;
}
public void setMaxArtifactSize(final long maxArtifactSize) {
this.maxArtifactSize = maxArtifactSize;
}
public long getMaxArtifactSize() {
return maxArtifactSize;
}
/** /**
* Configuration for hawkBits DOS prevention filter. This is usually an * Configuration for hawkBits DOS prevention filter. This is usually an
* infrastructure topic (e.g. Web Application Firewall (WAF)) but might * infrastructure topic (e.g. Web Application Firewall (WAF)) but might
@@ -179,13 +312,13 @@ public class HawkbitSecurityProperties {
* # Maximum number of allowed REST read/GET requests per second per * # Maximum number of allowed REST read/GET requests per second per
* client IP. * client IP.
*/ */
int maxRead = 200; private int maxRead = 200;
/** /**
* Maximum number of allowed REST write/(PUT/POST/etc.) requests per * Maximum number of allowed REST write/(PUT/POST/etc.) requests per
* second per client IP. * second per client IP.
*/ */
int maxWrite = 50; private int maxWrite = 50;
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
@@ -220,5 +353,6 @@ public class HawkbitSecurityProperties {
} }
} }
} }
} }

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException; import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState;
@@ -591,7 +592,8 @@ public class UploadConfirmationWindow implements Button.ClickListener {
customFile.getBaseSoftwareModuleName(), customFile.getBaseSoftwareModuleVersion()); customFile.getBaseSoftwareModuleName(), customFile.getBaseSoftwareModuleVersion());
if (customFile.getFileName().equals(fileName) if (customFile.getFileName().equals(fileName)
&& baseSwModuleNameVersion.equals(baseSoftwareModuleNameVersion)) { && baseSwModuleNameVersion.equals(baseSoftwareModuleNameVersion)) {
createArtifact(itemId, customFile.getFilePath(), artifactManagement, bSoftwareModule); createArtifact(itemId, customFile.getFilePath(), customFile.getFileSize(), artifactManagement,
bSoftwareModule);
} }
} }
refreshArtifactDetailsLayout = checkIfArtifactDetailsDisplayed(bSoftwareModule.getId()); refreshArtifactDetailsLayout = checkIfArtifactDetailsDisplayed(bSoftwareModule.getId());
@@ -618,8 +620,8 @@ public class UploadConfirmationWindow implements Button.ClickListener {
currentUploadResultWindow = null; currentUploadResultWindow = null;
} }
private void createArtifact(final String itemId, final String filePath, final ArtifactManagement artifactManagement, private void createArtifact(final String itemId, final String filePath, final long fileSize,
final SoftwareModule baseSw) { final ArtifactManagement artifactManagement, final SoftwareModule baseSw) {
final File newFile = new File(filePath); final File newFile = new File(filePath);
final Item item = tableContainer.getItem(itemId); final Item item = tableContainer.getItem(itemId);
@@ -638,11 +640,11 @@ public class UploadConfirmationWindow implements Button.ClickListener {
try (FileInputStream fis = new FileInputStream(newFile)) { try (FileInputStream fis = new FileInputStream(newFile)) {
artifactManagement.create(fis, baseSw.getId(), providedFileName, md5Checksum, sha1Checksum, true, artifactManagement.create(fis, baseSw.getId(), providedFileName, md5Checksum, sha1Checksum, true,
customFile.getMimeType()); customFile.getMimeType(), fileSize);
saveUploadStatus(providedFileName, swModuleNameVersion, SPUILabelDefinitions.SUCCESS, ""); saveUploadStatus(providedFileName, swModuleNameVersion, SPUILabelDefinitions.SUCCESS, "");
} catch (final ArtifactUploadFailedException | InvalidSHA1HashException | InvalidMD5HashException } catch (final ArtifactUploadFailedException | InvalidSHA1HashException | InvalidMD5HashException
| FileNotFoundException e) { | FileNotFoundException | QuotaExceededException e) {
saveUploadStatus(providedFileName, swModuleNameVersion, SPUILabelDefinitions.FAILED, e.getMessage()); saveUploadStatus(providedFileName, swModuleNameVersion, SPUILabelDefinitions.FAILED, e.getMessage());
LOG.error(ARTIFACT_UPLOAD_EXCEPTION, e); LOG.error(ARTIFACT_UPLOAD_EXCEPTION, e);

View File

@@ -91,14 +91,17 @@ import com.vaadin.ui.themes.ValoTheme;
/** /**
* Rollout add or update popup layout. * Rollout add or update popup layout.
*/ */
@SuppressWarnings({ "squid:MaximumInheritanceDepth", "squid:S2160" })
public class AddUpdateRolloutWindowLayout extends GridLayout { public class AddUpdateRolloutWindowLayout extends GridLayout {
private static final long serialVersionUID = 2999293468801479916L; private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LoggerFactory.getLogger(AddUpdateRolloutWindowLayout.class); private static final Logger LOGGER = LoggerFactory.getLogger(AddUpdateRolloutWindowLayout.class);
private static final String MESSAGE_ROLLOUT_FIELD_VALUE_RANGE = "message.rollout.field.value.range"; private static final String MESSAGE_ROLLOUT_FIELD_VALUE_RANGE = "message.rollout.field.value.range";
private static final String MESSAGE_ROLLOUT_MAX_GROUP_SIZE_EXCEEDED = "message.rollout.max.group.size.exceeded";
private static final String MESSAGE_ROLLOUT_FILTER_TARGET_EXISTS = "message.rollout.filter.target.exists"; private static final String MESSAGE_ROLLOUT_FILTER_TARGET_EXISTS = "message.rollout.filter.target.exists";
private static final String MESSAGE_ENTER_NUMBER = "message.enter.number"; private static final String MESSAGE_ENTER_NUMBER = "message.enter.number";
@@ -167,6 +170,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private final NullValidator nullValidator = new NullValidator(null, false); private final NullValidator nullValidator = new NullValidator(null, false);
@SuppressWarnings("squid:S00107")
AddUpdateRolloutWindowLayout(final RolloutManagement rolloutManagement, final TargetManagement targetManagement, AddUpdateRolloutWindowLayout(final RolloutManagement rolloutManagement, final TargetManagement targetManagement,
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory, final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
final VaadinMessageSource i18n, final UIEventBus eventBus, final VaadinMessageSource i18n, final UIEventBus eventBus,
@@ -339,7 +343,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private int getErrorThresholdPercentage(final int amountGroup) { private int getErrorThresholdPercentage(final int amountGroup) {
int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue()); int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue());
if (errorThresholdOptionGroup.getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) { if (errorThresholdOptionGroup.getValue().equals(ERROR_THRESHOLD_OPTIONS.COUNT.getValue())) {
final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup); final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup);
final int erroThresoldCount = Integer.parseInt(errorThreshold.getValue()); final int erroThresoldCount = Integer.parseInt(errorThreshold.getValue());
errorThresoldPercent = (int) Math.ceil(((float) erroThresoldCount / (float) groupSize) * 100); errorThresoldPercent = (int) Math.ceil(((float) erroThresoldCount / (float) groupSize) * 100);
@@ -626,7 +630,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private OptionGroup createErrorThresholdOptionGroup() { private OptionGroup createErrorThresholdOptionGroup() {
final OptionGroup errorThresoldOptions = new OptionGroup(); final OptionGroup errorThresoldOptions = new OptionGroup();
for (final ERRORTHRESOLDOPTIONS option : ERRORTHRESOLDOPTIONS.values()) { for (final ERROR_THRESHOLD_OPTIONS option : ERROR_THRESHOLD_OPTIONS.values()) {
errorThresoldOptions.addItem(option.getValue()); errorThresoldOptions.addItem(option.getValue());
} }
errorThresoldOptions.setId(UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_OPTION_ID); errorThresoldOptions.setId(UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_OPTION_ID);
@@ -640,8 +644,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private void listenerOnErrorThresoldOptionChange(final ValueChangeEvent event) { private void listenerOnErrorThresoldOptionChange(final ValueChangeEvent event) {
errorThreshold.clear(); errorThreshold.clear();
errorThreshold.removeAllValidators(); errorThreshold.removeAllValidators();
if (event.getProperty().getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) { if (event.getProperty().getValue().equals(ERROR_THRESHOLD_OPTIONS.COUNT.getValue())) {
errorThreshold.addValidator(new ErrorThresoldOptionValidator()); errorThreshold.addValidator(new ErrorThresholdOptionValidator());
} else { } else {
errorThreshold.addValidator(new ThresholdFieldValidator()); errorThreshold.addValidator(new ThresholdFieldValidator());
} }
@@ -732,6 +736,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
groupsLegendLayout.populateTotalTargets(totalTargetsCount); groupsLegendLayout.populateTotalTargets(totalTargetsCount);
defineGroupsLayout.setTargetFilter(filterQueryString); defineGroupsLayout.setTargetFilter(filterQueryString);
} }
noOfGroups.markAsDirty();
onGroupNumberChange(event); onGroupNumberChange(event);
} }
@@ -771,7 +776,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
} }
private void setDefaultSaveStartGroupOption() { private void setDefaultSaveStartGroupOption() {
errorThresholdOptionGroup.setValue(ERRORTHRESOLDOPTIONS.PERCENT.getValue()); errorThresholdOptionGroup.setValue(ERROR_THRESHOLD_OPTIONS.PERCENT.getValue());
} }
private static TextArea createDescription() { private static TextArea createDescription() {
@@ -802,6 +807,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
final TextField noOfGroupsField = createIntegerTextField("prompt.number.of.groups", final TextField noOfGroupsField = createIntegerTextField("prompt.number.of.groups",
UIComponentIdProvider.ROLLOUT_NO_OF_GROUPS_ID); UIComponentIdProvider.ROLLOUT_NO_OF_GROUPS_ID);
noOfGroupsField.addValidator(new GroupNumberValidator()); noOfGroupsField.addValidator(new GroupNumberValidator());
noOfGroupsField.addValidator(new GroupSizeValidator());
noOfGroupsField.setMaxLength(3); noOfGroupsField.setMaxLength(3);
noOfGroupsField.addValueChangeListener(this::onGroupNumberChange); noOfGroupsField.addValueChangeListener(this::onGroupNumberChange);
return noOfGroupsField; return noOfGroupsField;
@@ -850,8 +856,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
return rolloutNameField; return rolloutNameField;
} }
class ErrorThresoldOptionValidator implements Validator { class ErrorThresholdOptionValidator implements Validator {
private static final long serialVersionUID = 9049939751976326550L; private static final long serialVersionUID = 1L;
@Override @Override
public void validate(final Object value) { public void validate(final Object value) {
@@ -879,6 +885,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
} }
class TargetExistsValidator implements Validator { class TargetExistsValidator implements Validator {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
@@ -891,7 +898,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
} }
class ThresholdFieldValidator implements Validator { class ThresholdFieldValidator implements Validator {
private static final long serialVersionUID = 9049939751976326550L; private static final long serialVersionUID = 1L;
@Override @Override
public void validate(final Object value) { public void validate(final Object value) {
@@ -903,13 +910,29 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
} }
class GroupNumberValidator implements Validator { class GroupNumberValidator implements Validator {
private static final long serialVersionUID = 9043919751971326521L; private static final long serialVersionUID = 1L;
@Override @Override
public void validate(final Object value) { public void validate(final Object value) {
if (value != null) { if (value != null) {
new IntegerRangeValidator(i18n.getMessage(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 1, 500), 1, 500) final int maxGroups = quotaManagement.getMaxRolloutGroupsPerRollout();
.validate(Integer.valueOf(value.toString())); new IntegerRangeValidator(i18n.getMessage(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 1, maxGroups), 1,
maxGroups).validate(Integer.valueOf(value.toString()));
}
}
}
class GroupSizeValidator implements Validator {
private static final long serialVersionUID = 0L;
@Override
public void validate(final Object value) {
if (value != null && totalTargetsCount != null) {
final int maxGroupSize = quotaManagement.getMaxTargetsPerRolloutGroup();
if (getGroupSize() > maxGroupSize) {
final String msg = i18n.getMessage(MESSAGE_ROLLOUT_MAX_GROUP_SIZE_EXCEEDED, maxGroupSize);
throw new InvalidValueException(msg);
}
} }
} }
} }
@@ -1020,12 +1043,15 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
} }
} }
private enum ERRORTHRESOLDOPTIONS { private enum ERROR_THRESHOLD_OPTIONS {
PERCENT("%"), COUNT("Count");
String value; PERCENT("%"),
ERRORTHRESOLDOPTIONS(final String val) { COUNT("Count");
private final String value;
ERROR_THRESHOLD_OPTIONS(final String val) {
value = val; value = val;
} }

View File

@@ -67,7 +67,9 @@ import com.vaadin.ui.UI;
*/ */
public class DefineGroupsLayout extends GridLayout { public class DefineGroupsLayout extends GridLayout {
private static final long serialVersionUID = 2939193468001472916L; private static final long serialVersionUID = 1L;
private static final String MESSAGE_ROLLOUT_MAX_GROUP_SIZE_EXCEEDED = "message.rollout.max.group.size.exceeded.advanced";
private final VaadinMessageSource i18n; private final VaadinMessageSource i18n;
@@ -267,7 +269,7 @@ public class DefineGroupsLayout extends GridLayout {
validateRemainingTargets(); validateRemainingTargets();
} else { } else {
resetRemainingTargetsError(); resetErrors();
setValidationStatus(ValidationStatus.INVALID); setValidationStatus(ValidationStatus.INVALID);
} }
} }
@@ -279,12 +281,12 @@ public class DefineGroupsLayout extends GridLayout {
} }
} }
private void resetRemainingTargetsError() { private void resetErrors() {
groupRows.forEach(GroupRow::hideLastGroupError); groupRows.forEach(GroupRow::resetError);
} }
private void validateRemainingTargets() { private void validateRemainingTargets() {
resetRemainingTargetsError(); resetErrors();
if (targetFilter == null) { if (targetFilter == null) {
return; return;
} }
@@ -318,16 +320,33 @@ public class DefineGroupsLayout extends GridLayout {
} }
groupsValidation = validation; groupsValidation = validation;
final GroupRow lastRow = groupRows.get(groupRows.size() - 1); final int lastIdx = groupRows.size() - 1;
final GroupRow lastRow = groupRows.get(lastIdx);
if (groupsValidation != null && groupsValidation.isValid() && validationStatus != ValidationStatus.INVALID) { if (groupsValidation != null && groupsValidation.isValid() && validationStatus != ValidationStatus.INVALID) {
lastRow.hideLastGroupError(); lastRow.resetError();
setValidationStatus(ValidationStatus.VALID); setValidationStatus(ValidationStatus.VALID);
} else { } else {
lastRow.markWithLastGroupError(); lastRow.setError(i18n.getMessage("message.rollout.remaining.targets.error"));
setValidationStatus(ValidationStatus.INVALID); setValidationStatus(ValidationStatus.INVALID);
} }
// validate the single groups
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
final boolean hasRemainingTargetsError = validationStatus == ValidationStatus.INVALID;
for (int i = 0; i < groupRows.size(); ++i) {
final GroupRow row = groupRows.get(i);
// do not mask the 'remaining targets' error
if (hasRemainingTargetsError && row.equals(lastRow)) {
continue;
}
row.resetError();
final Long count = groupsValidation.getTargetsPerGroup().get(i);
if (count != null && count > maxTargets) {
row.setError(i18n.getMessage(MESSAGE_ROLLOUT_MAX_GROUP_SIZE_EXCEEDED, maxTargets));
setValidationStatus(ValidationStatus.INVALID);
}
}
} }
private List<RolloutGroupCreate> getGroupsFromRows() { private List<RolloutGroupCreate> getGroupsFromRows() {
@@ -389,13 +408,9 @@ public class DefineGroupsLayout extends GridLayout {
groupName = createTextField("textfield.name", UIComponentIdProvider.ROLLOUT_GROUP_LIST_GRID_ID); groupName = createTextField("textfield.name", UIComponentIdProvider.ROLLOUT_GROUP_LIST_GRID_ID);
groupName.setValue(i18n.getMessage("textfield.rollout.group.default.name", groupsCount)); groupName.setValue(i18n.getMessage("textfield.rollout.group.default.name", groupsCount));
groupName.setStyleName("rollout-group-name"); groupName.setStyleName("rollout-group-name");
groupName.addValueChangeListener( groupName.addValueChangeListener(event -> valueChanged());
event -> valueChanged()); targetFilterQueryCombo = createTargetFilterQueryCombo();
targetFilterQueryCombo =
createTargetFilterQueryCombo();
populateTargetFilterQuery(); populateTargetFilterQuery();
targetFilterQueryCombo.addValueChangeListener(event -> valueChanged()); targetFilterQueryCombo.addValueChangeListener(event -> valueChanged());
@@ -617,18 +632,16 @@ public class DefineGroupsLayout extends GridLayout {
&& triggerThreshold.isValid() && errorThreshold.isValid(); && triggerThreshold.isValid() && errorThreshold.isValid();
} }
private void markWithLastGroupError() { private void setError(final String error) {
targetPercentage targetPercentage.setComponentError(new UserError(error));
.setComponentError(new UserError(i18n.getMessage("message.rollout.remaining.targets.error")));
} }
/** /**
* Hides an error of the row * Hides an error of the row
*/ */
private void hideLastGroupError() { private void resetError() {
targetPercentage.setComponentError(null); targetPercentage.setComponentError(null);
} }
} }
} }

View File

@@ -18,6 +18,7 @@ import org.springframework.data.domain.Sort.Direction;
* Class to provide the unchanged constants. * Class to provide the unchanged constants.
*/ */
public final class SPUIDefinitions { public final class SPUIDefinitions {
/** /**
* Available locales. * Available locales.
*/ */
@@ -27,10 +28,12 @@ public final class SPUIDefinitions {
* Default locale. * Default locale.
*/ */
public static final String DEFAULT_LOCALE = "en"; public static final String DEFAULT_LOCALE = "en";
/** /**
* Locale cookie name. * Locale cookie name.
*/ */
public static final String COOKIE_NAME = "BOSCHSI_UII_LOCALE"; public static final String COOKIE_NAME = "BOSCHSI_UII_LOCALE";
/** /**
* Lazy query container page size. * Lazy query container page size.
*/ */
@@ -166,10 +169,12 @@ public final class SPUIDefinitions {
* New Software Module desc field id. * New Software Module desc field id.
*/ */
public static final String NEW_SOFTWARE_TYPE_DESC = "software.type.add.desc"; public static final String NEW_SOFTWARE_TYPE_DESC = "software.type.add.desc";
/** /**
* Hide filter by dist type layout button. * Hide filter by dist type layout button.
*/ */
public static final String HIDE_FILTER_DIST_TYPE = "hide.filter.dist.type.layout"; public static final String HIDE_FILTER_DIST_TYPE = "hide.filter.dist.type.layout";
/** /**
* New Distribution Type distribution field id. * New Distribution Type distribution field id.
*/ */
@@ -184,18 +189,22 @@ public final class SPUIDefinitions {
* New Software Module key field id. * New Software Module key field id.
*/ */
public static final String NEW_SOFTWARE_TYPE_KEY = "software.type.add.key"; public static final String NEW_SOFTWARE_TYPE_KEY = "software.type.add.key";
/** /**
* New Target tag desc field id. * New Target tag desc field id.
*/ */
public static final String NEW_TARGET_TAG_DESC = "target.tag.add.desc"; public static final String NEW_TARGET_TAG_DESC = "target.tag.add.desc";
/** /**
* New distribution Type set tag add icon id. * New distribution Type set tag add icon id.
*/ */
public static final String ADD_DISTRIBUTION_TYPE_TAG = "distribution.type.tag.add"; public static final String ADD_DISTRIBUTION_TYPE_TAG = "distribution.type.tag.add";
/** /**
* New software module set type add icon id. * New software module set type add icon id.
*/ */
public static final String ADD_SOFTWARE_MODULE_TYPE = "softwaremodule.type.add"; public static final String ADD_SOFTWARE_MODULE_TYPE = "softwaremodule.type.add";
/** /**
* No data. * No data.
*/ */
@@ -220,10 +229,12 @@ public final class SPUIDefinitions {
* Filter by tag key. * Filter by tag key.
*/ */
public static final String FILTER_BY_TAG = "FilterByTag"; public static final String FILTER_BY_TAG = "FilterByTag";
/** /**
* Filter by no tag button. * Filter by no tag button.
*/ */
public static final String FILTER_BY_NO_TAG = "FilterByNoTag"; public static final String FILTER_BY_NO_TAG = "FilterByNoTag";
/** /**
* Filter by target filter query. * Filter by target filter query.
*/ */
@@ -233,34 +244,42 @@ public final class SPUIDefinitions {
* Filter by distribution key. * Filter by distribution key.
*/ */
public static final String FILTER_BY_DISTRIBUTION = "FilterByDistribution"; public static final String FILTER_BY_DISTRIBUTION = "FilterByDistribution";
/** /**
* Filter by distributionSet Type. * Filter by distributionSet Type.
*/ */
public static final String FILTER_BY_DISTRIBUTION_SET_TYPE = "FilterByDistributionSetType"; public static final String FILTER_BY_DISTRIBUTION_SET_TYPE = "FilterByDistributionSetType";
/** /**
* Order by pinnedTarget. * Order by pinnedTarget.
*/ */
public static final String ORDER_BY_PINNED_TARGET = "OrderByPinnedTarget"; public static final String ORDER_BY_PINNED_TARGET = "OrderByPinnedTarget";
/** /**
* Filter by text key. * Filter by text key.
*/ */
public static final String FILTER_BY_TEXT = "FilterByText"; public static final String FILTER_BY_TEXT = "FilterByText";
/** /**
* Text field style. * Text field style.
*/ */
public static final String TEXT_STYLE = "text-style"; public static final String TEXT_STYLE = "text-style";
/** /**
* Show actions for a target. * Show actions for a target.
*/ */
public static final String ACTIONS_BY_TARGET = "ActionsByTarget"; public static final String ACTIONS_BY_TARGET = "ActionsByTarget";
/** /**
* Show action-states for a action. * Show action-states for a action.
*/ */
public static final String ACTIONSTATES_BY_ACTION = "ActionStatesByAction"; public static final String ACTIONSTATES_BY_ACTION = "ActionStatesByAction";
/** /**
* Show messages for a action-status. * Show messages for a action-status.
*/ */
public static final String MESSAGES_BY_ACTIONSTATUS = "MessagesByActionStatus"; public static final String MESSAGES_BY_ACTIONSTATUS = "MessagesByActionStatus";
/** /**
* Key for no-message MessageProxy. * Key for no-message MessageProxy.
*/ */
@@ -290,22 +309,27 @@ public final class SPUIDefinitions {
* Target and distribution column width in save popup window. * Target and distribution column width in save popup window.
*/ */
public static final float TARGET_DISTRIBUTION_COLUMN_WIDTH = 2.8F; public static final float TARGET_DISTRIBUTION_COLUMN_WIDTH = 2.8F;
/** /**
* Discard column width in save window popup. * Discard column width in save window popup.
*/ */
public static final float DISCARD_COLUMN_WIDTH = 1.2F; public static final float DISCARD_COLUMN_WIDTH = 1.2F;
/** /**
* Target tag name. * Target tag name.
*/ */
public static final String TAG_NAME = "target-tag-name"; public static final String TAG_NAME = "target-tag-name";
/** /**
* Target tag desc. * Target tag desc.
*/ */
public static final String TAG_DESC = "target-tag-desc"; public static final String TAG_DESC = "target-tag-desc";
/** /**
* Software type name. * Software type name.
*/ */
public static final String TYPE_NAME = "type-name"; public static final String TYPE_NAME = "type-name";
/** /**
* DistributionSet type name. * DistributionSet type name.
*/ */
@@ -320,6 +344,7 @@ public final class SPUIDefinitions {
* DistributionSet type key. * DistributionSet type key.
*/ */
public static final String DIST_SET_TYPE_KEY = "dist-set-type-key"; public static final String DIST_SET_TYPE_KEY = "dist-set-type-key";
/** /**
* Software type desc. * Software type desc.
*/ */
@@ -334,6 +359,7 @@ public final class SPUIDefinitions {
* Tag combo style. * Tag combo style.
*/ */
public static final String FILTER_TYPE_COMBO_STYLE = "filter-combo-specific-style"; public static final String FILTER_TYPE_COMBO_STYLE = "filter-combo-specific-style";
/** /**
* Color label style. * Color label style.
*/ */
@@ -351,6 +377,7 @@ public final class SPUIDefinitions {
* Target tag button id prefix. * Target tag button id prefix.
*/ */
public static final String TARGET_TAG_ID_PREFIXS = "target.tag."; public static final String TARGET_TAG_ID_PREFIXS = "target.tag.";
/** /**
* Distribution tag button id prefix. * Distribution tag button id prefix.
*/ */
@@ -359,7 +386,7 @@ public final class SPUIDefinitions {
/** /**
* Space. * Space.
*/ */
static final String SPACE = "&nbsp;"; public static final String SPACE = "&nbsp;";
/** /**
* Distribution tag button id prefix. * Distribution tag button id prefix.
@@ -370,27 +397,33 @@ public final class SPUIDefinitions {
* DistributionSet Type tag button id prefix. * DistributionSet Type tag button id prefix.
*/ */
public static final String DISTRIBUTION_SET_TYPE_ID_PREFIXS = "dist.set.type."; public static final String DISTRIBUTION_SET_TYPE_ID_PREFIXS = "dist.set.type.";
/** /**
* Target last query date format . * Target last query date format .
*/ */
public static final String LAST_QUERY_DATE_FORMAT = "EEE MMM d HH:mm:ss z yyyy"; public static final String LAST_QUERY_DATE_FORMAT = "EEE MMM d HH:mm:ss z yyyy";
/** /**
* Target last query date format . * Target last query date format .
*/ */
public static final String LAST_QUERY_DATE_FORMAT_SHORT = "MMM d HH:mm z ''yy"; public static final String LAST_QUERY_DATE_FORMAT_SHORT = "MMM d HH:mm z ''yy";
/** /**
* Item Id used in drop comparisons. * Item Id used in drop comparisons.
*/ */
public static final String ITEMID = "itemId"; public static final String ITEMID = "itemId";
/** /**
* Expand action history. * Expand action history.
*/ */
public static final String EXPAND_ACTION_HISTORY = "expand.action.history"; public static final String EXPAND_ACTION_HISTORY = "expand.action.history";
/** /**
* Filter by distribution key. * Filter by distribution key.
*/ */
public static final String ORDER_BY_DISTRIBUTION = "OrderByDistribution"; public static final String ORDER_BY_DISTRIBUTION = "OrderByDistribution";
/** Artifact upload related entries - start **/ /** Artifact upload related entries - start **/
/** /**
* Artifact details by Base software module id. * Artifact details by Base software module id.
@@ -408,15 +441,15 @@ public final class SPUIDefinitions {
* automatically and also horizontal scroll bars get displayed. Used for * automatically and also horizontal scroll bars get displayed. Used for
* Responsive UI. * Responsive UI.
*/ */
static final int REQ_MIN_UPLOAD_BROWSER_WIDTH = 1250; public static final int REQ_MIN_UPLOAD_BROWSER_WIDTH = 1250;
public static final int MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH = 1000; public static final int MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH = 1000;
public static final int MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT = 310; public static final int MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT = 310;
static final int MAX_UPLOAD_CONFIRMATION_POPUP_WIDTH = 1050; public static final int MAX_UPLOAD_CONFIRMATION_POPUP_WIDTH = 1050;
static final int MAX_UPLOAD_CONFIRMATION_POPUP_HEIGHT = 360; public static final int MAX_UPLOAD_CONFIRMATION_POPUP_HEIGHT = 360;
/** Artifact upload related entries - end. **/ /** Artifact upload related entries - end. **/

View File

@@ -566,6 +566,8 @@ message.correct.invalid.value = Please correct invalid values
message.enter.number = Please enter number message.enter.number = Please enter number
message.rollout.field.value.range = Value should be in range {0} to {1} message.rollout.field.value.range = Value should be in range {0} to {1}
message.rollout.filter.target.exists = The selected target filter does not match any existing target message.rollout.filter.target.exists = The selected target filter does not match any existing target
message.rollout.max.group.size.exceeded = The maximum group size of {0} targets is exceeded. Please increase the number of groups or select a different target filter
message.rollout.max.group.size.exceeded.advanced = The maximum size of {0} targets is exceeded for this group. Please re-distribute the targets across the groups and create further groups if needed
message.rollout.started = Rollout {0} started successfully message.rollout.started = Rollout {0} started successfully
message.rollout.paused = Rollout {0} paused successfully message.rollout.paused = Rollout {0} paused successfully
message.rollout.resumed = Rollout {0} resumed successfully message.rollout.resumed = Rollout {0} resumed successfully