| /* |
| * 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 Exception { |
| zkServer = new ZkTestServer(createTempDir("zkData")); |
| zkServer.run(); |
| } |
| |
| @AfterClass |
| public static void shutdownZkServer() throws IOException, InterruptedException { |
| if (null != zkServer) { |
| 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 copying a config works in both flavors |
| configManager.copyConfigDir("config2", "config2copy"); |
| configManager.copyConfigDir("config2", "config2copy2", null); |
| configs = configManager.listConfigs(); |
| assertTrue("config2copy should exist", configs.contains("config2copy")); |
| assertTrue("config2copy2 should exist", configs.contains("config2copy2")); |
| } |
| } |
| |
| @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; |
| } |
| }; |
| } |
| |
| } |