|
9 | 9 |
|
10 | 10 | import org.apache.lucene.search.TotalHits; |
11 | 11 | import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; |
| 12 | +import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; |
| 13 | +import org.elasticsearch.cluster.metadata.IndexMetadata; |
12 | 14 | import org.elasticsearch.common.settings.Settings; |
13 | 15 | import org.elasticsearch.common.unit.ByteSizeValue; |
14 | 16 | import org.elasticsearch.repositories.fs.FsRepository; |
| 17 | +import org.elasticsearch.snapshots.SnapshotRestoreException; |
| 18 | +import org.hamcrest.Matcher; |
15 | 19 |
|
16 | 20 | import java.util.Arrays; |
17 | 21 | import java.util.List; |
18 | 22 | import java.util.Locale; |
19 | 23 |
|
20 | 24 | import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_NUMBER_OF_SHARDS_SETTING; |
21 | 25 | import static org.elasticsearch.index.IndexSettings.INDEX_SOFT_DELETES_SETTING; |
| 26 | +import static org.elasticsearch.repositories.RepositoriesService.SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION; |
22 | 27 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; |
23 | 28 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; |
24 | 29 | import static org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest.Storage; |
| 30 | +import static org.hamcrest.Matchers.allOf; |
25 | 31 | import static org.hamcrest.Matchers.containsString; |
| 32 | +import static org.hamcrest.Matchers.equalTo; |
26 | 33 |
|
27 | 34 | public class SearchableSnapshotsRepositoryIntegTests extends BaseFrozenSearchableSnapshotsIntegTestCase { |
28 | 35 |
|
@@ -110,4 +117,153 @@ public void testRepositoryUsedBySearchableSnapshotCanBeUpdatedButNotUnregistered |
110 | 117 |
|
111 | 118 | assertAcked(clusterAdmin().prepareDeleteRepository(updatedRepositoryName)); |
112 | 119 | } |
| 120 | + |
| 121 | + public void testMountIndexWithDeletionOfSnapshotFailsIfNotSingleIndexSnapshot() throws Exception { |
| 122 | + final String repository = "repository-" + getTestName().toLowerCase(Locale.ROOT); |
| 123 | + createRepository(repository, FsRepository.TYPE, randomRepositorySettings()); |
| 124 | + |
| 125 | + final int nbIndices = randomIntBetween(2, 5); |
| 126 | + for (int i = 0; i < nbIndices; i++) { |
| 127 | + createAndPopulateIndex( |
| 128 | + "index-" + i, |
| 129 | + Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0).put(INDEX_SOFT_DELETES_SETTING.getKey(), true) |
| 130 | + ); |
| 131 | + } |
| 132 | + |
| 133 | + final String snapshot = "snapshot"; |
| 134 | + createFullSnapshot(repository, snapshot); |
| 135 | + assertAcked(client().admin().indices().prepareDelete("index-*")); |
| 136 | + |
| 137 | + final String index = "index-" + randomInt(nbIndices - 1); |
| 138 | + final String mountedIndex = "mounted-" + index; |
| 139 | + |
| 140 | + final SnapshotRestoreException exception = expectThrows( |
| 141 | + SnapshotRestoreException.class, |
| 142 | + () -> mountSnapshot(repository, snapshot, index, mountedIndex, deleteSnapshotIndexSettings(true), randomFrom(Storage.values())) |
| 143 | + ); |
| 144 | + assertThat( |
| 145 | + exception.getMessage(), |
| 146 | + allOf( |
| 147 | + containsString("cannot mount snapshot [" + repository + '/'), |
| 148 | + containsString(snapshot + "] as index [" + mountedIndex + "] with the deletion of snapshot on index removal enabled"), |
| 149 | + containsString("[index.store.snapshot.delete_searchable_snapshot: true]; "), |
| 150 | + containsString("snapshot contains [" + nbIndices + "] indices instead of 1.") |
| 151 | + ) |
| 152 | + ); |
| 153 | + } |
| 154 | + |
| 155 | + public void testMountIndexWithDeletionOfSnapshot() throws Exception { |
| 156 | + final String repository = "repository-" + getTestName().toLowerCase(Locale.ROOT); |
| 157 | + createRepository(repository, FsRepository.TYPE, randomRepositorySettings()); |
| 158 | + |
| 159 | + final String index = "index"; |
| 160 | + createAndPopulateIndex(index, Settings.builder().put(INDEX_SOFT_DELETES_SETTING.getKey(), true)); |
| 161 | + |
| 162 | + final TotalHits totalHits = internalCluster().client().prepareSearch(index).setTrackTotalHits(true).get().getHits().getTotalHits(); |
| 163 | + |
| 164 | + final String snapshot = "snapshot"; |
| 165 | + createSnapshot(repository, snapshot, List.of(index)); |
| 166 | + assertAcked(client().admin().indices().prepareDelete(index)); |
| 167 | + |
| 168 | + String mounted = "mounted-with-setting-enabled"; |
| 169 | + mountSnapshot(repository, snapshot, index, mounted, deleteSnapshotIndexSettings(true), randomFrom(Storage.values())); |
| 170 | + assertIndexSetting(mounted, SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, equalTo("true")); |
| 171 | + assertHitCount(client().prepareSearch(mounted).setTrackTotalHits(true).get(), totalHits.value); |
| 172 | + |
| 173 | + // the snapshot is already mounted as an index with "index.store.snapshot.delete_searchable_snapshot: true", |
| 174 | + // any attempt to mount the snapshot again should fail |
| 175 | + final String mountedAgain = randomValueOtherThan(mounted, () -> randomAlphaOfLength(10).toLowerCase(Locale.ROOT)); |
| 176 | + SnapshotRestoreException exception = expectThrows( |
| 177 | + SnapshotRestoreException.class, |
| 178 | + () -> mountSnapshot(repository, snapshot, index, mountedAgain, deleteSnapshotIndexSettings(randomBoolean())) |
| 179 | + ); |
| 180 | + assertThat( |
| 181 | + exception.getMessage(), |
| 182 | + allOf( |
| 183 | + containsString("cannot mount snapshot [" + repository + '/'), |
| 184 | + containsString(':' + snapshot + "] as index [" + mountedAgain + "]; another index [" + mounted + '/'), |
| 185 | + containsString("] uses the snapshot with the deletion of snapshot on index removal enabled "), |
| 186 | + containsString("[index.store.snapshot.delete_searchable_snapshot: true].") |
| 187 | + ) |
| 188 | + ); |
| 189 | + |
| 190 | + assertAcked(client().admin().indices().prepareDelete(mounted)); |
| 191 | + mounted = "mounted-with-setting-disabled"; |
| 192 | + mountSnapshot(repository, snapshot, index, mounted, deleteSnapshotIndexSettings(false), randomFrom(Storage.values())); |
| 193 | + assertIndexSetting(mounted, SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, equalTo("false")); |
| 194 | + assertHitCount(client().prepareSearch(mounted).setTrackTotalHits(true).get(), totalHits.value); |
| 195 | + |
| 196 | + // the snapshot is now mounted as an index with "index.store.snapshot.delete_searchable_snapshot: false", |
| 197 | + // any attempt to mount the snapshot again with "delete_searchable_snapshot: true" should fail |
| 198 | + exception = expectThrows( |
| 199 | + SnapshotRestoreException.class, |
| 200 | + () -> mountSnapshot(repository, snapshot, index, mountedAgain, deleteSnapshotIndexSettings(true)) |
| 201 | + ); |
| 202 | + assertThat( |
| 203 | + exception.getMessage(), |
| 204 | + allOf( |
| 205 | + containsString("cannot mount snapshot [" + repository + '/'), |
| 206 | + containsString(snapshot + "] as index [" + mountedAgain + "] with the deletion of snapshot on index removal enabled"), |
| 207 | + containsString("[index.store.snapshot.delete_searchable_snapshot: true]; another index [" + mounted + '/'), |
| 208 | + containsString("] uses the snapshot.") |
| 209 | + ) |
| 210 | + ); |
| 211 | + |
| 212 | + // but we can continue to mount the snapshot, as long as it does not require the cascade deletion of the snapshot |
| 213 | + mountSnapshot(repository, snapshot, index, mountedAgain, deleteSnapshotIndexSettings(false)); |
| 214 | + assertIndexSetting(mountedAgain, SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, equalTo("false")); |
| 215 | + assertHitCount(client().prepareSearch(mountedAgain).setTrackTotalHits(true).get(), totalHits.value); |
| 216 | + |
| 217 | + assertAcked(client().admin().indices().prepareDelete(mountedAgain)); |
| 218 | + assertAcked(client().admin().indices().prepareDelete(mounted)); |
| 219 | + } |
| 220 | + |
| 221 | + public void testDeletionOfSnapshotSettingCannotBeUpdated() throws Exception { |
| 222 | + final String repository = "repository-" + getTestName().toLowerCase(Locale.ROOT); |
| 223 | + createRepository(repository, FsRepository.TYPE, randomRepositorySettings()); |
| 224 | + |
| 225 | + final String index = "index"; |
| 226 | + createAndPopulateIndex(index, Settings.builder().put(INDEX_SOFT_DELETES_SETTING.getKey(), true)); |
| 227 | + |
| 228 | + final TotalHits totalHits = internalCluster().client().prepareSearch(index).setTrackTotalHits(true).get().getHits().getTotalHits(); |
| 229 | + |
| 230 | + final String snapshot = "snapshot"; |
| 231 | + createSnapshot(repository, snapshot, List.of(index)); |
| 232 | + assertAcked(client().admin().indices().prepareDelete(index)); |
| 233 | + |
| 234 | + final String mounted = "mounted-" + index; |
| 235 | + final boolean deleteSnapshot = randomBoolean(); |
| 236 | + |
| 237 | + mountSnapshot(repository, snapshot, index, mounted, deleteSnapshotIndexSettings(deleteSnapshot), randomFrom(Storage.values())); |
| 238 | + assertIndexSetting(mounted, SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, equalTo(Boolean.toString(deleteSnapshot))); |
| 239 | + assertHitCount(client().prepareSearch(mounted).setTrackTotalHits(true).get(), totalHits.value); |
| 240 | + |
| 241 | + final IllegalArgumentException exception = expectThrows( |
| 242 | + IllegalArgumentException.class, |
| 243 | + () -> client().admin() |
| 244 | + .indices() |
| 245 | + .prepareUpdateSettings(mounted) |
| 246 | + .setSettings(deleteSnapshotIndexSettings(deleteSnapshot == false)) |
| 247 | + .get() |
| 248 | + ); |
| 249 | + assertThat( |
| 250 | + exception.getMessage(), |
| 251 | + containsString("can not update private setting [index.store.snapshot.delete_searchable_snapshot]; ") |
| 252 | + ); |
| 253 | + |
| 254 | + assertAcked(client().admin().indices().prepareDelete(mounted)); |
| 255 | + } |
| 256 | + |
| 257 | + private static Settings deleteSnapshotIndexSettings(boolean value) { |
| 258 | + return Settings.builder().put(SEARCHABLE_SNAPSHOTS_DELETE_SNAPSHOT_ON_INDEX_DELETION, value).build(); |
| 259 | + } |
| 260 | + |
| 261 | + private static void assertIndexSetting(String indexName, String indexSettingName, Matcher<String> matcher) { |
| 262 | + final GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings(indexName).get(); |
| 263 | + assertThat( |
| 264 | + "Unexpected value for setting [" + indexSettingName + "] of index [" + indexName + ']', |
| 265 | + getSettingsResponse.getSetting(indexName, indexSettingName), |
| 266 | + matcher |
| 267 | + ); |
| 268 | + } |
113 | 269 | } |
0 commit comments