ArtifactRepository tenant aware. (#539)

* ArtifactRepository tenant aware.

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

* No need to have this protected. Updated event to boot > 1.3

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

* Remove conditional.

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

* Remove Debug log.

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

* Cleanup

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

* Missing validation and readability.

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

* Fix test after change.

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

* Fix computation is DosFilter

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

* Fix session state on RESTful APIs.

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

* Performance improvement controllermanagement

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

* Added cross tenant test.

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

* Typos.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2017-06-14 19:07:52 +02:00
committed by GitHub
parent 787042ce85
commit 8d17d21259
27 changed files with 340 additions and 160 deletions

View File

@@ -16,8 +16,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@@ -26,7 +26,7 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@ConditionalOnProperty(prefix = "hawkbit.device.simulator", name = "autostart", matchIfMissing = true) @ConditionalOnProperty(prefix = "hawkbit.device.simulator", name = "autostart", matchIfMissing = true)
public class SimulatorStartup implements ApplicationListener<ContextRefreshedEvent> { public class SimulatorStartup implements ApplicationListener<ApplicationReadyEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SimulatorStartup.class); private static final Logger LOGGER = LoggerFactory.getLogger(SimulatorStartup.class);
@Autowired @Autowired
@@ -42,7 +42,7 @@ public class SimulatorStartup implements ApplicationListener<ContextRefreshedEve
private AmqpProperties amqpProperties; private AmqpProperties amqpProperties;
@Override @Override
public void onApplicationEvent(final ContextRefreshedEvent event) { public void onApplicationEvent(final ApplicationReadyEvent event) {
simulationProperties.getAutostarts().forEach(autostart -> { simulationProperties.getAutostarts().forEach(autostart -> {
for (int i = 0; i < autostart.getAmount(); i++) { for (int i = 0; i < autostart.getAmount(); i++) {
final String deviceId = autostart.getName() + i; final String deviceId = autostart.getName() + i;

View File

@@ -54,6 +54,11 @@
<artifactId>allure-junit-adaptor</artifactId> <artifactId>allure-junit-adaptor</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>test</scope>
</dependency>
<!-- MongoDB --> <!-- MongoDB -->
<dependency> <dependency>
<groupId>de.flapdoodle.embed</groupId> <groupId>de.flapdoodle.embed</groupId>

View File

@@ -24,13 +24,13 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsOperations; import org.springframework.data.mongodb.gridfs.GridFsOperations;
import org.springframework.validation.annotation.Validated;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
@@ -44,6 +44,7 @@ import com.mongodb.gridfs.GridFSFile;
* The file management which looks up all the file in the file tore. * The file management which looks up all the file in the file tore.
* *
*/ */
@Validated
public class MongoDBArtifactStore implements ArtifactRepository { public class MongoDBArtifactStore implements ArtifactRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBArtifactStore.class); private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBArtifactStore.class);
@@ -56,9 +57,14 @@ public class MongoDBArtifactStore implements ArtifactRepository {
private static final String FILENAME = "filename"; private static final String FILENAME = "filename";
/** /**
* The mongoDB field which automatically calculated by the mongoDB. * The mongoDB field which holds the tenant of the file to download.
*/ */
private static final String MD5 = "md5"; private static final String TENANT = "tenant";
/**
* Query by {@link TenantAware} field.
*/
private static final String TENANT_QUERY = "metadata." + TENANT;
/** /**
* The mongoDB field which holds the SHA1 hash, stored in the meta data * The mongoDB field which holds the SHA1 hash, stored in the meta data
@@ -68,10 +74,11 @@ public class MongoDBArtifactStore implements ArtifactRepository {
private static final String ID = "_id"; private static final String ID = "_id";
@Autowired private final GridFsOperations gridFs;
private GridFsOperations gridFs;
MongoTemplate mongoTemplate; MongoDBArtifactStore(final GridFsOperations gridFs) {
this.gridFs = gridFs;
}
/** /**
* Retrieves a {@link GridFSDBFile} from the store by it's SHA1 hash. * Retrieves a {@link GridFSDBFile} from the store by it's SHA1 hash.
@@ -82,36 +89,28 @@ public class MongoDBArtifactStore implements ArtifactRepository {
* @return The DbArtifact object or {@code null} if no file exists. * @return The DbArtifact object or {@code null} if no file exists.
*/ */
@Override @Override
public DbArtifact getArtifactBySha1(final String sha1Hash) { public DbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) {
return map(gridFs.findOne(new Query().addCriteria(Criteria.where(FILENAME).is(sha1Hash))));
}
/** return map(gridFs.findOne(new Query()
* Retrieves a {@link GridFSDBFile} from the store by it's MD5 hash. .addCriteria(Criteria.where(FILENAME).is(sha1Hash).and(TENANT_QUERY).is(sanitizeTenant(tenant)))));
*
* @param md5Hash
* the md5-hash of the file to lookup.
* @return The gridfs file object or {@code null} if no file exists.
*/
public DbArtifact getArtifactByMd5(final String md5Hash) {
return map(gridFs.findOne(new Query().addCriteria(Criteria.where(MD5).is(md5Hash))));
} }
@Override @Override
public DbArtifact store(final InputStream content, final String filename, final String contentType) { public DbArtifact store(final String tenant, final InputStream content, final String filename,
return store(content, filename, contentType, null); final String contentType) {
return store(tenant, content, filename, contentType, null);
} }
@Override @Override
public DbArtifact store(final InputStream content, final String filename, final String contentType, public DbArtifact store(final String tenant, final InputStream content, final String filename,
final DbArtifactHash hash) { final String contentType, final DbArtifactHash hash) {
File tempFile = null; File tempFile = null;
try { try {
LOGGER.debug("storing file {} of content {}", filename, contentType); LOGGER.debug("storing file {} of content {}", filename, contentType);
tempFile = File.createTempFile("uploadFile", null); tempFile = File.createTempFile("uploadFile", null);
try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tempFile))) { try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tempFile))) {
try (BufferedInputStream bis = new BufferedInputStream(content)) { try (BufferedInputStream bis = new BufferedInputStream(content)) {
return store(bis, contentType, bos, tempFile, hash); return store(tenant, bis, contentType, bos, tempFile, hash);
} }
} }
} catch (final IOException | MongoException e1) { } catch (final IOException | MongoException e1) {
@@ -123,10 +122,15 @@ public class MongoDBArtifactStore implements ArtifactRepository {
} }
} }
private static String sanitizeTenant(final String tenant) {
return tenant.trim().toUpperCase();
}
@Override @Override
public void deleteBySha1(final String sha1Hash) { public void deleteBySha1(final String tenant, final String sha1Hash) {
try { try {
deleteArtifact(gridFs.findOne(new Query().addCriteria(Criteria.where(FILENAME).is(sha1Hash)))); deleteArtifact(gridFs.findOne(new Query()
.addCriteria(Criteria.where(FILENAME).is(sha1Hash).and(TENANT_QUERY).is(sanitizeTenant(tenant)))));
} catch (final MongoException e) { } catch (final MongoException e) {
throw new ArtifactStoreException(e.getMessage(), e); throw new ArtifactStoreException(e.getMessage(), e);
} }
@@ -143,18 +147,21 @@ public class MongoDBArtifactStore implements ArtifactRepository {
} }
private DbArtifact store(final InputStream content, final String contentType, final OutputStream os, private DbArtifact store(final String t, final InputStream content, final String contentType, final OutputStream os,
final File tempFile, final DbArtifactHash hash) { final File tempFile, final DbArtifactHash hash) {
final GridFsArtifact storedArtifact; final GridFsArtifact storedArtifact;
final String tenant = sanitizeTenant(t);
try { try {
final String sha1Hash = computeSHA1Hash(content, os, hash != null ? hash.getSha1() : null); final String sha1Hash = computeSHA1Hash(content, os, hash != null ? hash.getSha1() : null);
// upload if it does not exist already, check if file exists, not // upload if it does not exist already, check if file exists, not
// tenant specific. // tenant specific.
final GridFSDBFile result = gridFs.findOne(new Query().addCriteria(Criteria.where(FILENAME).is(sha1Hash))); final GridFSDBFile result = gridFs.findOne(
if (null == result) { new Query().addCriteria(Criteria.where(FILENAME).is(sha1Hash).and(TENANT_QUERY).is(tenant)));
if (result == null) {
try (FileInputStream inputStream = new FileInputStream(tempFile)) { try (FileInputStream inputStream = new FileInputStream(tempFile)) {
final BasicDBObject metadata = new BasicDBObject(); final BasicDBObject metadata = new BasicDBObject();
metadata.put(SHA1, sha1Hash); metadata.put(SHA1, sha1Hash);
metadata.put(TENANT, tenant);
storedArtifact = map(gridFs.store(inputStream, sha1Hash, contentType, metadata)); storedArtifact = map(gridFs.store(inputStream, sha1Hash, contentType, metadata));
} }
} else { } else {
@@ -244,4 +251,13 @@ public class MongoDBArtifactStore implements ArtifactRepository {
artifact.setHashes(new DbArtifactHash(fsFile.getFilename(), fsFile.getMD5())); artifact.setHashes(new DbArtifactHash(fsFile.getFilename(), fsFile.getMD5()));
return artifact; return artifact;
} }
@Override
public void deleteByTenant(final String tenant) {
try {
gridFs.delete(new Query().addCriteria(Criteria.where(TENANT_QUERY).is(sanitizeTenant(tenant))));
} catch (final MongoClientException e) {
throw new ArtifactStoreException(e.getMessage(), e);
}
}
} }

View File

@@ -12,6 +12,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.data.mongodb.gridfs.GridFsOperations;
/** /**
* Auto configuration for the {@link MongoDBArtifactStore}. * Auto configuration for the {@link MongoDBArtifactStore}.
@@ -25,7 +26,7 @@ public class MongoDBArtifactStoreAutoConfiguration {
* @return Default {@link ArtifactRepository} implementation. * @return Default {@link ArtifactRepository} implementation.
*/ */
@Bean @Bean
public ArtifactRepository artifactRepository() { ArtifactRepository artifactRepository(final GridFsOperations gridFs) {
return new MongoDBArtifactStore(); return new MongoDBArtifactStore(gridFs);
} }
} }

View File

@@ -20,54 +20,75 @@ import org.eclipse.hawkbit.artifact.TestConfiguration;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.gridfs.GridFsOperations;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
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.Stories; import ru.yandex.qatools.allure.annotations.Stories;
@Features("Component Tests - Repository") @Features("Component Tests - Repository")
@Stories("Artifact Store MongoDB") @Stories("Artifact Store MongoDB")
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { MongoDBArtifactStoreAutoConfiguration.class, TestConfiguration.class }) @SpringBootTest(classes = { MongoDBArtifactStoreAutoConfiguration.class, TestConfiguration.class }, properties = {
@TestPropertySource(properties = { "spring.data.mongodb.port=0", "spring.mongodb.embedded.version=3.2.7" }) "spring.data.mongodb.port=0", "spring.mongodb.embedded.version=3.4.4" })
public class MongoDBArtifactStoreTest { public class MongoDBArtifactStoreTest {
private static final String TENANT = "test_tenant";
private static final String TENANT2 = "test_tenant2";
@Autowired @Autowired
private MongoDBArtifactStore artifactStoreUnderTest; private MongoDBArtifactStore artifactStoreUnderTest;
@Autowired
private GridFsOperations gridFs;
@Test @Test
@Description("Ensures that search by SHA1 hash (which is used by hawkBit as artifact ID) finds the expected results.") @Description("Ensures that search by SHA1 hash (which is used by hawkBit as artifact ID) finds the expected results.")
public void findArtifactBySHA1Hash() throws NoSuchAlgorithmException { public void findArtifactBySHA1Hash() throws NoSuchAlgorithmException {
final String sha1 = storeRandomArifactAndVerify(TENANT);
final String sha2 = storeRandomArifactAndVerify(TENANT2);
assertThat(artifactStoreUnderTest.getArtifactBySha1(TENANT2, sha1)).isNull();
assertThat(artifactStoreUnderTest.getArtifactBySha1(TENANT, sha2)).isNull();
}
@Step
private String storeRandomArifactAndVerify(final String tenant) throws NoSuchAlgorithmException {
final int filelengthBytes = 128; final int filelengthBytes = 128;
final String filename = "testfile.json"; final String filename = "testfile.json";
final String contentType = "application/json"; final String contentType = "application/json";
final DigestInputStream digestInputStream = digestInputStream(generateInputStream(filelengthBytes), "SHA-1"); final DigestInputStream digestInputStream = digestInputStream(generateInputStream(filelengthBytes), "SHA-1");
artifactStoreUnderTest.store(digestInputStream, filename, contentType); artifactStoreUnderTest.store(tenant, digestInputStream, filename, contentType);
assertThat(artifactStoreUnderTest.getArtifactBySha1(
BaseEncoding.base16().lowerCase().encode(digestInputStream.getMessageDigest().digest()))).isNotNull(); final String sha1 = BaseEncoding.base16().lowerCase().encode(digestInputStream.getMessageDigest().digest());
assertThat(artifactStoreUnderTest.getArtifactBySha1(tenant, sha1)).isNotNull();
return sha1;
} }
@Test @Test
@Description("Ensures that search by MD5 hash finds the expected results.") @Description("Deletes file from repository identified by SHA1 hash as filename.")
public void findArtifactByMD5Hash() throws NoSuchAlgorithmException { public void deleteArtifactBySHA1Hash() throws NoSuchAlgorithmException {
final int filelengthBytes = 128;
final String filename = "testfile.json";
final String contentType = "application/json";
final DigestInputStream digestInputStream = digestInputStream(generateInputStream(filelengthBytes), "MD5"); final String sha1 = storeRandomArifactAndVerify(TENANT);
artifactStoreUnderTest.store(digestInputStream, filename, contentType);
assertThat(artifactStoreUnderTest.getArtifactByMd5( artifactStoreUnderTest.deleteBySha1(TENANT, sha1);
BaseEncoding.base16().lowerCase().encode(digestInputStream.getMessageDigest().digest()))).isNotNull(); assertThat(artifactStoreUnderTest.getArtifactBySha1(TENANT, sha1)).isNull();
}
@Test
@Description("Verfies that all data of a tenant is erased if repository is asked to do so. "
+ "Data of other tenants is not affected.")
public void deleteTenant() throws NoSuchAlgorithmException {
final String shaDeleted = storeRandomArifactAndVerify(TENANT);
final String shaUndeleted = storeRandomArifactAndVerify("another_tenant");
artifactStoreUnderTest.deleteByTenant("tenant_that_does_not_exist");
artifactStoreUnderTest.deleteByTenant(TENANT);
assertThat(artifactStoreUnderTest.getArtifactBySha1(TENANT, shaDeleted)).isNull();
assertThat(artifactStoreUnderTest.getArtifactBySha1("another_tenant", shaUndeleted)).isNotNull();
} }
private static ByteArrayInputStream generateInputStream(final int length) { private static ByteArrayInputStream generateInputStream(final int length) {

View File

@@ -22,22 +22,22 @@ public class S3Artifact extends DbArtifact {
private final AmazonS3 amazonS3; private final AmazonS3 amazonS3;
private final S3RepositoryProperties s3Properties; private final S3RepositoryProperties s3Properties;
private final String sha1; private final String key;
S3Artifact(final AmazonS3 amazonS3, final S3RepositoryProperties s3Properties, final String sha1) { S3Artifact(final AmazonS3 amazonS3, final S3RepositoryProperties s3Properties, final String key) {
this.amazonS3 = amazonS3; this.amazonS3 = amazonS3;
this.s3Properties = s3Properties; this.s3Properties = s3Properties;
this.sha1 = sha1; this.key = key;
} }
@Override @Override
public InputStream getFileInputStream() { public InputStream getFileInputStream() {
return amazonS3.getObject(s3Properties.getBucketName(), sha1).getObjectContent(); return amazonS3.getObject(s3Properties.getBucketName(), key).getObjectContent();
} }
@Override @Override
public String toString() { public String toString() {
return "S3Artifact [sha1=" + sha1 + ", getArtifactId()=" + getArtifactId() + ", getHashes()=" + getHashes() return "S3Artifact [key=" + key + ", getArtifactId()=" + getArtifactId() + ", getHashes()=" + getHashes()
+ ", getSize()=" + getSize() + ", getContentType()=" + getContentType() + "]"; + ", getSize()=" + getSize() + ", getContentType()=" + getContentType() + "]";
} }
} }

View File

@@ -24,14 +24,17 @@ import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonClientException;
import com.amazonaws.RequestClientOptions; import com.amazonaws.RequestClientOptions;
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.Headers; import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
@@ -48,6 +51,7 @@ import com.google.common.io.ByteStreams;
* across several buckets. * across several buckets.
* </p> * </p>
*/ */
@Validated
public class S3Repository implements ArtifactRepository { public class S3Repository implements ArtifactRepository {
private static final Logger LOG = LoggerFactory.getLogger(S3Repository.class); private static final Logger LOG = LoggerFactory.getLogger(S3Repository.class);
@@ -73,16 +77,17 @@ public class S3Repository implements ArtifactRepository {
} }
@Override @Override
public DbArtifact store(final InputStream content, final String filename, final String contentType) { public DbArtifact store(final String tenant, final InputStream content, final String filename,
return store(content, filename, contentType, null); final String contentType) {
return store(tenant, content, filename, contentType, null);
} }
@Override @Override
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 // suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5
// is not used security related // is not used security related
@SuppressWarnings("squid:S2070") @SuppressWarnings("squid:S2070")
public DbArtifact store(final InputStream content, final String filename, final String contentType, public DbArtifact store(final String tenant, final InputStream content, final String filename,
final DbArtifactHash hash) { final String contentType, final DbArtifactHash hash) {
final MessageDigest mdSHA1; final MessageDigest mdSHA1;
final MessageDigest mdMD5; final MessageDigest mdMD5;
try { try {
@@ -102,7 +107,7 @@ public class S3Repository implements ArtifactRepository {
final String sha1Hash16 = BaseEncoding.base16().lowerCase().encode(mdSHA1.digest()); final String sha1Hash16 = BaseEncoding.base16().lowerCase().encode(mdSHA1.digest());
final String md5Hash16 = BaseEncoding.base16().lowerCase().encode(mdMD5.digest()); final String md5Hash16 = BaseEncoding.base16().lowerCase().encode(mdMD5.digest());
return store(sha1Hash16, md5Hash16, contentType, file, hash); return store(tenant, sha1Hash16, md5Hash16, contentType, file, hash);
} catch (final IOException e) { } catch (final IOException e) {
throw new ArtifactStoreException(e.getMessage(), e); throw new ArtifactStoreException(e.getMessage(), e);
} finally { } finally {
@@ -112,15 +117,16 @@ public class S3Repository implements ArtifactRepository {
} }
} }
private DbArtifact store(final String sha1Hash16, final String mdMD5Hash16, final String contentType, private DbArtifact store(final String tenant, final String sha1Hash16, final String mdMD5Hash16,
final File file, final DbArtifactHash hash) { final String contentType, final File file, final DbArtifactHash hash) {
final S3Artifact s3Artifact = createS3Artifact(sha1Hash16, mdMD5Hash16, contentType, file); final S3Artifact s3Artifact = createS3Artifact(tenant, sha1Hash16, mdMD5Hash16, contentType, file);
checkHashes(s3Artifact, hash); checkHashes(s3Artifact, hash);
final String key = objectKey(tenant, sha1Hash16);
LOG.info("Storing file {} with length {} to AWS S3 bucket {} as SHA1 {}", file.getName(), file.length(), LOG.info("Storing file {} with length {} to AWS S3 bucket {} as SHA1 {}", file.getName(), file.length(),
s3Properties.getBucketName(), sha1Hash16); s3Properties.getBucketName(), key);
if (exists(sha1Hash16)) { if (exists(key)) {
LOG.debug("Artifact {} already exists on S3 bucket {}, don't need to upload twice", sha1Hash16, LOG.debug("Artifact {} already exists on S3 bucket {}, don't need to upload twice", sha1Hash16,
s3Properties.getBucketName()); s3Properties.getBucketName());
return s3Artifact; return s3Artifact;
@@ -129,7 +135,7 @@ public class S3Repository implements ArtifactRepository {
try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(file), try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(file),
RequestClientOptions.DEFAULT_STREAM_BUFFER_SIZE)) { RequestClientOptions.DEFAULT_STREAM_BUFFER_SIZE)) {
final ObjectMetadata objectMetadata = createObjectMetadata(mdMD5Hash16, contentType, file); final ObjectMetadata objectMetadata = createObjectMetadata(mdMD5Hash16, contentType, file);
amazonS3.putObject(s3Properties.getBucketName(), sha1Hash16, inputStream, objectMetadata); amazonS3.putObject(s3Properties.getBucketName(), key, inputStream, objectMetadata);
return s3Artifact; return s3Artifact;
} catch (final IOException | AmazonClientException e) { } catch (final IOException | AmazonClientException e) {
@@ -137,14 +143,15 @@ public class S3Repository implements ArtifactRepository {
} }
} }
private S3Artifact createS3Artifact(final String sha1Hash16, final String mdMD5Hash16, final String contentType, private S3Artifact createS3Artifact(final String tenant, final String sha1Hash, final String mdMD5Hash16,
final File file) { final String contentType, final File file) {
final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, sha1Hash16); final String key = objectKey(tenant, sha1Hash);
final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, key);
s3Artifact.setContentType(contentType); s3Artifact.setContentType(contentType);
s3Artifact.setArtifactId(sha1Hash16); s3Artifact.setArtifactId(sha1Hash);
s3Artifact.setSize(file.length()); s3Artifact.setSize(file.length());
s3Artifact.setContentType(contentType); s3Artifact.setContentType(contentType);
s3Artifact.setHashes(new DbArtifactHash(sha1Hash16, mdMD5Hash16)); s3Artifact.setHashes(new DbArtifactHash(sha1Hash, mdMD5Hash16));
return s3Artifact; return s3Artifact;
} }
@@ -162,26 +169,34 @@ public class S3Repository implements ArtifactRepository {
} }
@Override @Override
public void deleteBySha1(final String sha1Hash) { public void deleteBySha1(final String tenant, final String sha1Hash) {
LOG.info("Deleting S3 object from bucket {} and key {}", s3Properties.getBucketName(), sha1Hash); final String key = objectKey(tenant, sha1Hash);
amazonS3.deleteObject(new DeleteObjectRequest(s3Properties.getBucketName(), sha1Hash));
LOG.info("Deleting S3 object from bucket {} and key {}", s3Properties.getBucketName(), key);
amazonS3.deleteObject(new DeleteObjectRequest(s3Properties.getBucketName(), key));
}
private static String objectKey(final String tenant, final String sha1Hash) {
return sanitizeTenant(tenant) + "/" + sha1Hash;
} }
@Override @Override
public DbArtifact getArtifactBySha1(final String sha1) { public DbArtifact getArtifactBySha1(final String tenant, final String sha1Hash) {
LOG.info("Retrieving S3 object from bucket {} and key {}", s3Properties.getBucketName(), sha1); final String key = objectKey(tenant, sha1Hash);
final S3Object s3Object = amazonS3.getObject(s3Properties.getBucketName(), sha1);
LOG.info("Retrieving S3 object from bucket {} and key {}", s3Properties.getBucketName(), key);
final S3Object s3Object = amazonS3.getObject(s3Properties.getBucketName(), key);
if (s3Object == null) { if (s3Object == null) {
return null; return null;
} }
final ObjectMetadata s3ObjectMetadata = s3Object.getObjectMetadata(); final ObjectMetadata s3ObjectMetadata = s3Object.getObjectMetadata();
final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, sha1); final S3Artifact s3Artifact = new S3Artifact(amazonS3, s3Properties, key);
s3Artifact.setArtifactId(sha1); s3Artifact.setArtifactId(sha1Hash);
s3Artifact.setSize(s3ObjectMetadata.getContentLength()); s3Artifact.setSize(s3ObjectMetadata.getContentLength());
// the MD5Content is stored in the ETag // the MD5Content is stored in the ETag
s3Artifact.setHashes(new DbArtifactHash(sha1, s3Artifact.setHashes(new DbArtifactHash(sha1Hash,
BaseEncoding.base16().lowerCase().encode(BaseEncoding.base64().decode(s3ObjectMetadata.getETag())))); BaseEncoding.base16().lowerCase().encode(BaseEncoding.base64().decode(s3ObjectMetadata.getETag()))));
s3Artifact.setContentType(s3ObjectMetadata.getContentType()); s3Artifact.setContentType(s3ObjectMetadata.getContentType());
return s3Artifact; return s3Artifact;
@@ -220,4 +235,25 @@ public class S3Repository implements ArtifactRepository {
private boolean exists(final String sha1) { private boolean exists(final String sha1) {
return amazonS3.doesObjectExist(s3Properties.getBucketName(), sha1); return amazonS3.doesObjectExist(s3Properties.getBucketName(), sha1);
} }
@Override
public void deleteByTenant(final String tenant) {
final String folder = sanitizeTenant(tenant);
LOG.info("Deleting S3 object folder (tenant) from bucket {} and key {}", s3Properties.getBucketName(), folder);
// Delete artifacts
ObjectListing objects = amazonS3.listObjects(s3Properties.getBucketName(), folder + "/");
do {
for (final S3ObjectSummary objectSummary : objects.getObjectSummaries()) {
amazonS3.deleteObject(s3Properties.getBucketName(), objectSummary.getKey());
}
objects = amazonS3.listNextBatchOfObjects(objects);
} while (objects.isTruncated());
}
private static String sanitizeTenant(final String tenant) {
return tenant.trim().toUpperCase();
}
} }

View File

@@ -54,6 +54,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Features("Unit Tests - S3 Repository") @Features("Unit Tests - S3 Repository")
@Stories("S3 Artifact Repository") @Stories("S3 Artifact Repository")
public class S3RepositoryTest { public class S3RepositoryTest {
private static final String TENANT = "test_tenant";
@Mock @Mock
private AmazonS3 amazonS3Mock; private AmazonS3 amazonS3Mock;
@@ -89,8 +90,9 @@ public class S3RepositoryTest {
storeRandomBytes(rndBytes, knownContentType); storeRandomBytes(rndBytes, knownContentType);
// verify // verify
Mockito.verify(amazonS3Mock).putObject(eq(s3Properties.getBucketName()), eq(knownSHA1), Mockito.verify(amazonS3Mock).putObject(eq(s3Properties.getBucketName()),
inputStreamCaptor.capture(), objectMetaDataCaptor.capture()); eq(TENANT.toUpperCase() + "/" + knownSHA1), inputStreamCaptor.capture(),
objectMetaDataCaptor.capture());
final ObjectMetadata recordedObjectMetadata = objectMetaDataCaptor.getValue(); final ObjectMetadata recordedObjectMetadata = objectMetaDataCaptor.getValue();
assertThat(recordedObjectMetadata.getContentType()).isEqualTo(knownContentType); assertThat(recordedObjectMetadata.getContentType()).isEqualTo(knownContentType);
@@ -115,7 +117,7 @@ public class S3RepositoryTest {
when(s3ObjectMetadataMock.getContentType()).thenReturn(knownContentType); when(s3ObjectMetadataMock.getContentType()).thenReturn(knownContentType);
// test // test
final DbArtifact artifactBySha1 = s3RepositoryUnderTest.getArtifactBySha1(knownSHA1Hash); final DbArtifact artifactBySha1 = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash);
// verify // verify
assertThat(artifactBySha1.getArtifactId()).isEqualTo(knownSHA1Hash); assertThat(artifactBySha1.getArtifactId()).isEqualTo(knownSHA1Hash);
@@ -150,7 +152,7 @@ public class S3RepositoryTest {
when(amazonS3Mock.getObject(s3Properties.getBucketName(), knownSHA1Hash)).thenReturn(null); when(amazonS3Mock.getObject(s3Properties.getBucketName(), knownSHA1Hash)).thenReturn(null);
// test // test
final DbArtifact artifactBySha1NotExists = s3RepositoryUnderTest.getArtifactBySha1(knownSHA1Hash); final DbArtifact artifactBySha1NotExists = s3RepositoryUnderTest.getArtifactBySha1(TENANT, knownSHA1Hash);
// verify // verify
assertThat(artifactBySha1NotExists).isNull(); assertThat(artifactBySha1NotExists).isNull();
@@ -201,7 +203,7 @@ public class S3RepositoryTest {
throws IOException { throws IOException {
final String knownFileName = "randomBytes"; final String knownFileName = "randomBytes";
try (InputStream content = new BufferedInputStream(new ByteArrayInputStream(rndBytes))) { try (InputStream content = new BufferedInputStream(new ByteArrayInputStream(rndBytes))) {
s3RepositoryUnderTest.store(content, knownFileName, contentType, hashes); s3RepositoryUnderTest.store(TENANT, content, knownFileName, contentType, hashes);
} }
} }

View File

@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
@@ -44,6 +45,7 @@ import com.google.common.io.Files;
* are stored in different sub-directories based on the last four digits of the * are stored in different sub-directories based on the last four digits of the
* SHA1-hash {@code (/basepath/[two digit sha1]/[two digit sha1])}. * SHA1-hash {@code (/basepath/[two digit sha1]/[two digit sha1])}.
*/ */
@Validated
public class ArtifactFilesystemRepository implements ArtifactRepository { public class ArtifactFilesystemRepository implements ArtifactRepository {
private static final Logger LOG = LoggerFactory.getLogger(ArtifactFilesystemRepository.class); private static final Logger LOG = LoggerFactory.getLogger(ArtifactFilesystemRepository.class);
@@ -64,16 +66,17 @@ public class ArtifactFilesystemRepository implements ArtifactRepository {
} }
@Override @Override
public ArtifactFilesystem store(final InputStream content, final String filename, final String contentType) { public ArtifactFilesystem store(final String tenant, final InputStream content, final String filename,
return store(content, filename, contentType, null); final String contentType) {
return store(tenant, content, filename, contentType, null);
} }
@Override @Override
// suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5 // suppress warning, of not strong enough hashing algorithm, SHA-1 and MD5
// is not used security related // is not used security related
@SuppressWarnings("squid:S2070") @SuppressWarnings("squid:S2070")
public ArtifactFilesystem store(final InputStream content, final String filename, final String contentType, public ArtifactFilesystem store(final String tenant, final InputStream content, final String filename,
final DbArtifactHash hash) { final String contentType, final DbArtifactHash hash) {
final MessageDigest mdSHA1; final MessageDigest mdSHA1;
final MessageDigest mdMD5; final MessageDigest mdMD5;
@@ -86,17 +89,17 @@ public class ArtifactFilesystemRepository implements ArtifactRepository {
final File file = createTempFile(); final File file = createTempFile();
final DbArtifact artifact = store(content, contentType, hash, mdSHA1, mdMD5, file); final DbArtifact artifact = store(content, contentType, hash, mdSHA1, mdMD5, file);
return renameFileToSHA1Naming(file, artifact); return renameFileToSHA1Naming(tenant, file, artifact);
} }
@Override @Override
public void deleteBySha1(final String sha1Hash) { public void deleteBySha1(final String tenant, final String sha1Hash) {
FileUtils.deleteQuietly(getFile(sha1Hash)); FileUtils.deleteQuietly(getFile(tenant, sha1Hash));
} }
@Override @Override
public ArtifactFilesystem getArtifactBySha1(final String sha1) { public ArtifactFilesystem getArtifactBySha1(final String tenant, final String sha1) {
final File file = getFile(sha1); final File file = getFile(tenant, sha1);
if (!file.exists()) { if (!file.exists()) {
return null; return null;
} }
@@ -131,8 +134,8 @@ public class ArtifactFilesystemRepository implements ArtifactRepository {
return artifact; return artifact;
} }
private ArtifactFilesystem renameFileToSHA1Naming(final File file, final DbArtifact artifact) { private ArtifactFilesystem renameFileToSHA1Naming(final String tenant, final File file, final DbArtifact artifact) {
final File fileSHA1Naming = getFile(artifact.getHashes().getSha1()); final File fileSHA1Naming = getFile(tenant, artifact.getHashes().getSha1());
final ArtifactFilesystem fileSystemArtifact = new ArtifactFilesystem(fileSHA1Naming); final ArtifactFilesystem fileSystemArtifact = new ArtifactFilesystem(fileSHA1Naming);
if (fileSHA1Naming.exists()) { if (fileSHA1Naming.exists()) {
FileUtils.deleteQuietly(file); FileUtils.deleteQuietly(file);
@@ -179,18 +182,18 @@ public class ArtifactFilesystemRepository implements ArtifactRepository {
} }
} }
private File getFile(final String sha1) { private File getFile(final String tenant, final String sha1) {
final File aritfactDirectory = getSha1DirectoryPath(sha1).toFile(); final File aritfactDirectory = getSha1DirectoryPath(tenant, sha1).toFile();
aritfactDirectory.mkdirs(); aritfactDirectory.mkdirs();
return new File(aritfactDirectory, sha1); return new File(aritfactDirectory, sha1);
} }
private Path getSha1DirectoryPath(final String sha1) { private Path getSha1DirectoryPath(final String tenant, final String sha1) {
final int length = sha1.length(); final int length = sha1.length();
final List<String> folders = Splitter.fixedLength(2).splitToList(sha1.substring(length - 4, length)); final List<String> folders = Splitter.fixedLength(2).splitToList(sha1.substring(length - 4, length));
final String folder1 = folders.get(0); final String folder1 = folders.get(0);
final String folder2 = folders.get(1); final String folder2 = folders.get(1);
return Paths.get(artifactResourceProperties.getPath(), folder1, folder2); return Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant), folder1, folder2);
} }
private DigestOutputStream openFileOutputStream(final File file, final MessageDigest mdSHA1, private DigestOutputStream openFileOutputStream(final File file, final MessageDigest mdSHA1,
@@ -198,4 +201,13 @@ public class ArtifactFilesystemRepository implements ArtifactRepository {
return new DigestOutputStream( return new DigestOutputStream(
new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(file)), mdMD5), mdSHA1); new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(file)), mdMD5), mdSHA1);
} }
@Override
public void deleteByTenant(final String tenant) {
FileUtils.deleteQuietly(Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant)).toFile());
}
private static String sanitizeTenant(final String tenant) {
return tenant.trim().toUpperCase();
}
} }

View File

@@ -26,6 +26,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Features("Unit Tests - Artifact File System Repository") @Features("Unit Tests - Artifact File System Repository")
@Stories("Test storing artifact binaries in the file-system") @Stories("Test storing artifact binaries in the file-system")
public class ArtifactFilesystemRepositoryTest { public class ArtifactFilesystemRepositoryTest {
private static final String TENANT = "test_tenant";
private final ArtifactFilesystemProperties artifactResourceProperties = new ArtifactFilesystemProperties(); private final ArtifactFilesystemProperties artifactResourceProperties = new ArtifactFilesystemProperties();
@@ -51,8 +52,8 @@ public class ArtifactFilesystemRepositoryTest {
final byte[] fileContent = randomBytes(); final byte[] fileContent = randomBytes();
final ArtifactFilesystem artifact = storeRandomArtifact(fileContent); final ArtifactFilesystem artifact = storeRandomArtifact(fileContent);
final DbArtifact artifactBySha1 = artifactFilesystemRepository final DbArtifact artifactBySha1 = artifactFilesystemRepository.getArtifactBySha1(TENANT,
.getArtifactBySha1(artifact.getHashes().getSha1()); artifact.getHashes().getSha1());
assertThat(artifactBySha1).isNotNull(); assertThat(artifactBySha1).isNotNull();
} }
@@ -61,16 +62,33 @@ public class ArtifactFilesystemRepositoryTest {
public void deleteStoredArtifactBySHA1Hash() { public void deleteStoredArtifactBySHA1Hash() {
final ArtifactFilesystem artifact = storeRandomArtifact(randomBytes()); final ArtifactFilesystem artifact = storeRandomArtifact(randomBytes());
artifactFilesystemRepository.deleteBySha1(artifact.getHashes().getSha1()); artifactFilesystemRepository.deleteBySha1(TENANT, artifact.getHashes().getSha1());
assertThat(artifactFilesystemRepository.getArtifactBySha1(artifact.getHashes().getSha1())).isNull(); assertThat(artifactFilesystemRepository.getArtifactBySha1(TENANT, artifact.getHashes().getSha1())).isNull();
}
@Test
@Description("Verfies that all artifacts of a tenant can be deleted in the file-system repository")
public void deleteStoredArtifactOfTenant() {
final ArtifactFilesystem artifact = storeRandomArtifact(randomBytes());
artifactFilesystemRepository.deleteByTenant(TENANT);
assertThat(artifactFilesystemRepository.getArtifactBySha1(TENANT, artifact.getHashes().getSha1())).isNull();
} }
@Test @Test
@Description("Verfies that an artifact which does not exists is deleted quietly in the file-system repository") @Description("Verfies that an artifact which does not exists is deleted quietly in the file-system repository")
public void deleteArtifactWhichDoesNotExistsBySHA1HashWithoutException() { public void deleteArtifactWhichDoesNotExistsBySHA1HashWithoutException() {
try { try {
artifactFilesystemRepository.deleteBySha1("sha1HashWhichDoesNotExists"); artifactFilesystemRepository.deleteBySha1(TENANT, "sha1HashWhichDoesNotExists");
} catch (final Exception e) {
Assertions.fail("did not expect an exception while deleting a file which does not exists");
}
final ArtifactFilesystem artifact = storeRandomArtifact(randomBytes());
try {
artifactFilesystemRepository.deleteBySha1("tenantWhichDoesNotExist", artifact.getHashes().getSha1());
} catch (final Exception e) { } catch (final Exception e) {
Assertions.fail("did not expect an exception while deleting a file which does not exists"); Assertions.fail("did not expect an exception while deleting a file which does not exists");
} }
@@ -79,7 +97,8 @@ public class ArtifactFilesystemRepositoryTest {
private ArtifactFilesystem storeRandomArtifact(final byte[] fileContent) { private ArtifactFilesystem storeRandomArtifact(final byte[] fileContent) {
final String fileName = "filename.tmp"; final String fileName = "filename.tmp";
final ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContent); final ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContent);
final ArtifactFilesystem store = artifactFilesystemRepository.store(inputStream, fileName, "application/txt"); final ArtifactFilesystem store = artifactFilesystemRepository.store(TENANT, inputStream, fileName,
"application/txt");
return store; return store;
} }

View File

@@ -313,7 +313,8 @@ public class SecurityManagedConfiguration {
http.regexMatcher(HttpDownloadAuthenticationFilter.REQUEST_ID_REGEX_PATTERN) http.regexMatcher(HttpDownloadAuthenticationFilter.REQUEST_ID_REGEX_PATTERN)
.addFilterBefore(downloadIdAuthenticationFilter, FilterSecurityInterceptor.class); .addFilterBefore(downloadIdAuthenticationFilter, FilterSecurityInterceptor.class);
http.authorizeRequests().anyRequest().authenticated(); http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
} }
@Override @Override
@@ -401,6 +402,7 @@ public class SecurityManagedConfiguration {
httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint); httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint);
httpSec.anonymous().disable(); httpSec.anonymous().disable();
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
} }
} }

View File

@@ -28,6 +28,14 @@
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>

View File

@@ -10,8 +10,11 @@ package org.eclipse.hawkbit.artifact.repository;
import java.io.InputStream; import java.io.InputStream;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* ArtifactRepository service interface. * ArtifactRepository service interface.
@@ -39,11 +42,14 @@ public interface ArtifactRepository {
* @throws ArtifactStoreException * @throws ArtifactStoreException
* in case storing of the artifact was not successful * in case storing of the artifact was not successful
*/ */
DbArtifact store(final InputStream content, final String filename, final String contentType); DbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
String contentType);
/** /**
* Stores an artifact into the repository. * Stores an artifact into the repository.
* *
* @param tenant
* the tenant to store the artifact
* @param content * @param content
* the content to store * the content to store
* @param filename * @param filename
@@ -63,23 +69,27 @@ public interface ArtifactRepository {
* in case {@code hash} is provided and not matching to the * in case {@code hash} is provided and not matching to the
* calculated hashes during storing * calculated hashes during storing
*/ */
DbArtifact store(final InputStream content, final String filename, final String contentType, DbArtifactHash hash); DbArtifact store(@NotEmpty String tenant, @NotNull InputStream content, @NotEmpty String filename,
String contentType, DbArtifactHash hash);
/** /**
* Deletes an artifact by its SHA1 hash. * Deletes an artifact by its SHA1 hash.
* *
* * @param tenant
* the tenant to store the artifact
* @param sha1Hash * @param sha1Hash
* the sha1-hash of the artifact to delete * the sha1-hash of the artifact to delete
* *
* @throws MethodNotSupportedException * @throws MethodNotSupportedException
* if implementation does not support the operation * if implementation does not support the operation
*/ */
void deleteBySha1(final String sha1Hash); void deleteBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
/** /**
* Retrieves a {@link DbArtifact} from the store by it's SHA1 hash. * Retrieves a {@link DbArtifact} from the store by it's SHA1 hash.
* *
* @param tenant
* the tenant to store the artifact
* @param sha1Hash * @param sha1Hash
* the sha1-hash of the file to lookup. * the sha1-hash of the file to lookup.
* @return The artifact file object or {@code null} if no file exists. * @return The artifact file object or {@code null} if no file exists.
@@ -87,5 +97,13 @@ public interface ArtifactRepository {
* @throws MethodNotSupportedException * @throws MethodNotSupportedException
* if implementation does not support the operation * if implementation does not support the operation
*/ */
DbArtifact getArtifactBySha1(String sha1Hash); DbArtifact getArtifactBySha1(@NotEmpty String tenant, @NotEmpty String sha1Hash);
/**
* Deletes all artifacts of given tenant.
*
* @param tenant
* to erase
*/
void deleteByTenant(@NotEmpty String tenant);
} }

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.cache.DownloadType; import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadRestApi;
import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder; import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -37,9 +38,6 @@ import com.google.common.net.HttpHeaders;
/** /**
* A resource for download artifacts. * A resource for download artifacts.
*
*
*
*/ */
@RestController @RestController
@Scope(value = WebApplicationContext.SCOPE_REQUEST) @Scope(value = WebApplicationContext.SCOPE_REQUEST)
@@ -56,6 +54,9 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi {
@Autowired @Autowired
private RequestResponseContextHolder requestResponseContextHolder; private RequestResponseContextHolder requestResponseContextHolder;
@Autowired
private TenantAware tenantAware;
/** /**
* Handles the GET request for downloading an artifact. * Handles the GET request for downloading an artifact.
* *
@@ -77,7 +78,7 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi {
DbArtifact artifact = null; DbArtifact artifact = null;
if (DownloadType.BY_SHA1.equals(artifactCache.getDownloadType())) { if (DownloadType.BY_SHA1.equals(artifactCache.getDownloadType())) {
artifact = artifactRepository.getArtifactBySha1(artifactCache.getId()); artifact = artifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifactCache.getId());
} else { } else {
LOGGER.warn("Download Type {} not supported", artifactCache.getDownloadType()); LOGGER.warn("Download Type {} not supported", artifactCache.getDownloadType());
} }

View File

@@ -23,11 +23,11 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException; import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
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.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
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.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -119,12 +119,14 @@ public interface ControllerManagement {
* Retrieves oldest {@link Action} that is active and assigned to a * Retrieves oldest {@link Action} that is active and assigned to a
* {@link Target}. * {@link Target}.
* *
* For performance reasons this method does not throw
* {@link EntityNotFoundException} in case target with given controlelrId
* does not exist but will return an {@link Optional#empty()} instead.
*
* @param controllerId * @param controllerId
* identifies the target to retrieve the actions from * identifies the target to retrieve the actions from
* @return a list of actions assigned to given target which are active * @return a list of actions assigned to given target which are active
* *
* @throws EntityNotFoundException
* if target with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Optional<Action> findOldestActiveActionByTarget(@NotNull String controllerId); Optional<Action> findOldestActiveActionByTarget(@NotNull String controllerId);

View File

@@ -116,6 +116,17 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
Optional<Action> findFirstByTargetControllerIdAndActive(final Sort sort, final String controllerId, boolean active); Optional<Action> findFirstByTargetControllerIdAndActive(final Sort sort, final String controllerId, boolean active);
/**
* Checks if an active action exists for given
* {@link Target#getControllerId()}.
*
* @param controllerId
* of target to check
* @return <code>true</code> if an active action for the target exists.
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a JOIN a.target t WHERE t.controllerId=:controllerId AND a.active=1")
boolean activeActionExistsForControllerId(@Param("controllerId") String controllerId);
/** /**
* Retrieves latest {@link Action} for given target and * Retrieves latest {@link Action} for given target and
* {@link SoftwareModule}. * {@link SoftwareModule}.

View File

@@ -28,9 +28,9 @@ 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;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -49,14 +49,22 @@ public class JpaArtifactManagement implements ArtifactManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class); private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class);
@Autowired private final LocalArtifactRepository localArtifactRepository;
private LocalArtifactRepository localArtifactRepository;
@Autowired private final SoftwareModuleRepository softwareModuleRepository;
private SoftwareModuleRepository softwareModuleRepository;
@Autowired private final ArtifactRepository artifactRepository;
private ArtifactRepository artifactRepository;
private final TenantAware tenantAware;
JpaArtifactManagement(final LocalArtifactRepository localArtifactRepository,
final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) {
this.localArtifactRepository = localArtifactRepository;
this.softwareModuleRepository = softwareModuleRepository;
this.artifactRepository = artifactRepository;
this.tenantAware = tenantAware;
}
private static Artifact checkForExistingArtifact(final String filename, final boolean overrideExisting, private static Artifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
final SoftwareModule softwareModule) { final SoftwareModule softwareModule) {
@@ -87,7 +95,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
final Artifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule); final Artifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
try { try {
result = artifactRepository.store(stream, filename, contentType, result = artifactRepository.store(tenantAware.getCurrentTenant(), stream, filename, contentType,
new DbArtifactHash(providedSha1Sum, providedMd5Sum)); new DbArtifactHash(providedSha1Sum, providedMd5Sum));
} catch (final ArtifactStoreException e) { } catch (final ArtifactStoreException e) {
throw new ArtifactUploadFailedException(e); throw new ArtifactUploadFailedException(e);
@@ -118,7 +126,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
try { try {
LOG.debug("deleting artifact from repository {}", sha1Hash); LOG.debug("deleting artifact from repository {}", sha1Hash);
artifactRepository.deleteBySha1(sha1Hash); artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash);
return true; return true;
} catch (final ArtifactStoreException e) { } catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e); throw new ArtifactDeleteFailedException(e);
@@ -177,7 +185,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override @Override
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash) { public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash) {
return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash)); return Optional.ofNullable(artifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), sha1Hash));
} }
private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename, private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,

View File

@@ -188,7 +188,9 @@ public class JpaControllerManagement implements ControllerManagement {
@Override @Override
public Optional<Action> findOldestActiveActionByTarget(final String controllerId) { public Optional<Action> findOldestActiveActionByTarget(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId); if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
return Optional.empty();
}
// used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to // used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to
// DATAJPA-841 issue. // DATAJPA-841 issue.

View File

@@ -73,7 +73,6 @@ import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/** /**
* JPA implementation of {@link SoftwareModuleManagement}. * JPA implementation of {@link SoftwareModuleManagement}.
@@ -591,7 +590,7 @@ 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 void deleteSoftwareModule(final Long moduleId) { public void deleteSoftwareModule(final Long moduleId) {
deleteSoftwareModules(Sets.newHashSet(moduleId)); deleteSoftwareModules(Arrays.asList(moduleId));
} }
@Override @Override

View File

@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.cache.TenancyCacheManager; import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
@@ -116,6 +117,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Autowired @Autowired
private RolloutStatusCache rolloutStatusCache; private RolloutStatusCache rolloutStatusCache;
@Autowired
private ArtifactRepository artifactRepository;
@Override @Override
public SystemUsageReport getSystemUsageStatistics() { public SystemUsageReport getSystemUsageStatistics() {
@@ -236,6 +240,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
distributionSetRepository.deleteByTenant(tenant); distributionSetRepository.deleteByTenant(tenant);
distributionSetTypeRepository.deleteByTenant(tenant); distributionSetTypeRepository.deleteByTenant(tenant);
softwareModuleRepository.deleteByTenant(tenant); softwareModuleRepository.deleteByTenant(tenant);
artifactRepository.deleteByTenant(tenant);
softwareModuleTypeRepository.deleteByTenant(tenant); softwareModuleTypeRepository.deleteByTenant(tenant);
return null; return null;
}); });

View File

@@ -13,6 +13,7 @@ import java.util.Map;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -502,16 +503,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new JpaControllerManagement(); return new JpaControllerManagement();
} }
/**
* {@link JpaArtifactManagement} bean.
*
* @return a new {@link ArtifactManagement}
*/
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
ArtifactManagement artifactManagement() { ArtifactManagement artifactManagement(final LocalArtifactRepository localArtifactRepository,
return new JpaArtifactManagement(); final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) {
return new JpaArtifactManagement(localArtifactRepository, softwareModuleRepository, artifactRepository,
tenantAware);
} }
/** /**

View File

@@ -175,16 +175,21 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
assertThat(result2.getId()).isNotNull(); assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash()); assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNotNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result2).getSha1Hash())).isNotNull(); .isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
artifactManagement.deleteArtifact(result.getId()); artifactManagement.deleteArtifact(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result2).getSha1Hash())).isNotNull(); .isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
artifactManagement.deleteArtifact(result2.getId()); artifactManagement.deleteArtifact(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result2).getSha1Hash())).isNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNull();
assertThat(artifactRepository.findAll()).hasSize(0); assertThat(artifactRepository.findAll()).hasSize(0);
} }
@@ -211,12 +216,15 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
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(((JpaArtifact) result).getSha1Hash())).isNotNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
artifactManagement.deleteArtifact(result.getId()); artifactManagement.deleteArtifact(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNotNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
artifactManagement.deleteArtifact(result2.getId()); artifactManagement.deleteArtifact(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
} }
@Test @Test

View File

@@ -78,6 +78,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(controllerManagement.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), assertThat(controllerManagement.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(),
module.getId())).isNotPresent(); module.getId())).isNotPresent();
assertThat(controllerManagement.findOldestActiveActionByTarget(NOT_EXIST_ID)).isNotPresent();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse(); assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse();
assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse(); assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse();
} }
@@ -100,8 +102,6 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> controllerManagement.addUpdateActionStatus( verifyThrownExceptionBy(() -> controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(NOT_EXIST_IDL).status(Action.Status.FINISHED)), "Action"); entityFactory.actionStatus().create(NOT_EXIST_IDL).status(Action.Status.FINISHED)), "Action");
verifyThrownExceptionBy(() -> controllerManagement.findOldestActiveActionByTarget(NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> controllerManagement verifyThrownExceptionBy(() -> controllerManagement
.getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), NOT_EXIST_IDL), .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), NOT_EXIST_IDL),
"SoftwareModule"); "SoftwareModule");

View File

@@ -22,7 +22,6 @@ import java.util.List;
import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.RandomUtils;
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.jpa.model.JpaArtifact;
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;
@@ -490,13 +489,15 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(artifactRepository.findAll()).hasSize(results.length); assertThat(artifactRepository.findAll()).hasSize(results.length);
for (final Artifact result : results) { for (final Artifact result : results) {
assertThat(result.getId()).isNotNull(); assertThat(result.getId()).isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNotNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
} }
} }
private void assertArtfiactNull(final Artifact... results) { private void assertArtfiactNull(final Artifact... results) {
for (final Artifact result : results) { for (final Artifact result : results) {
assertThat(binaryArtifactRepository.getArtifactBySha1(((JpaArtifact) result).getSha1Hash())).isNull(); assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
} }
} }

View File

@@ -10,8 +10,9 @@
# Test utility properties for easier fault investigation - START # Test utility properties for easier fault investigation - START
## Logging - START ## Logging - START
logging.level.=INFO logging.level.=INFO
logging.level.org.eclipse.persistence=ERROR
logging.level.org.eclipse.hawkbit.repository.test.matcher.EventVerifier=ERROR logging.level.org.eclipse.hawkbit.repository.test.matcher.EventVerifier=ERROR
logging.level.org.eclipse.persistence=ERROR
spring.datasource.eclipselink.logging.logger=JavaLogger
spring.jpa.properties.eclipselink.logging.level=FINE spring.jpa.properties.eclipselink.logging.level=FINE
spring.jpa.properties.eclipselink.logging.level.sql=FINE spring.jpa.properties.eclipselink.logging.level.sql=FINE
spring.jpa.properties.eclipselink.logging.parameters=true spring.jpa.properties.eclipselink.logging.parameters=true

View File

@@ -48,10 +48,10 @@ public class DosFilter extends OncePerRequestFilter {
private final Pattern ipAdressBlacklist; private final Pattern ipAdressBlacklist;
private final Cache<String, AtomicInteger> readCountCache = Caffeine.newBuilder() private final Cache<String, AtomicInteger> readCountCache = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.SECONDS).build(); .expireAfterWrite(1, TimeUnit.SECONDS).build();
private final Cache<String, AtomicInteger> writeCountCache = Caffeine.newBuilder() private final Cache<String, AtomicInteger> writeCountCache = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.SECONDS).build(); .expireAfterWrite(1, TimeUnit.SECONDS).build();
private final int maxRead; private final int maxRead;
private final int maxWrite; private final int maxWrite;

View File

@@ -52,6 +52,10 @@ public class CreateUpdateDistributionTagLayoutWindow extends AbstractCreateUpdat
@Override @Override
protected void populateTagNameCombo() { protected void populateTagNameCombo() {
if (tagNameComboBox == null) {
return;
}
tagNameComboBox.removeAllItems(); tagNameComboBox.removeAllItems();
final List<DistributionSetTag> distTagNameList = tagManagement final List<DistributionSetTag> distTagNameList = tagManagement
.findAllDistributionSetTags(new PageRequest(0, MAX_TAGS)).getContent(); .findAllDistributionSetTags(new PageRequest(0, MAX_TAGS)).getContent();