Snapshot/Restore: Add snapshot name validation logic to all snapshot operation

Make sure snapshot name validation occurs earlier in all snapshot operations.
This commit is contained in:
Igor Motov 2015-06-11 19:52:02 -04:00
parent 36da42c93b
commit b2000a48a4
4 changed files with 68 additions and 19 deletions

View File

@ -19,14 +19,13 @@
package org.elasticsearch.snapshots;
import org.elasticsearch.ElasticsearchWrapperException;
import org.elasticsearch.cluster.metadata.SnapshotId;
import org.elasticsearch.rest.RestStatus;
/**
* Thrown on the attempt to create a snapshot with invalid name
*/
public class InvalidSnapshotNameException extends SnapshotException implements ElasticsearchWrapperException {
public class InvalidSnapshotNameException extends SnapshotException {
public InvalidSnapshotNameException(SnapshotId snapshot, String desc) {
super(snapshot, "Invalid snapshot name [" + snapshot.getSnapshot() + "], " + desc);

View File

@ -25,7 +25,7 @@ import org.elasticsearch.cluster.metadata.SnapshotId;
/**
* Thrown when snapshot creation fails completely
*/
public class SnapshotCreationException extends SnapshotException implements ElasticsearchWrapperException {
public class SnapshotCreationException extends SnapshotException {
public SnapshotCreationException(SnapshotId snapshot, String message) {
super(snapshot, message);

View File

@ -135,6 +135,7 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
* @throws SnapshotMissingException if snapshot is not found
*/
public Snapshot snapshot(SnapshotId snapshotId) {
validate(snapshotId);
List<SnapshotsInProgress.Entry> entries = currentSnapshots(snapshotId.getRepository(), new String[]{snapshotId.getSnapshot()});
if (!entries.isEmpty()) {
return inProgressSnapshot(entries.iterator().next());
@ -191,6 +192,7 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
*/
public void createSnapshot(final SnapshotRequest request, final CreateSnapshotListener listener) {
final SnapshotId snapshotId = new SnapshotId(request.repository(), request.name());
validate(snapshotId);
clusterService.submitStateUpdateTask(request.cause(), new TimeoutClusterStateUpdateTask() {
private SnapshotsInProgress.Entry newSnapshot = null;
@ -253,26 +255,31 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
if (repositoriesMetaData == null || repositoriesMetaData.repository(request.repository()) == null) {
throw new RepositoryMissingException(request.repository());
}
if (!Strings.hasLength(request.name())) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "cannot be empty");
validate(new SnapshotId(request.repository(), request.name()));
}
private static void validate(SnapshotId snapshotId) {
String name = snapshotId.getSnapshot();
if (!Strings.hasLength(name)) {
throw new InvalidSnapshotNameException(snapshotId, "cannot be empty");
}
if (request.name().contains(" ")) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must not contain whitespace");
if (name.contains(" ")) {
throw new InvalidSnapshotNameException(snapshotId, "must not contain whitespace");
}
if (request.name().contains(",")) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must not contain ','");
if (name.contains(",")) {
throw new InvalidSnapshotNameException(snapshotId, "must not contain ','");
}
if (request.name().contains("#")) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must not contain '#'");
if (name.contains("#")) {
throw new InvalidSnapshotNameException(snapshotId, "must not contain '#'");
}
if (request.name().charAt(0) == '_') {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must not start with '_'");
if (name.charAt(0) == '_') {
throw new InvalidSnapshotNameException(snapshotId, "must not start with '_'");
}
if (!request.name().toLowerCase(Locale.ROOT).equals(request.name())) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must be lowercase");
if (!name.toLowerCase(Locale.ROOT).equals(name)) {
throw new InvalidSnapshotNameException(snapshotId, "must be lowercase");
}
if (!Strings.validFileName(request.name())) {
throw new InvalidSnapshotNameException(new SnapshotId(request.repository(), request.name()), "must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
if (!Strings.validFileName(name)) {
throw new InvalidSnapshotNameException(snapshotId, "must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
}
}
@ -316,7 +323,6 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
@Override
public ClusterState execute(ClusterState currentState) {
MetaData metaData = currentState.metaData();
SnapshotsInProgress snapshots = currentState.custom(SnapshotsInProgress.TYPE);
ImmutableList.Builder<SnapshotsInProgress.Entry> entries = ImmutableList.builder();
for (SnapshotsInProgress.Entry entry : snapshots.entries()) {
@ -472,6 +478,7 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
* @return map of shard id to snapshot status
*/
public ImmutableMap<ShardId, IndexShardSnapshotStatus> currentSnapshotShards(SnapshotId snapshotId) {
validate(snapshotId);
SnapshotShards snapshotShards = shardSnapshots.get(snapshotId);
if (snapshotShards == null) {
return null;
@ -491,6 +498,7 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
* @return map of shard id to snapshot status
*/
public ImmutableMap<ShardId, IndexShardSnapshotStatus> snapshotShards(SnapshotId snapshotId) throws IOException {
validate(snapshotId);
ImmutableMap.Builder<ShardId, IndexShardSnapshotStatus> shardStatusBuilder = ImmutableMap.builder();
Repository repository = repositoriesService.repository(snapshotId.getRepository());
IndexShardRepository indexShardRepository = repositoriesService.indexShardRepository(snapshotId.getRepository());
@ -1174,6 +1182,7 @@ public class SnapshotsService extends AbstractLifecycleComponent<SnapshotsServic
* @param listener listener
*/
public void deleteSnapshot(final SnapshotId snapshotId, final DeleteSnapshotListener listener) {
validate(snapshotId);
clusterService.submitStateUpdateTask("delete snapshot", new ProcessedClusterStateUpdateTask() {
boolean waitForSnapshot = false;

View File

@ -1887,4 +1887,45 @@ public class SharedClusterSnapshotRestoreTests extends AbstractSnapshotTests {
// Check that cluster state update task was called only once
assertEquals(1, restoreListener.count());
}
}
@Test
public void snapshotNameTest() throws Exception {
final Client client = client();
logger.info("--> creating repository");
assertAcked(client.admin().cluster().preparePutRepository("test-repo")
.setType("fs").setSettings(Settings.settingsBuilder()
.put("location", randomRepoPath())
.put("compress", randomBoolean())
.put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));
try {
client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("_foo").get();
fail("shouldn't be here");
} catch (InvalidSnapshotNameException ex) {
assertThat(ex.getMessage(), containsString("Invalid snapshot name"));
}
try {
client.admin().cluster().prepareCreateSnapshot("test-repo", "_foo").get();
fail("shouldn't be here");
} catch (InvalidSnapshotNameException ex) {
assertThat(ex.getMessage(), containsString("Invalid snapshot name"));
}
try {
client.admin().cluster().prepareDeleteSnapshot("test-repo", "_foo").get();
fail("shouldn't be here");
} catch (InvalidSnapshotNameException ex) {
assertThat(ex.getMessage(), containsString("Invalid snapshot name"));
}
try {
client.admin().cluster().prepareSnapshotStatus("test-repo").setSnapshots("_foo").get();
fail("shouldn't be here");
} catch (InvalidSnapshotNameException ex) {
assertThat(ex.getMessage(), containsString("Invalid snapshot name"));
}
}
}