/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.integrationtests; import com.google.common.base.Joiner; import io.crate.action.sql.SQLActionException; import io.crate.testing.TestingHelpers; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.util.List; import java.util.Locale; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class SnapshotRestoreIntegrationTest extends SQLTransportIntegrationTest { private static final String REPOSITORY_NAME = "my_repo"; private static final String SNAPSHOT_NAME = "my_snapshot"; @ClassRule public static final TemporaryFolder TEMPORARY_FOLDER = new TemporaryFolder(); private File defaultRepositoryLocation; @Override protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder().put(super.nodeSettings(nodeOrdinal)) .put("path.repo", TEMPORARY_FOLDER.getRoot().getAbsolutePath()) .build(); } @Before public void createRepository() throws Exception { defaultRepositoryLocation = TEMPORARY_FOLDER.newFolder(); execute("CREATE REPOSITORY " + REPOSITORY_NAME + " TYPE \"fs\" with (location=?, compress=True)", new Object[]{defaultRepositoryLocation.getAbsolutePath()}); assertThat(response.rowCount(), is(1L)); } private void createTableAndSnapshot(String tableName, String snapshotName) { createTableAndSnapshot(tableName, snapshotName, false); } private void createTableAndSnapshot(String tableName, String snapshotName, boolean partitioned) { createTable(tableName, partitioned); createSnapshot(snapshotName, tableName); } private void createTable(String tableName, boolean partitioned) { execute("CREATE TABLE " + tableName + " (" + " id long primary key, " + " name string, " + " date timestamp " + (partitioned ? "primary key," : ",") + " ft string index using fulltext with (analyzer='german')" + ") " + (partitioned ? "partitioned by (date) " : "") + "clustered into 1 shards with (number_of_replicas=0)"); ensureYellow(); execute("INSERT INTO " + tableName + " (id, name, date, ft) VALUES (?, ?, ?, ?)", new Object[][]{ {1L, "foo", "1970-01-01", "The quick brown fox jumps over the lazy dog."}, {2L, "bar", "2015-10-27T11:29:00+01:00", "Morgenstund hat Gold im Mund."}, {3L, "baz", "1989-11-09", "Reden ist Schweigen. Silber ist Gold."}, }); execute("REFRESH TABLE " + tableName); } private void createSnapshot(String snapshotName, String... tables) { execute("CREATE SNAPSHOT " + REPOSITORY_NAME + "." + snapshotName + " TABLE " + Joiner.on(", ").join(tables) + " WITH (wait_for_completion=true)"); assertThat(response.rowCount(), is(1L)); } private static String snapshotName() { return String.format(Locale.ENGLISH, "%s.%s", REPOSITORY_NAME, SNAPSHOT_NAME); } @Test public void testDropSnapshot() throws Exception { String snapshotName = "my_snap_1"; createTableAndSnapshot("my_table", snapshotName); execute("drop snapshot " + REPOSITORY_NAME + "." + snapshotName); assertThat(response.rowCount(), is(1L)); execute("select * from sys.snapshots where name = ?", new Object[]{snapshotName}); assertThat(response.rowCount(), is(0L)); } @Test public void testDropUnknownSnapshot() throws Exception { String snapshot = "unknown_snap"; expectedException.expect(SQLActionException.class); expectedException.expectMessage(String.format(Locale.ENGLISH, "Snapshot '%s.%s' unknown", REPOSITORY_NAME, snapshot)); execute("drop snapshot " + REPOSITORY_NAME + "." + snapshot); } @Test public void testDropSnapshotUnknownRepository() throws Exception { String repository = "unknown_repo"; String snapshot = "unknown_snap"; expectedException.expect(SQLActionException.class); expectedException.expectMessage(String.format(Locale.ENGLISH, "Repository '%s' unknown", repository)); execute("drop snapshot " + repository + "." + snapshot); } @Test public void testCreateSnapshot() throws Exception { createTable("backmeup", false); execute("CREATE SNAPSHOT " + snapshotName() + " TABLE backmeup WITH (wait_for_completion=true)"); assertThat(response.rowCount(), is(1L)); execute("select name, \"repository\", concrete_indices, state from sys.snapshots"); assertThat(TestingHelpers.printedTable(response.rows()), is("my_snapshot| my_repo| [backmeup]| SUCCESS\n")); } @Test public void testCreateSnapshotWithoutWaitForCompletion() throws Exception { // this test just verifies that no exception is thrown if wait_for_completion is false execute("CREATE SNAPSHOT my_repo.snapshot_no_wait ALL WITH (wait_for_completion=false)"); assertThat(response.rowCount(), is(1L)); waitForCompletion(REPOSITORY_NAME, "snapshot_no_wait", TimeValue.timeValueSeconds(20)); } private SnapshotInfo waitForCompletion(String repository, String snapshotName, TimeValue timeout) throws InterruptedException { long start = System.currentTimeMillis(); Snapshot snapshot = new Snapshot(repository, new SnapshotId(repository, snapshotName)); while (System.currentTimeMillis() - start < timeout.millis()) { List<SnapshotInfo> snapshotInfos = client().admin().cluster().prepareGetSnapshots(repository).setSnapshots(snapshotName).get().getSnapshots(); assertThat(snapshotInfos.size(), equalTo(1)); if (snapshotInfos.get(0).state().completed()) { // Make sure that snapshot clean up operations are finished ClusterStateResponse stateResponse = client().admin().cluster().prepareState().get(); SnapshotsInProgress snapshotsInProgress = stateResponse.getState().custom(SnapshotsInProgress.TYPE); if (snapshotsInProgress == null || snapshotsInProgress.snapshot(snapshot) == null) { return snapshotInfos.get(0); } } Thread.sleep(100); } fail("Timeout waiting for snapshot completion!"); return null; } @Test public void testCreateSnapshotFromPartition() throws Exception { createTable("custom.backmeup", true); execute("CREATE SNAPSHOT " + snapshotName() + " TABLE custom.backmeup PARTITION (date='1970-01-01') WITH (wait_for_completion=true)"); assertThat(response.rowCount(), is(1L)); execute("select name, \"repository\", concrete_indices, state from sys.snapshots"); assertThat(TestingHelpers.printedTable(response.rows()), is("my_snapshot| my_repo| [custom..partitioned.backmeup.04130]| SUCCESS\n")); } @Test public void testCreateExistingSnapshot() throws Exception { createTable("backmeup", randomBoolean()); execute("CREATE SNAPSHOT " + snapshotName() + " ALL WITH (wait_for_completion=true)"); assertThat(response.rowCount(), is(1L)); expectedException.expect(SQLActionException.class); expectedException.expectMessage("Snapshot \"my_repo\".\"my_snapshot\" already exists"); execute("CREATE SNAPSHOT " + snapshotName() + " ALL WITH (wait_for_completion=true)"); } @Test public void testCreateSnapshotUnknownRepo() throws Exception { expectedException.expect(SQLActionException.class); expectedException.expectMessage("Repository 'unknown_repo' unknown"); execute("CREATE SNAPSHOT unknown_repo.my_snapshot ALL WITH (wait_for_completion=true)"); } @Test public void testInvalidSnapshotName() throws Exception { expectedException.expect(SQLActionException.class); expectedException.expectMessage("Invalid snapshot name [MY_UPPER_SNAPSHOT], must be lowercase"); execute("CREATE SNAPSHOT my_repo.\"MY_UPPER_SNAPSHOT\" ALL WITH (wait_for_completion=true)"); } @Test public void testCreateSnapshotInURLRepoFails() throws Exception { // lets be sure the repository location contains some data, empty directories will result in "no data found" error instead execute("CREATE SNAPSHOT my_repo.my_snapshot ALL WITH (wait_for_completion=true)"); // URL Repositories are always marked as read_only, use the same location that the existing repository to have valid data execute("CREATE REPOSITORY uri_repo TYPE url WITH (url=?)", new Object[]{defaultRepositoryLocation.toURI().toString()}); waitNoPendingTasksOnAll(); expectedException.expect(SQLActionException.class); expectedException.expectMessage("[uri_repo] cannot create snapshot in a readonly repository"); execute("CREATE SNAPSHOT uri_repo.my_snapshot ALL WITH (wait_for_completion=true)"); } @Test public void testSnapshotWithMetadataDoesNotDeleteExistingStuff() throws Exception { createTable("my_other", true); execute("CREATE SNAPSHOT " + snapshotName() + " TABLE my_other with (wait_for_completion=true)"); execute("alter table my_other add column x double"); waitForMappingUpdateOnAll("my_other", "x"); execute("delete from my_other"); execute("CREATE TABLE survivor (bla string, blubb float) partitioned by (blubb) with (number_of_replicas=0)"); ensureYellow(); execute("insert into survivor (bla, blubb) values (?, ?)", new Object[][]{ {"foo", 1.2}, {"bar", 1.4}, {"baz", 1.2} }); execute("refresh table survivor"); execute("restore snapshot " + snapshotName() + " ALL with (wait_for_completion=true)"); execute("select * from survivor order by bla"); assertThat(TestingHelpers.printedTable(response.rows()), is( "bar| 1.4\n" + "baz| 1.2\n" + "foo| 1.2\n")); } @Test public void testRestoreSnapshotAll() throws Exception { createTableAndSnapshot("my_table", SNAPSHOT_NAME); execute("drop table my_table"); execute("RESTORE SNAPSHOT " + snapshotName() + " ALL with (" + "ignore_unavailable=false, " + "wait_for_completion=true)"); ensureGreen(); execute("select * from my_table order by id"); assertThat(response.rowCount(), is(3L)); } @Test public void testRestoreSnapshotSinglePartition() throws Exception { createTableAndSnapshot("my_parted_table", SNAPSHOT_NAME, true); execute("delete from my_parted_table"); waitNoPendingTasksOnAll(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE my_parted_table PARTITION (date='1970-01-01') with (" + "ignore_unavailable=false, " + "wait_for_completion=true)"); execute("select date from my_parted_table"); assertThat(TestingHelpers.printedTable(response.rows()), is("0\n")); } @Test public void testRestoreSinglePartitionSnapshotIntoDroppedPartition() throws Exception { createTable("parted_table", true); execute("CREATE SNAPSHOT " + snapshotName() + " TABLE parted_table PARTITION (date=0) WITH (wait_for_completion=true)"); execute("delete from parted_table where date=0"); waitNoPendingTasksOnAll(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE parted_table PARTITION (date=0) with (" + "ignore_unavailable=false, " + "wait_for_completion=true)"); execute("select date from parted_table order by id"); assertThat(TestingHelpers.printedTable(response.rows()), is("0\n1445941740000\n626572800000\n")); } @Test public void testRestoreSinglePartitionSnapshotIntoDroppedTable() throws Exception { createTable("parted_table", true); execute("CREATE SNAPSHOT " + snapshotName() + " TABLE parted_table PARTITION (date=0) WITH (wait_for_completion=true)"); execute("drop table parted_table"); waitNoPendingTasksOnAll(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE parted_table PARTITION (date=0) with (" + "ignore_unavailable=false, " + "wait_for_completion=true)"); execute("select date from parted_table order by id"); assertThat(TestingHelpers.printedTable(response.rows()), is("0\n")); } @Test public void testRestoreFullPartedTableSnapshotSinglePartitionIntoDroppedTable() throws Exception { createTableAndSnapshot("my_parted_table", SNAPSHOT_NAME, true); execute("drop table my_parted_table"); waitNoPendingTasksOnAll(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE my_parted_table PARTITION (date=0) with (" + "ignore_unavailable=false, " + "wait_for_completion=true)"); execute("select date from my_parted_table"); assertThat(TestingHelpers.printedTable(response.rows()), is("0\n")); } @Test public void testRestoreSnapshotIgnoreUnavailable() throws Exception { createTableAndSnapshot("my_table", SNAPSHOT_NAME, true); execute("drop table my_table"); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE my_table, not_my_table with (" + "ignore_unavailable=true, " + "wait_for_completion=true)"); execute("select table_schema || '.' || table_name from information_schema.tables where table_schema='doc'"); assertThat(TestingHelpers.printedTable(response.rows()), is("doc.my_table\n")); } @Test public void testRestoreOnlyOneTable() throws Exception { createTable("my_table_1", false); createTable("my_table_2", false); createSnapshot(SNAPSHOT_NAME, "my_table_1", "my_table_2"); execute("drop table my_table_1"); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE my_table_1 with (" + "wait_for_completion=true)"); execute("select table_schema || '.' || table_name from information_schema.tables where table_schema='doc' order by 1"); assertThat(TestingHelpers.printedTable(response.rows()), is("doc.my_table_1\ndoc.my_table_2\n")); } /** * Test to restore a concrete partitioned table. * <p> * This requires a patch in ES in order to restore templates when concrete tables are passed as an restore argument: * https://github.com/crate/elasticsearch/commit/3c14e74a3e50ea7d890f436db72ff18c2953ebc4 */ @Test public void testRestoreOnlyOnePartitionedTable() throws Exception { createTable("my_parted_1", true); createTable("my_parted_2", true); createSnapshot(SNAPSHOT_NAME, "my_parted_1", "my_parted_2"); execute("drop table my_parted_1"); execute("drop table my_parted_2"); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE my_parted_1 with (" + "wait_for_completion=true)"); execute("select table_schema || '.' || table_name from information_schema.tables where table_schema='doc'"); assertThat(TestingHelpers.printedTable(response.rows()), is("doc.my_parted_1\n")); } @Test public void testRestoreEmptyPartitionedTableUsingALL() throws Exception { execute("create table employees(section integer, name string) partitioned by (section)"); ensureYellow(); execute("CREATE SNAPSHOT " + snapshotName() + " ALL WITH (wait_for_completion=true)"); execute("drop table employees"); ensureYellow(); execute("RESTORE SNAPSHOT " + snapshotName() + " ALL with (wait_for_completion=true)"); ensureYellow(); execute("select table_schema || '.' || table_name from information_schema.tables where table_schema='doc'"); assertThat(TestingHelpers.printedTable(response.rows()), is("doc.employees\n")); } @Test public void testRestoreEmptyPartitionedTable() throws Exception { execute("create table employees(section integer, name string) partitioned by (section)"); ensureYellow(); execute("CREATE SNAPSHOT " + snapshotName() + " ALL WITH (wait_for_completion=true)"); execute("drop table employees"); ensureYellow(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE employees with (wait_for_completion=true)"); ensureYellow(); execute("select table_schema || '.' || table_name from information_schema.tables where table_schema='doc'"); assertThat(TestingHelpers.printedTable(response.rows()), is("doc.employees\n")); } @Test public void testResolveUnknownTableFromSnapshot() throws Exception { expectedException.expect(SQLActionException.class); expectedException.expectMessage("ResourceNotFoundException: [.partitioned.employees.] template not found"); execute("CREATE SNAPSHOT " + snapshotName() + " ALL WITH (wait_for_completion=true)"); ensureYellow(); execute("RESTORE SNAPSHOT " + snapshotName() + " TABLE employees with (wait_for_completion=true)"); } }