Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BACKPORT 7x Client-side encrypted snapshot repository (feature flag) (#66773) #66809

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions plugins/repository-azure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,18 @@ task azureThirdPartyTest(type: Test) {
}
}
tasks.named("check").configure { dependsOn("azureThirdPartyTest") }

// test jar is exported by the integTestArtifacts configuration to be used in the encrypted Azure repository test
configurations {
internalClusterTestArtifacts.extendsFrom internalClusterTestImplementation
internalClusterTestArtifacts.extendsFrom internalClusterTestRuntime
}

def internalClusterTestJar = tasks.register("internalClusterTestJar", Jar) {
appendix 'internalClusterTest'
from sourceSets.internalClusterTest.output
}

artifacts {
internalClusterTestArtifacts internalClusterTestJar
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,15 @@ protected String repositoryType() {
}

@Override
protected Settings repositorySettings() {
return Settings.builder()
.put(super.repositorySettings())
.put(AzureRepository.Repository.CONTAINER_SETTING.getKey(), "container")
.put(AzureStorageSettings.ACCOUNT_SETTING.getKey(), "test")
.build();
protected Settings repositorySettings(String repoName) {
Settings.Builder settingsBuilder = Settings.builder()
.put(super.repositorySettings(repoName))
.put(AzureRepository.Repository.CONTAINER_SETTING.getKey(), "container")
.put(AzureStorageSettings.ACCOUNT_SETTING.getKey(), "test");
if (randomBoolean()) {
settingsBuilder.put(AzureRepository.Repository.BASE_PATH_SETTING.getKey(), randomFrom("test", "test/1"));
}
return settingsBuilder.build();
}

@Override
Expand Down
17 changes: 17 additions & 0 deletions plugins/repository-gcs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,20 @@ def gcsThirdPartyTest = tasks.register("gcsThirdPartyTest", Test) {
tasks.named("check").configure {
dependsOn(largeBlobYamlRestTest, gcsThirdPartyTest)
}

// test jar is exported by the integTestArtifacts configuration to be used in the encrypted GCS repository test
configurations {
internalClusterTestArtifacts.extendsFrom internalClusterTestImplementation
internalClusterTestArtifacts.extendsFrom internalClusterTestRuntime
}

def internalClusterTestJar = tasks.register("internalClusterTestJar", Jar) {
appendix 'internalClusterTest'
from sourceSets.internalClusterTest.output
// for the repositories.gcs.TestUtils class
from sourceSets.test.output
}

artifacts {
internalClusterTestArtifacts internalClusterTestJar
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.ENDPOINT_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.TOKEN_URI_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageRepository.BASE_PATH;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageRepository.BUCKET;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageRepository.CLIENT_NAME;

Expand All @@ -94,12 +95,15 @@ protected String repositoryType() {
}

@Override
protected Settings repositorySettings() {
return Settings.builder()
.put(super.repositorySettings())
.put(BUCKET.getKey(), "bucket")
.put(CLIENT_NAME.getKey(), "test")
.build();
protected Settings repositorySettings(String repoName) {
Settings.Builder settingsBuilder = Settings.builder()
.put(super.repositorySettings(repoName))
.put(BUCKET.getKey(), "bucket")
.put(CLIENT_NAME.getKey(), "test");
if (randomBoolean()) {
settingsBuilder.put(BASE_PATH.getKey(), randomFrom("test", "test/1"));
}
return settingsBuilder.build();
}

@Override
Expand Down Expand Up @@ -135,7 +139,7 @@ protected Settings nodeSettings(int nodeOrdinal) {
}

public void testDeleteSingleItem() {
final String repoName = createRepository(randomName());
final String repoName = createRepository(randomRepositoryName());
final RepositoriesService repositoriesService = internalCluster().getMasterNodeInstance(RepositoriesService.class);
final BlobStoreRepository repository = (BlobStoreRepository) repositoriesService.repository(repoName);
PlainActionFuture.get(f -> repository.threadPool().generic().execute(ActionRunnable.run(f, () ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected String repositoryType() {
}

@Override
protected Settings repositorySettings() {
protected Settings repositorySettings(String repoName) {
return Settings.builder()
.put("uri", "hdfs:///")
.put("conf.fs.AbstractFileSystem.hdfs.impl", TestingFs.class.getName())
Expand All @@ -47,6 +47,12 @@ protected Settings repositorySettings() {
.put("compress", randomBoolean()).build();
}

@Override
public void testSnapshotAndRestore() throws Exception {
// the HDFS mockup doesn't preserve the repository contents after removing the repository
testSnapshotAndRestore(false);
}

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
return Collections.singletonList(HdfsPlugin.class);
Expand Down
17 changes: 17 additions & 0 deletions plugins/repository-s3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,20 @@ tasks.named("thirdPartyAudit").configure {
ignoreMissingClasses 'javax.activation.DataHandler'
}
}

// test jar is exported by the integTestArtifacts configuration to be used in the encrypted S3 repository test
configurations {
internalClusterTestArtifacts.extendsFrom internalClusterTestImplementation
internalClusterTestArtifacts.extendsFrom internalClusterTestRuntime
}

def internalClusterTestJar = tasks.register("internalClusterTestJar", Jar) {
appendix 'internalClusterTest'
from sourceSets.internalClusterTest.output
// for the plugin-security.policy resource
from sourceSets.test.output
}

artifacts {
internalClusterTestArtifacts internalClusterTestJar
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,17 @@ protected String repositoryType() {
}

@Override
protected Settings repositorySettings() {
return Settings.builder()
.put(super.repositorySettings())
protected Settings repositorySettings(String repoName) {
Settings.Builder settingsBuilder = Settings.builder()
.put(super.repositorySettings(repoName))
.put(S3Repository.BUCKET_SETTING.getKey(), "bucket")
.put(S3Repository.CLIENT_NAME.getKey(), "test")
// Don't cache repository data because some tests manually modify the repository data
.put(BlobStoreRepository.CACHE_REPOSITORY_DATA.getKey(), false)
.build();
.put(BlobStoreRepository.CACHE_REPOSITORY_DATA.getKey(), false);
if (randomBoolean()) {
settingsBuilder.put(S3Repository.BASE_PATH_SETTING.getKey(), randomFrom("test", "test/1"));
}
return settingsBuilder.build();
}

@Override
Expand Down Expand Up @@ -145,8 +148,9 @@ protected Settings nodeSettings(int nodeOrdinal) {
}

public void testEnforcedCooldownPeriod() throws IOException {
final String repoName = createRepository(randomName(), Settings.builder().put(repositorySettings())
.put(S3Repository.COOLDOWN_PERIOD.getKey(), TEST_COOLDOWN_PERIOD).build());
final String repoName = randomRepositoryName();
createRepository(repoName, Settings.builder().put(repositorySettings(repoName))
.put(S3Repository.COOLDOWN_PERIOD.getKey(), TEST_COOLDOWN_PERIOD).build(), true);

final SnapshotId fakeOldSnapshot = client().admin().cluster().prepareCreateSnapshot(repoName, "snapshot-old")
.setWaitForCompletion(true).setIndices().get().getSnapshotInfo().snapshotId();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.repositories.fs;

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.repositories.blobstore.ESFsBasedRepositoryIntegTestCase;

public class FsBlobStoreRepositoryIntegTests extends ESFsBasedRepositoryIntegTestCase {

@Override
protected Settings repositorySettings(String repositoryName) {
final Settings.Builder settings = Settings.builder()
.put("compress", randomBoolean())
.put("location", randomRepoPath());
if (randomBoolean()) {
long size = 1 << randomInt(10);
settings.put("chunk_size", new ByteSizeValue(size, ByteSizeUnit.KB));
}
return settings.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
* The list of paths where a blob can reside. The contents of the paths are dependent upon the implementation of {@link BlobContainer}.
Expand Down Expand Up @@ -91,4 +92,17 @@ public String toString() {
}
return sb.toString();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BlobPath other = (BlobPath) o;
return paths.equals(other.paths);
}

@Override
public int hashCode() {
return Objects.hash(paths);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.elasticsearch.repositories.RepositoriesService;
import org.elasticsearch.repositories.Repository;
import org.elasticsearch.repositories.RepositoryData;
import org.elasticsearch.repositories.RepositoryMissingException;
import org.elasticsearch.snapshots.SnapshotMissingException;
import org.elasticsearch.snapshots.SnapshotRestoreException;
import org.elasticsearch.test.ESIntegTestCase;
Expand Down Expand Up @@ -78,17 +79,19 @@ public static RepositoryData getRepositoryData(Repository repository) {

protected abstract String repositoryType();

protected Settings repositorySettings() {
protected Settings repositorySettings(String repoName) {
return Settings.builder().put("compress", randomBoolean()).build();
}

protected final String createRepository(final String name) {
return createRepository(name, repositorySettings());
return createRepository(name, true);
}

protected final String createRepository(final String name, final Settings settings) {
final boolean verify = randomBoolean();
protected final String createRepository(final String name, final boolean verify) {
return createRepository(name, repositorySettings(name), verify);
}

protected final String createRepository(final String name, final Settings settings, final boolean verify) {
logger.info("--> creating repository [name: {}, verify: {}, settings: {}]", name, verify, settings);
assertAcked(client().admin().cluster().preparePutRepository(name)
.setType(repositoryType())
Expand All @@ -98,14 +101,23 @@ protected final String createRepository(final String name, final Settings settin
internalCluster().getDataOrMasterNodeInstances(RepositoriesService.class).forEach(repositories -> {
assertThat(repositories.repository(name), notNullValue());
assertThat(repositories.repository(name), instanceOf(BlobStoreRepository.class));
assertThat(repositories.repository(name).isReadOnly(), is(false));
assertThat(repositories.repository(name).isReadOnly(), is(settings.getAsBoolean("readonly", false)));
BlobStore blobStore = ((BlobStoreRepository) repositories.repository(name)).getBlobStore();
assertThat("blob store has to be lazy initialized", blobStore, verify ? is(notNullValue()) : is(nullValue()));
});

return name;
}

protected final void deleteRepository(final String name) {
logger.debug("--> deleting repository [name: {}]", name);
assertAcked(client().admin().cluster().prepareDeleteRepository(name));
internalCluster().getDataOrMasterNodeInstances(RepositoriesService.class).forEach(repositories -> {
RepositoryMissingException e = expectThrows(RepositoryMissingException.class, () -> repositories.repository(name));
assertThat(e.repository(), equalTo(name));
});
}

public void testReadNonExistingPath() throws IOException {
try (BlobStore store = newBlobStore()) {
final BlobContainer container = store.blobContainer(new BlobPath());
Expand Down Expand Up @@ -176,7 +188,7 @@ public void testList() throws IOException {
BlobMetadata blobMetadata = blobs.get(generated.getKey());
assertThat(generated.getKey(), blobMetadata, CoreMatchers.notNullValue());
assertThat(blobMetadata.name(), CoreMatchers.equalTo(generated.getKey()));
assertThat(blobMetadata.length(), CoreMatchers.equalTo(generated.getValue()));
assertThat(blobMetadata.length(), CoreMatchers.equalTo(blobLengthFromContentLength(generated.getValue())));
}

assertThat(container.listBlobsByPrefix("foo-").size(), CoreMatchers.equalTo(numberOfFooBlobs));
Expand Down Expand Up @@ -259,15 +271,25 @@ protected static void writeBlob(BlobContainer container, String blobName, BytesA
}

protected BlobStore newBlobStore() {
final String repository = createRepository(randomName());
final String repository = createRepository(randomRepositoryName());
return newBlobStore(repository);
}

protected BlobStore newBlobStore(String repository) {
final BlobStoreRepository blobStoreRepository =
(BlobStoreRepository) internalCluster().getMasterNodeInstance(RepositoriesService.class).repository(repository);
return PlainActionFuture.get(
f -> blobStoreRepository.threadPool().generic().execute(ActionRunnable.supply(f, blobStoreRepository::blobStore)));
}

public void testSnapshotAndRestore() throws Exception {
final String repoName = createRepository(randomName());
testSnapshotAndRestore(randomBoolean());
}

protected void testSnapshotAndRestore(boolean recreateRepositoryBeforeRestore) throws Exception {
final String repoName = randomRepositoryName();
final Settings repoSettings = repositorySettings(repoName);
createRepository(repoName, repoSettings, randomBoolean());
int indexCount = randomIntBetween(1, 5);
int[] docCounts = new int[indexCount];
String[] indexNames = generateRandomNames(indexCount);
Expand Down Expand Up @@ -315,6 +337,11 @@ public void testSnapshotAndRestore() throws Exception {
assertAcked(client().admin().indices().prepareClose(closeIndices.toArray(new String[closeIndices.size()])));
}

if (recreateRepositoryBeforeRestore) {
deleteRepository(repoName);
createRepository(repoName, repoSettings, randomBoolean());
}

logger.info("--> restore all indices from the snapshot");
assertSuccessfulRestore(client().admin().cluster().prepareRestoreSnapshot(repoName, snapshotName).setWaitForCompletion(true));

Expand All @@ -339,7 +366,7 @@ public void testSnapshotAndRestore() throws Exception {
}

public void testMultipleSnapshotAndRollback() throws Exception {
final String repoName = createRepository(randomName());
final String repoName = createRepository(randomRepositoryName());
int iterationCount = randomIntBetween(2, 5);
int[] docCounts = new int[iterationCount];
String indexName = randomName();
Expand Down Expand Up @@ -394,7 +421,7 @@ public void testMultipleSnapshotAndRollback() throws Exception {
}

public void testIndicesDeletedFromRepository() throws Exception {
final String repoName = createRepository("test-repo");
final String repoName = createRepository(randomRepositoryName());
Client client = client();
createIndex("test-idx-1", "test-idx-2", "test-idx-3");
ensureGreen();
Expand Down Expand Up @@ -491,7 +518,15 @@ private static void assertSuccessfulRestore(RestoreSnapshotResponse response) {
assertThat(response.getRestoreInfo().successfulShards(), equalTo(response.getRestoreInfo().totalShards()));
}

protected static String randomName() {
protected String randomName() {
return randomAlphaOfLength(randomIntBetween(1, 10)).toLowerCase(Locale.ROOT);
}

protected String randomRepositoryName() {
return randomName();
}

protected long blobLengthFromContentLength(long contentLength) {
return contentLength;
}
}
Loading