|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * |
| 4 | + * The OpenSearch Contributors require contributions made to |
| 5 | + * this file be licensed under the Apache-2.0 license or a |
| 6 | + * compatible open source license. |
| 7 | + */ |
| 8 | + |
| 9 | +package org.opensearch.remotestore; |
| 10 | + |
| 11 | +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; |
| 12 | +import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; |
| 13 | +import org.opensearch.client.Client; |
| 14 | +import org.opensearch.cluster.metadata.IndexMetadata; |
| 15 | +import org.opensearch.common.settings.Settings; |
| 16 | +import org.opensearch.index.IndexSettings; |
| 17 | +import org.opensearch.plugins.Plugin; |
| 18 | +import org.opensearch.remotestore.multipart.mocks.MockFsRepositoryPlugin; |
| 19 | +import org.opensearch.repositories.blobstore.BlobStoreRepository; |
| 20 | +import org.opensearch.snapshots.AbstractSnapshotIntegTestCase; |
| 21 | +import org.opensearch.snapshots.SnapshotState; |
| 22 | +import org.opensearch.test.OpenSearchIntegTestCase; |
| 23 | +import org.junit.After; |
| 24 | +import org.junit.Before; |
| 25 | + |
| 26 | +import java.io.IOException; |
| 27 | +import java.nio.file.Path; |
| 28 | +import java.util.Arrays; |
| 29 | +import java.util.Collection; |
| 30 | +import java.util.stream.Collectors; |
| 31 | +import java.util.stream.Stream; |
| 32 | + |
| 33 | +import static org.opensearch.remotestore.RemoteStoreBaseIntegTestCase.remoteStoreClusterSettings; |
| 34 | +import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked; |
| 35 | +import static org.hamcrest.Matchers.equalTo; |
| 36 | +import static org.hamcrest.Matchers.greaterThan; |
| 37 | + |
| 38 | +@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0) |
| 39 | +public class RemoteSnapshotFailureScenariosIT extends AbstractSnapshotIntegTestCase { |
| 40 | + private static final String BASE_REMOTE_REPO = "test-rs-repo" + TEST_REMOTE_STORE_REPO_SUFFIX; |
| 41 | + private static final String SNAP_REPO = "test-snap-repo"; |
| 42 | + private static final String INDEX_NAME_1 = "testindex1"; |
| 43 | + private static final String RESTORED_INDEX_NAME_1 = INDEX_NAME_1 + "-restored"; |
| 44 | + private static final String INDEX_NAME_2 = "testindex2"; |
| 45 | + private static final String RESTORED_INDEX_NAME_2 = INDEX_NAME_2 + "-restored"; |
| 46 | + |
| 47 | + private Path remoteRepoPath; |
| 48 | + private Path snapRepoPath; |
| 49 | + |
| 50 | + @Override |
| 51 | + protected Collection<Class<? extends Plugin>> nodePlugins() { |
| 52 | + return Stream.concat(super.nodePlugins().stream(), Stream.of(MockFsRepositoryPlugin.class)).collect(Collectors.toList()); |
| 53 | + } |
| 54 | + |
| 55 | + @Before |
| 56 | + public void setup() { |
| 57 | + remoteRepoPath = randomRepoPath().toAbsolutePath(); |
| 58 | + snapRepoPath = randomRepoPath().toAbsolutePath(); |
| 59 | + } |
| 60 | + |
| 61 | + @After |
| 62 | + public void teardown() { |
| 63 | + clusterAdmin().prepareCleanupRepository(BASE_REMOTE_REPO).get(); |
| 64 | + clusterAdmin().prepareCleanupRepository(SNAP_REPO).get(); |
| 65 | + } |
| 66 | + |
| 67 | + @Override |
| 68 | + protected Settings nodeSettings(int nodeOrdinal) { |
| 69 | + return Settings.builder() |
| 70 | + .put(super.nodeSettings(nodeOrdinal)) |
| 71 | + .put(remoteStoreClusterSettings(BASE_REMOTE_REPO, remoteRepoPath, "mock", BASE_REMOTE_REPO, remoteRepoPath, "mock")) |
| 72 | + .build(); |
| 73 | + } |
| 74 | + |
| 75 | + private Settings.Builder getIndexSettings(int numOfShards, int numOfReplicas) { |
| 76 | + Settings.Builder settingsBuilder = Settings.builder() |
| 77 | + .put(super.indexSettings()) |
| 78 | + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, numOfShards) |
| 79 | + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, numOfReplicas) |
| 80 | + .put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), "300s"); |
| 81 | + return settingsBuilder; |
| 82 | + } |
| 83 | + |
| 84 | + private void indexDocuments(Client client, String indexName, int numOfDocs) { |
| 85 | + indexDocuments(client, indexName, 0, numOfDocs); |
| 86 | + } |
| 87 | + |
| 88 | + private void indexDocuments(Client client, String indexName, int fromId, int toId) { |
| 89 | + for (int i = fromId; i < toId; i++) { |
| 90 | + String id = Integer.toString(i); |
| 91 | + client.prepareIndex(indexName).setId(id).setSource("text", "sometext").get(); |
| 92 | + } |
| 93 | + client.admin().indices().prepareFlush(indexName).get(); |
| 94 | + } |
| 95 | + |
| 96 | + public void testRestoreDownloadFromRemoteStore() { |
| 97 | + String snapshotName1 = "test-restore-snapshot1"; |
| 98 | + |
| 99 | + createRepository(SNAP_REPO, "fs", getRepositorySettings(snapRepoPath, true)); |
| 100 | + |
| 101 | + Client client = client(); |
| 102 | + Settings indexSettings = getIndexSettings(1, 0).build(); |
| 103 | + createIndex(INDEX_NAME_1, indexSettings); |
| 104 | + |
| 105 | + Settings indexSettings2 = getIndexSettings(1, 0).build(); |
| 106 | + createIndex(INDEX_NAME_2, indexSettings2); |
| 107 | + |
| 108 | + final int numDocsInIndex1 = 5; |
| 109 | + final int numDocsInIndex2 = 6; |
| 110 | + indexDocuments(client, INDEX_NAME_1, numDocsInIndex1); |
| 111 | + indexDocuments(client, INDEX_NAME_2, numDocsInIndex2); |
| 112 | + ensureGreen(INDEX_NAME_1, INDEX_NAME_2); |
| 113 | + |
| 114 | + logger.info("--> snapshot"); |
| 115 | + CreateSnapshotResponse createSnapshotResponse = client.admin() |
| 116 | + .cluster() |
| 117 | + .prepareCreateSnapshot(SNAP_REPO, snapshotName1) |
| 118 | + .setWaitForCompletion(true) |
| 119 | + .setIndices(INDEX_NAME_1, INDEX_NAME_2) |
| 120 | + .get(); |
| 121 | + assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0)); |
| 122 | + assertThat( |
| 123 | + createSnapshotResponse.getSnapshotInfo().successfulShards(), |
| 124 | + equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()) |
| 125 | + ); |
| 126 | + assertThat(createSnapshotResponse.getSnapshotInfo().state(), equalTo(SnapshotState.SUCCESS)); |
| 127 | + assertTrue(createSnapshotResponse.getSnapshotInfo().isRemoteStoreIndexShallowCopyEnabled()); |
| 128 | + |
| 129 | + logger.info("--> updating repository to induce remote store upload failure"); |
| 130 | + assertAcked( |
| 131 | + client.admin() |
| 132 | + .cluster() |
| 133 | + .preparePutRepository(BASE_REMOTE_REPO) |
| 134 | + .setType("mock") |
| 135 | + .setSettings( |
| 136 | + Settings.builder().put("location", remoteRepoPath).put("regexes_to_fail_io", ".si").put("max_failure_number", 6L) |
| 137 | + ) |
| 138 | + ); // we retry IO 5 times, keeping it 6 so that first read for single segment file will fail |
| 139 | + |
| 140 | + RestoreSnapshotResponse restoreSnapshotResponse = client.admin() |
| 141 | + .cluster() |
| 142 | + .prepareRestoreSnapshot(SNAP_REPO, snapshotName1) |
| 143 | + .setWaitForCompletion(true) |
| 144 | + .setIndices(INDEX_NAME_1) |
| 145 | + .setRenamePattern(INDEX_NAME_1) |
| 146 | + .setRenameReplacement(RESTORED_INDEX_NAME_1) |
| 147 | + .get(); |
| 148 | + |
| 149 | + ensureRed(RESTORED_INDEX_NAME_1); |
| 150 | + assertEquals(1, restoreSnapshotResponse.getRestoreInfo().failedShards()); |
| 151 | + |
| 152 | + assertAcked(client().admin().indices().prepareClose(RESTORED_INDEX_NAME_1).get()); |
| 153 | + restoreSnapshotResponse = client.admin() |
| 154 | + .cluster() |
| 155 | + .prepareRestoreSnapshot(SNAP_REPO, snapshotName1) |
| 156 | + .setWaitForCompletion(true) |
| 157 | + .setIndices(INDEX_NAME_1) |
| 158 | + .setRenamePattern(INDEX_NAME_1) |
| 159 | + .setRenameReplacement(RESTORED_INDEX_NAME_1) |
| 160 | + .get(); |
| 161 | + |
| 162 | + ensureGreen(RESTORED_INDEX_NAME_1); |
| 163 | + assertEquals(0, restoreSnapshotResponse.getRestoreInfo().failedShards()); |
| 164 | + |
| 165 | + // resetting repository to original settings. |
| 166 | + logger.info("--> removing repository settings overrides"); |
| 167 | + assertAcked( |
| 168 | + client.admin() |
| 169 | + .cluster() |
| 170 | + .preparePutRepository(BASE_REMOTE_REPO) |
| 171 | + .setType("mock") |
| 172 | + .setSettings(Settings.builder().put("location", remoteRepoPath)) |
| 173 | + ); |
| 174 | + } |
| 175 | + |
| 176 | + public void testAcquireLockFails() throws IOException { |
| 177 | + final Client client = client(); |
| 178 | + |
| 179 | + logger.info("--> creating snapshot repository"); |
| 180 | + createRepository(SNAP_REPO, "mock", getRepositorySettings(snapRepoPath, true)); |
| 181 | + |
| 182 | + logger.info("--> updating remote store repository to induce lock file upload failure"); |
| 183 | + assertAcked( |
| 184 | + client.admin() |
| 185 | + .cluster() |
| 186 | + .preparePutRepository(BASE_REMOTE_REPO) |
| 187 | + .setType("mock") |
| 188 | + .setSettings(Settings.builder().put("location", remoteRepoPath).put("regexes_to_fail_io", "lock$")) |
| 189 | + ); |
| 190 | + |
| 191 | + logger.info("--> creating indices and index documents"); |
| 192 | + Settings indexSettings = getIndexSettings(1, 0).build(); |
| 193 | + createIndex(INDEX_NAME_1, indexSettings); |
| 194 | + createIndex(INDEX_NAME_2, indexSettings); |
| 195 | + |
| 196 | + final int numDocsInIndex1 = 5; |
| 197 | + final int numDocsInIndex2 = 6; |
| 198 | + indexDocuments(client, INDEX_NAME_1, numDocsInIndex1); |
| 199 | + indexDocuments(client, INDEX_NAME_2, numDocsInIndex2); |
| 200 | + ensureGreen(INDEX_NAME_1, INDEX_NAME_2); |
| 201 | + |
| 202 | + logger.info("--> create first shallow snapshot"); |
| 203 | + CreateSnapshotResponse createSnapshotResponse = client.admin() |
| 204 | + .cluster() |
| 205 | + .prepareCreateSnapshot(SNAP_REPO, "test-snap") |
| 206 | + .setWaitForCompletion(true) |
| 207 | + .setIndices(INDEX_NAME_1, INDEX_NAME_2) |
| 208 | + .get(); |
| 209 | + assertTrue(createSnapshotResponse.getSnapshotInfo().isRemoteStoreIndexShallowCopyEnabled()); |
| 210 | + assertTrue(createSnapshotResponse.getSnapshotInfo().failedShards() > 0); |
| 211 | + assertSame(createSnapshotResponse.getSnapshotInfo().state(), SnapshotState.PARTIAL); |
| 212 | + logger.info("--> delete partial snapshot"); |
| 213 | + assertAcked(client().admin().cluster().prepareDeleteSnapshot(SNAP_REPO, "test-snap").get()); |
| 214 | + // resetting repository to original settings. |
| 215 | + logger.info("--> removing repository settings overrides"); |
| 216 | + assertAcked( |
| 217 | + client.admin() |
| 218 | + .cluster() |
| 219 | + .preparePutRepository(BASE_REMOTE_REPO) |
| 220 | + .setType("mock") |
| 221 | + .setSettings(Settings.builder().put("location", remoteRepoPath)) |
| 222 | + ); |
| 223 | + } |
| 224 | + |
| 225 | + public void testWriteShallowSnapFileFails() throws IOException, InterruptedException { |
| 226 | + final Client client = client(); |
| 227 | + |
| 228 | + Settings.Builder snapshotRepoSettingsBuilder = randomRepositorySettings().put( |
| 229 | + BlobStoreRepository.REMOTE_STORE_INDEX_SHALLOW_COPY.getKey(), |
| 230 | + Boolean.TRUE |
| 231 | + ).putList("regexes_to_fail_io", "^" + BlobStoreRepository.SHALLOW_SNAPSHOT_PREFIX); |
| 232 | + |
| 233 | + logger.info("--> creating snapshot repository"); |
| 234 | + createRepository(SNAP_REPO, "mock", snapshotRepoSettingsBuilder); |
| 235 | + |
| 236 | + logger.info("--> creating indices and index documents"); |
| 237 | + Settings indexSettings = getIndexSettings(1, 0).build(); |
| 238 | + createIndex(INDEX_NAME_1, indexSettings); |
| 239 | + createIndex(INDEX_NAME_2, indexSettings); |
| 240 | + |
| 241 | + final int numDocsInIndex1 = 5; |
| 242 | + final int numDocsInIndex2 = 6; |
| 243 | + indexDocuments(client, INDEX_NAME_1, numDocsInIndex1); |
| 244 | + indexDocuments(client, INDEX_NAME_2, numDocsInIndex2); |
| 245 | + ensureGreen(INDEX_NAME_1, INDEX_NAME_2); |
| 246 | + |
| 247 | + logger.info("--> create first shallow snapshot"); |
| 248 | + CreateSnapshotResponse createSnapshotResponse = client().admin() |
| 249 | + .cluster() |
| 250 | + .prepareCreateSnapshot(SNAP_REPO, "test-snap") |
| 251 | + .setWaitForCompletion(true) |
| 252 | + .setIndices(INDEX_NAME_1, INDEX_NAME_2) |
| 253 | + .get(); |
| 254 | + assertTrue(createSnapshotResponse.getSnapshotInfo().isRemoteStoreIndexShallowCopyEnabled()); |
| 255 | + |
| 256 | + assertTrue(createSnapshotResponse.getSnapshotInfo().failedShards() > 0); |
| 257 | + assertSame(createSnapshotResponse.getSnapshotInfo().state(), SnapshotState.PARTIAL); |
| 258 | + String[] lockFiles = getLockFilesInRemoteStore(INDEX_NAME_1, BASE_REMOTE_REPO); |
| 259 | + assertEquals("there should be no lock files, but found " + Arrays.toString(lockFiles), 0, lockFiles.length); |
| 260 | + assertAcked(client().admin().cluster().prepareDeleteSnapshot(SNAP_REPO, "test-snap").get()); |
| 261 | + } |
| 262 | +} |
0 commit comments