blob: a72c1f007ea8e3b83056b81c8e4561ca99aa1d6b [file] [log] [blame]
/*
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.knox.gateway.topology.monitor;
import org.apache.commons.io.FileUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.InstanceSpec;
import org.apache.curator.test.TestingCluster;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.test.TestUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.easymock.EasyMock;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Test the ZooKeeperConfigMonitor WITHOUT SASL configured or znode ACLs applied.
* The implementation of the monitor is the same regardless, since the ACLs are defined by the ZooKeeper znode
* creator, and the SASL config is purely JAAS (and external to the implementation).
*/
public class ZooKeeperConfigurationMonitorTest {
private static final String PATH_KNOX = "/knox";
private static final String PATH_KNOX_CONFIG = PATH_KNOX + "/config";
private static final String PATH_KNOX_PROVIDERS = PATH_KNOX_CONFIG + "/shared-providers";
private static final String PATH_KNOX_DESCRIPTORS = PATH_KNOX_CONFIG + "/descriptors";
private static File testTmp;
private static File providersDir;
private static File descriptorsDir;
private static TestingCluster zkCluster;
private static CuratorFramework client;
private GatewayConfig gc;
@BeforeClass
public static void setupSuite() throws Exception {
testTmp = TestUtils.createTempDir(ZooKeeperConfigurationMonitorTest.class.getName());
File confDir = TestUtils.createTempDir(testTmp + "/conf");
providersDir = TestUtils.createTempDir(confDir + "/shared-providers");
descriptorsDir = TestUtils.createTempDir(confDir + "/descriptors");
configureAndStartZKCluster();
}
private static void configureAndStartZKCluster() throws Exception {
// Configure security for the ZK cluster instances
Map<String, Object> customInstanceSpecProps = new HashMap<>();
customInstanceSpecProps.put("authProvider.1", "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
customInstanceSpecProps.put("requireClientAuthScheme", "sasl");
customInstanceSpecProps.put("admin.enableServer", false);
// Define the test cluster
List<InstanceSpec> instanceSpecs = new ArrayList<>();
for (int i = 0 ; i < 1 ; i++) {
InstanceSpec is = new InstanceSpec(null, -1, -1, -1, false, (i+1), -1, -1, customInstanceSpecProps);
instanceSpecs.add(is);
}
zkCluster = new TestingCluster(instanceSpecs);
// Start the cluster
zkCluster.start();
// Create the client for the test cluster
client = CuratorFrameworkFactory.builder()
.connectString(zkCluster.getConnectString())
.retryPolicy(new ExponentialBackoffRetry(100, 3))
.build();
assertNotNull(client);
client.start();
boolean connected = client.blockUntilConnected(10, TimeUnit.SECONDS);
assertTrue(connected);
// Create the knox config paths with an ACL for the sasl user configured for the client
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE));
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX_DESCRIPTORS);
assertNotNull("Failed to create node:" + PATH_KNOX_DESCRIPTORS,
client.checkExists().forPath(PATH_KNOX_DESCRIPTORS));
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX_PROVIDERS);
assertNotNull("Failed to create node:" + PATH_KNOX_PROVIDERS,
client.checkExists().forPath(PATH_KNOX_PROVIDERS));
}
@AfterClass
public static void tearDownSuite() throws Exception {
// Clean up the ZK nodes, and close the client
if (client != null) {
if (client.checkExists().forPath(PATH_KNOX) != null) {
client.delete().deletingChildrenIfNeeded().forPath(PATH_KNOX);
}
client.close();
}
// Shutdown the ZK cluster
zkCluster.close();
// Delete the working dir
testTmp.delete();
}
@Test
public void testZooKeeperConfigMonitor() throws Exception {
String configMonitorName = "remoteConfigMonitorClient";
// Setup the base GatewayConfig mock
gc = EasyMock.createNiceMock(GatewayConfig.class);
EasyMock.expect(gc.getGatewayProvidersConfigDir()).andReturn(providersDir.getAbsolutePath()).anyTimes();
EasyMock.expect(gc.getGatewayDescriptorsDir()).andReturn(descriptorsDir.getAbsolutePath()).anyTimes();
EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
.andReturn(Collections.singletonList(configMonitorName))
.anyTimes();
final String registryConfig =
GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
.andReturn(registryConfig)
.anyTimes();
EasyMock.expect(gc.getRemoteConfigurationMonitorClientName()).andReturn(configMonitorName).anyTimes();
EasyMock.replay(gc);
AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
EasyMock.replay(aliasService);
RemoteConfigurationRegistryClientService clientService = (new ZooKeeperClientServiceProvider()).newInstance();
clientService.setAliasService(aliasService);
clientService.init(gc, Collections.emptyMap());
clientService.start();
DefaultRemoteConfigurationMonitor cm = new DefaultRemoteConfigurationMonitor(gc, clientService);
// Create a provider configuration in the test ZK, prior to starting the monitor, to make sure that the monitor
// will download existing entries upon starting.
final String preExistingProviderConfig = getProviderPath("pre-existing-providers.xml");
client.create().withMode(CreateMode.PERSISTENT).forPath(preExistingProviderConfig,
TEST_PROVIDERS_CONFIG_1.getBytes(StandardCharsets.UTF_8));
File preExistingProviderConfigLocalFile = new File(providersDir, "pre-existing-providers.xml");
assertFalse("This file should not exist locally prior to monitor starting.",
preExistingProviderConfigLocalFile.exists());
try {
cm.start();
} catch (Exception e) {
fail("Failed to start monitor: " + e.getMessage());
}
assertTrue("This file should exist locally immediately after monitor starting.",
preExistingProviderConfigLocalFile.exists());
try {
final String pc_one_znode = getProviderPath("providers-config1.xml");
final File pc_one = new File(providersDir, "providers-config1.xml");
final String pc_two_znode = getProviderPath("providers-config2.xml");
final File pc_two = new File(providersDir, "providers-config2.xml");
client.create().withMode(CreateMode.PERSISTENT).forPath(pc_one_znode, TEST_PROVIDERS_CONFIG_1.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(pc_one::exists, true,1000));
assertEquals(TEST_PROVIDERS_CONFIG_1, FileUtils.readFileToString(pc_one, StandardCharsets.UTF_8));
client.create().withMode(CreateMode.PERSISTENT).forPath(getProviderPath("providers-config2.xml"), TEST_PROVIDERS_CONFIG_2.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(pc_two::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_PROVIDERS_CONFIG_2.equals(FileUtils.readFileToString(pc_two, StandardCharsets.UTF_8)),
true, 1000));
client.setData().forPath(pc_two_znode, TEST_PROVIDERS_CONFIG_1.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(pc_two::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_PROVIDERS_CONFIG_1.equals(FileUtils.readFileToString(pc_two, StandardCharsets.UTF_8)),
true, 1000));
client.delete().forPath(pc_two_znode);
assertFalse(TestUtils.waitUntil(pc_two::exists, false,1000));
client.delete().forPath(pc_one_znode);
assertFalse(TestUtils.waitUntil(pc_one::exists, false,1000));
final String desc_one_znode = getDescriptorPath("test1.json");
final String desc_two_znode = getDescriptorPath("test2.json");
final String desc_three_znode = getDescriptorPath("test3.json");
final File desc_one = new File(descriptorsDir, "test1.json");
final File desc_two = new File(descriptorsDir, "test2.json");
final File desc_three = new File(descriptorsDir, "test3.json");
client.create().withMode(CreateMode.PERSISTENT).forPath(desc_one_znode, TEST_DESCRIPTOR_1.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(desc_one::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_DESCRIPTOR_1.equals(FileUtils.readFileToString(desc_one, StandardCharsets.UTF_8)),
true, 1000));
client.create().withMode(CreateMode.PERSISTENT).forPath(desc_two_znode, TEST_DESCRIPTOR_1.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(desc_two::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_DESCRIPTOR_1.equals(FileUtils.readFileToString(desc_two, StandardCharsets.UTF_8)),
true, 1000));
client.setData().forPath(desc_two_znode, TEST_DESCRIPTOR_2.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(desc_two::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_DESCRIPTOR_2.equals(FileUtils.readFileToString(desc_two, StandardCharsets.UTF_8)),
true, 1000));
client.create().withMode(CreateMode.PERSISTENT).forPath(desc_three_znode, TEST_DESCRIPTOR_1.getBytes(StandardCharsets.UTF_8));
assertTrue(TestUtils.waitUntil(desc_three::exists, true,1000));
assertTrue(TestUtils.waitUntil(
() -> TEST_DESCRIPTOR_1.equals(FileUtils.readFileToString(desc_three, StandardCharsets.UTF_8)),
true, 1000));
client.delete().forPath(desc_two_znode);
assertFalse("Expected test2.json to have been deleted.",
TestUtils.waitUntil(desc_two::exists, false,1000));
client.delete().forPath(desc_three_znode);
assertFalse(TestUtils.waitUntil(desc_three::exists, false,1000));
client.delete().forPath(desc_one_znode);
assertFalse(TestUtils.waitUntil(desc_one::exists, false,1000));
} finally {
clientService.stop();
cm.stop();
}
}
private static String getDescriptorPath(String descriptorName) {
return PATH_KNOX_DESCRIPTORS + "/" + descriptorName;
}
private static String getProviderPath(String providerConfigName) {
return PATH_KNOX_PROVIDERS + "/" + providerConfigName;
}
private static final String TEST_PROVIDERS_CONFIG_1 =
"<gateway>\n" +
" <provider>\n" +
" <role>identity-assertion</role>\n" +
" <name>Default</name>\n" +
" <enabled>true</enabled>\n" +
" </provider>\n" +
" <provider>\n" +
" <role>hostmap</role>\n" +
" <name>static</name>\n" +
" <enabled>true</enabled>\n" +
" <param><name>localhost</name><value>sandbox,sandbox.hortonworks.com</value></param>\n" +
" </provider>\n" +
"</gateway>\n";
private static final String TEST_PROVIDERS_CONFIG_2 =
"<gateway>\n" +
" <provider>\n" +
" <role>authentication</role>\n" +
" <name>ShiroProvider</name>\n" +
" <enabled>true</enabled>\n" +
" <param>\n" +
" <name>sessionTimeout</name>\n" +
" <value>30</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapRealm</name>\n" +
" <value>org.apache.knox.gateway.shirorealm.KnoxLdapRealm</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapContextFactory</name>\n" +
" <value>org.apache.knox.gateway.shirorealm.KnoxLdapContextFactory</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapRealm.contextFactory</name>\n" +
" <value>$ldapContextFactory</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapRealm.userDnTemplate</name>\n" +
" <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapRealm.contextFactory.url</name>\n" +
" <value>ldap://localhost:33389</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>main.ldapRealm.contextFactory.authenticationMechanism</name>\n" +
" <value>simple</value>\n" +
" </param>\n" +
" <param>\n" +
" <name>urls./**</name>\n" +
" <value>authcBasic</value>\n" +
" </param>\n" +
" </provider>\n" +
"</gateway>\n";
private static final String TEST_DESCRIPTOR_1 =
"{\n" +
" \"discovery-type\":\"AMBARI\",\n" +
" \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
" \"discovery-user\":\"maria_dev\",\n" +
" \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
" \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
" \"cluster\":\"Sandbox\",\n" +
" \"services\":[\n" +
" {\"name\":\"NODEUI\"},\n" +
" {\"name\":\"YARNUI\"},\n" +
" {\"name\":\"HDFSUI\"},\n" +
" {\"name\":\"OOZIEUI\"},\n" +
" {\"name\":\"HBASEUI\"},\n" +
" {\"name\":\"NAMENODE\"},\n" +
" {\"name\":\"JOBTRACKER\"},\n" +
" {\"name\":\"WEBHDFS\"},\n" +
" {\"name\":\"WEBHCAT\"},\n" +
" {\"name\":\"OOZIE\"},\n" +
" {\"name\":\"WEBHBASE\"},\n" +
" {\"name\":\"RESOURCEMANAGER\"},\n" +
" {\"name\":\"AMBARI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]},\n" +
" {\"name\":\"AMBARIUI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]}\n" +
" ]\n" +
"}\n";
private static final String TEST_DESCRIPTOR_2 =
"{\n" +
" \"discovery-type\":\"AMBARI\",\n" +
" \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
" \"discovery-user\":\"maria_dev\",\n" +
" \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
" \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
" \"cluster\":\"Sandbox\",\n" +
" \"services\":[\n" +
" {\"name\":\"NAMENODE\"},\n" +
" {\"name\":\"JOBTRACKER\"},\n" +
" {\"name\":\"WEBHDFS\"},\n" +
" {\"name\":\"WEBHCAT\"},\n" +
" {\"name\":\"OOZIE\"},\n" +
" {\"name\":\"WEBHBASE\"},\n" +
" {\"name\":\"RESOURCEMANAGER\"}\n" +
" ]\n" +
"}\n";
}