/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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. */ package org.apache.solr.common.cloud; import com.google.common.base.Throwables; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.cloud.ZkTestServer; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class TestZkConfigManager extends SolrTestCaseJ4 { private static ZkTestServer zkServer; @BeforeClass public static void startZkServer() throws InterruptedException { zkServer = new ZkTestServer(createTempDir("zkData").toString()); zkServer.run(); } @AfterClass public static void shutdownZkServer() throws IOException, InterruptedException { zkServer.shutdown(); zkServer = null; } @Test public void testConstants() throws Exception { assertEquals("/configs", ZkConfigManager.CONFIGS_ZKNODE); assertEquals("^\\..*$", ZkConfigManager.UPLOAD_FILENAME_EXCLUDE_REGEX); } @Test public void testUploadConfig() throws IOException { zkServer.ensurePathExists("/solr"); try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress("/solr"), 10000)) { ZkConfigManager configManager = new ZkConfigManager(zkClient); assertEquals(0, configManager.listConfigs().size()); byte[] testdata = "test data".getBytes(StandardCharsets.UTF_8); Path tempConfig = createTempDir("config"); Files.createFile(tempConfig.resolve("file1")); Files.write(tempConfig.resolve("file1"), testdata); Files.createFile(tempConfig.resolve("file2")); Files.createDirectory(tempConfig.resolve("subdir")); Files.createFile(tempConfig.resolve("subdir").resolve("file3")); Files.createFile(tempConfig.resolve(".ignored")); Files.createDirectory(tempConfig.resolve(".ignoreddir")); Files.createFile(tempConfig.resolve(".ignoreddir").resolve("ignored")); configManager.uploadConfigDir(tempConfig, "testconfig"); // uploading a directory creates a new config List<String> configs = configManager.listConfigs(); assertEquals(1, configs.size()); assertEquals("testconfig", configs.get(0)); // check downloading Path downloadPath = createTempDir("download"); configManager.downloadConfigDir("testconfig", downloadPath); assertTrue(Files.exists(downloadPath.resolve("file1"))); assertTrue(Files.exists(downloadPath.resolve("file2"))); assertTrue(Files.isDirectory(downloadPath.resolve("subdir"))); assertTrue(Files.exists(downloadPath.resolve("subdir/file3"))); // dotfiles should be ignored assertFalse(Files.exists(downloadPath.resolve(".ignored"))); assertFalse(Files.exists(downloadPath.resolve(".ignoreddir/ignored"))); byte[] checkdata = Files.readAllBytes(downloadPath.resolve("file1")); assertArrayEquals(testdata, checkdata); // uploading to the same config overwrites byte[] overwritten = "new test data".getBytes(StandardCharsets.UTF_8); Files.write(tempConfig.resolve("file1"), overwritten); configManager.uploadConfigDir(tempConfig, "testconfig"); assertEquals(1, configManager.listConfigs().size()); Path download2 = createTempDir("download2"); configManager.downloadConfigDir("testconfig", download2); byte[] checkdata2 = Files.readAllBytes(download2.resolve("file1")); assertArrayEquals(overwritten, checkdata2); // uploading same files to a new name creates a new config configManager.uploadConfigDir(tempConfig, "config2"); assertEquals(2, configManager.listConfigs().size()); } } @Test public void testUploadWithACL() throws IOException { zkServer.ensurePathExists("/acl"); final String readOnlyUsername = "readonly"; final String readOnlyPassword = "readonly"; final String writeableUsername = "writeable"; final String writeablePassword = "writeable"; ZkACLProvider aclProvider = new DefaultZkACLProvider(){ @Override protected List<ACL> createGlobalACLsToAdd() { try { List<ACL> result = new ArrayList<>(); result.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(writeableUsername + ":" + writeablePassword)))); result.add(new ACL(ZooDefs.Perms.READ, new Id("digest", DigestAuthenticationProvider.generateDigest(readOnlyUsername + ":" + readOnlyPassword)))); return result; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } }; ZkCredentialsProvider readonly = new DefaultZkCredentialsProvider(){ @Override protected Collection<ZkCredentials> createCredentials() { List<ZkCredentials> credentials = new ArrayList<>(); credentials.add(new ZkCredentials("digest", (readOnlyUsername + ":" + readOnlyPassword).getBytes(StandardCharsets.UTF_8))); return credentials; } }; ZkCredentialsProvider writeable = new DefaultZkCredentialsProvider(){ @Override protected Collection<ZkCredentials> createCredentials() { List<ZkCredentials> credentials = new ArrayList<>(); credentials.add(new ZkCredentials("digest", (writeableUsername + ":" + writeablePassword).getBytes(StandardCharsets.UTF_8))); return credentials; } }; Path configPath = createTempDir("acl-config"); Files.createFile(configPath.resolve("file1")); // Start with all-access client try (SolrZkClient client = buildZkClient(zkServer.getZkAddress("/acl"), aclProvider, writeable)) { ZkConfigManager configManager = new ZkConfigManager(client); configManager.uploadConfigDir(configPath, "acltest"); assertEquals(1, configManager.listConfigs().size()); } // Readonly access client can get the list of configs, but can't upload try (SolrZkClient client = buildZkClient(zkServer.getZkAddress("/acl"), aclProvider, readonly)) { ZkConfigManager configManager = new ZkConfigManager(client); assertEquals(1, configManager.listConfigs().size()); configManager.uploadConfigDir(configPath, "acltest2"); fail ("Should have thrown an ACL exception"); } catch (IOException e) { assertEquals(KeeperException.NoAuthException.class, Throwables.getRootCause(e).getClass()); } // Client with no auth whatsoever can't even get the list of configs try (SolrZkClient client = new SolrZkClient(zkServer.getZkAddress("/acl"), 10000)) { ZkConfigManager configManager = new ZkConfigManager(client); configManager.listConfigs(); fail("Should have thrown an ACL exception"); } catch (IOException e) { assertEquals(KeeperException.NoAuthException.class, Throwables.getRootCause(e).getClass()); } } static SolrZkClient buildZkClient(String zkAddress, final ZkACLProvider aclProvider, final ZkCredentialsProvider credentialsProvider) { return new SolrZkClient(zkAddress, 10000){ @Override protected ZkCredentialsProvider createZkCredentialsToAddAutomatically() { return credentialsProvider; } @Override protected ZkACLProvider createZkACLProvider() { return aclProvider; } }; } }