/* * 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.action.sql.SQLOperations; import io.crate.testing.SQLResponse; import io.crate.testing.SQLTransportExecutor; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.ESIntegTestCase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import javax.annotation.Nullable; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; @ESIntegTestCase.ClusterScope(numDataNodes = 1, numClientNodes = 1, transportClientRatio = 0) public class ReadOnlyNodeIntegrationTest extends SQLTransportIntegrationTest { private SQLTransportExecutor readOnlyExecutor; @Rule public TemporaryFolder folder = new TemporaryFolder(); public ReadOnlyNodeIntegrationTest() { super(new SQLTransportExecutor( new SQLTransportExecutor.ClientProvider() { @Override public Client client() { // make sure we use the read-only client return internalCluster().client(internalCluster().getNodeNames()[1]); } @Override public String pgUrl() { return null; } @Override public SQLOperations sqlOperations() { return internalCluster().getInstance(SQLOperations.class, internalCluster().getNodeNames()[1]); } } )); } @Override protected Settings nodeSettings(int nodeOrdinal) { Settings.Builder builder = Settings.builder(); builder.put(super.nodeSettings(nodeOrdinal)); if ((nodeOrdinal + 1) % 2 == 0) { builder.put(SQLOperations.NODE_READ_ONLY_SETTING.getKey(), true); } return builder.build(); } @Before public void setUpTestData() throws Exception { executeWrite("create table write_test (id int primary key, name string) with (number_of_replicas=0)"); executeWrite("create table write_test2 (id int, name string) with (number_of_replicas=0)"); executeWrite("create blob table write_blob_test with (number_of_replicas=0)"); executeWrite("create repository existing_repo TYPE \"fs\" with (location=?, compress=True)", new Object[]{folder}); ensureYellow(); } private SQLResponse executeWrite(String stmt, Object[] args) { if (readOnlyExecutor == null) { readOnlyExecutor = new SQLTransportExecutor( new SQLTransportExecutor.ClientProvider() { @Override public Client client() { // make sure we use NOT the read-only client return internalCluster().client(internalCluster().getNodeNames()[0]); } @Nullable @Override public String pgUrl() { return null; } @Override public SQLOperations sqlOperations() { // make sure we use NOT the read-only operations return internalCluster().getInstance(SQLOperations.class, internalCluster().getNodeNames()[0]); } } ); } response = readOnlyExecutor.exec(stmt, args); return response; } private SQLResponse executeWrite(String stmt) { return executeWrite(stmt, null); } private void assertReadOnly(String stmt, Object[] args) throws Exception { expectedException.expect(SQLActionException.class); expectedException.expectMessage("Only read operations are allowed on this node"); execute(stmt, args); } private void assertReadOnly(String stmt) throws Exception { assertReadOnly(stmt, null); } /** * ALLOWED STATEMENT TESTS **/ @Test public void testAllowedSelectSys() throws Exception { execute("select name from sys.cluster"); assertThat(response.rowCount(), is(1L)); } @Test public void testAllowedSelect() throws Exception { execute("select name from write_test"); assertThat(response.rowCount(), is(0L)); } @Test public void testAllowedExplainSelect() throws Exception { execute("explain select name from write_test"); assertThat(response.rowCount(), is(1L)); } @Test public void testAllowedExplainWhichIncludesNestedWrite() throws Exception { String copyFilePath = getClass().getResource("/essetup/data/copy").getPath(); String uriPath = Joiner.on("/").join(copyFilePath, "test_copy_from.json"); execute("explain copy write_test from ?", new Object[]{uriPath}); assertThat(response.rowCount(), is(1L)); } @Test public void testAllowedShowCreateTable() throws Exception { execute("show create table write_test"); assertThat(response.rowCount(), is(1L)); } @Test public void testAllowedCopyTo() throws Exception { String uri = folder.getRoot().toURI().toString(); SQLResponse response = execute("copy write_test to directory ?", new Object[]{uri}); assertThat(response.rowCount(), greaterThanOrEqualTo(0L)); } /** * FORBIDDEN STATEMENT TESTS **/ @Test public void testForbiddenCreateTable() throws Exception { assertReadOnly("create table test (id int)"); } @Test public void testForbiddenDropTable() throws Exception { assertReadOnly("drop table write_test"); } @Test public void testForbiddenCreateBlobTable() throws Exception { assertReadOnly("create blob table blobs_test"); } @Test public void testForbiddenDropBlobTable() throws Exception { assertReadOnly("drop blob table write_blob_test"); } @Test public void testForbiddenCopyFrom() throws Exception { assertReadOnly("copy write_test from '/tmp/copy.json'"); } @Test public void testForbiddenInsertFromValues() throws Exception { assertReadOnly("insert into write_test (id) values (1)"); } @Test public void testForbiddenInsertFromValuesOnDuplicateKey() throws Exception { assertReadOnly("insert into write_test (id) values (1) on duplicate key update name = 'foo'"); } @Test public void testForbiddenInsertFromQuery() throws Exception { assertReadOnly("insert into write_test2 (select * from write_test)"); } @Test public void testForbiddenUpdate() throws Exception { assertReadOnly("update write_test set name = 'foo'"); } @Test public void testForbiddenDelete() throws Exception { assertReadOnly("delete from write_test"); } @Test public void testForbiddenAlterTableSetParameter() throws Exception { assertReadOnly("alter table write_test set (number_of_replicas=1)"); } @Test public void testForbiddenAlterTableAddColumn() throws Exception { assertReadOnly("alter table write_test add column new_column_name string"); } @Test public void testForbiddenSetGlobal() throws Exception { assertReadOnly("set global PERSISTENT stats.enabled = false"); } @Test public void testForbiddenResetGlobal() throws Exception { assertReadOnly("reset global stats.enabled"); } @Test public void testForbiddenRefresh() throws Exception { assertReadOnly("refresh table write_test"); } @Test public void testForbiddenCreateRepository() throws Exception { assertReadOnly("create repository new_repo type fs with (location=?)", new Object[]{folder}); } @Test public void testForbiddenDropRepository() throws Exception { assertReadOnly("drop repository existing_repo"); } @Test public void testForbiddenCreateSnapshot() throws Exception { assertReadOnly("create snapshot existing_repo.my_snap1 all"); } @Test public void testForbiddenDropSnapshot() throws Exception { assertReadOnly("drop snapshot existing_repo.my_snap"); } @Test public void testForbiddenRestore() throws Exception { assertReadOnly("restore snapshot existing_repo.my_snap all"); } }