http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java new file mode 100644 index 0000000..9fed589 --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java @@ -0,0 +1,60 @@ +/** + * 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.hadoop.gateway.service.config.remote.config; + +import org.apache.hadoop.gateway.config.GatewayConfig; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class RemoteConfigurationRegistriesAccessor { + + // System property for specifying a reference to an XML configuration external to the gateway config + private static final String XML_CONFIG_REFERENCE_SYSTEM_PROPERTY_NAME = + "org.apache.knox.gateway.remote.registry.config.file"; + + + public static List<RemoteConfigurationRegistryConfig> getRemoteRegistryConfigurations(GatewayConfig gatewayConfig) { + List<RemoteConfigurationRegistryConfig> result = new ArrayList<>(); + + boolean useReferencedFile = false; + + // First check for the system property pointing to a valid XML config for the remote registries + String remoteConfigRegistryConfigFilename = System.getProperty(XML_CONFIG_REFERENCE_SYSTEM_PROPERTY_NAME); + if (remoteConfigRegistryConfigFilename != null) { + File remoteConfigRegistryConfigFile = new File(remoteConfigRegistryConfigFilename); + if (remoteConfigRegistryConfigFile.exists()) { + useReferencedFile = true; + // Parse the file, and build the registry config set + result.addAll(RemoteConfigurationRegistriesParser.getConfig(remoteConfigRegistryConfigFilename)); + } + } + + // If the system property was not set to a valid reference to another config file, then try to derive the + // registry configurations from the gateway config. + if (!useReferencedFile) { + RemoteConfigurationRegistries remoteConfigRegistries = + new DefaultRemoteConfigurationRegistries(gatewayConfig); + result.addAll(remoteConfigRegistries.getRegistryConfigurations()); + } + + return result; + } + +}
http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java new file mode 100644 index 0000000..3ea71ef --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java @@ -0,0 +1,48 @@ +/** + * 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.hadoop.gateway.service.config.remote.config; + +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +class RemoteConfigurationRegistriesParser { + + static List<RemoteConfigurationRegistryConfig> getConfig(String configFilename) { + List<RemoteConfigurationRegistryConfig> result = new ArrayList<>(); + + File file = new File(configFilename); + + try { + JAXBContext jaxbContext = JAXBContext.newInstance(RemoteConfigurationRegistries.class); + Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + RemoteConfigurationRegistries parsedContent = (RemoteConfigurationRegistries) jaxbUnmarshaller.unmarshal(file); + if (parsedContent != null) { + result.addAll(parsedContent.getRegistryConfigurations()); + } + } catch (JAXBException e) { + e.printStackTrace(); + } + + return result; + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java new file mode 100644 index 0000000..f3e7dbd --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java @@ -0,0 +1,139 @@ +/** + * 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.hadoop.gateway.service.config.remote.config; + +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; + +import javax.xml.bind.annotation.XmlElement; + +class RemoteConfigurationRegistry implements RemoteConfigurationRegistryConfig { + + private String name; + private String type; + private String connectionString; + private String namespace; + private String authType; + private String principal; + private String credentialAlias; + private String keyTab; + private boolean useKeyTab; + private boolean useTicketCache; + + RemoteConfigurationRegistry() { + } + + public void setName(String name) { + this.name = name; + } + + public void setRegistryType(String type) { + this.type = type; + } + + public void setConnectionString(String connectionString) { + this.connectionString = connectionString; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public void setCredentialAlias(String alias) { + this.credentialAlias = alias; + } + + public void setUseTicketCache(boolean useTicketCache) { + this.useTicketCache = useTicketCache; + } + + public void setUseKeytab(boolean useKeytab) { + this.useKeyTab = useKeytab; + } + + public void setKeytab(String keytab) { + this.keyTab = keytab; + } + + @XmlElement(name="name") + public String getName() { + return name; + } + + @XmlElement(name="type") + public String getRegistryType() { + return type; + } + + @XmlElement(name="auth-type") + public String getAuthType() { + return authType; + } + + @XmlElement(name="principal") + public String getPrincipal() { + return principal; + } + + @XmlElement(name="credential-alias") + public String getCredentialAlias() { + return credentialAlias; + } + + @Override + @XmlElement(name="address") + public String getConnectionString() { + return connectionString; + } + + @Override + @XmlElement(name="namespace") + public String getNamespace() { + return namespace; + } + + @Override + @XmlElement(name="use-ticket-cache") + public boolean isUseTicketCache() { + return useTicketCache; + } + + @Override + @XmlElement(name="use-key-tab") + public boolean isUseKeyTab() { + return useKeyTab; + } + + @Override + @XmlElement(name="keytab") + public String getKeytab() { + return keyTab; + } + + @Override + public boolean isSecureRegistry() { + return (getAuthType() != null); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java new file mode 100644 index 0000000..0908252 --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java @@ -0,0 +1,423 @@ +/** + * 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.hadoop.gateway.service.config.remote.zk; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.api.ACLProvider; +import org.apache.curator.framework.imps.DefaultACLProvider; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.NodeCache; +import org.apache.curator.framework.recipes.cache.NodeCacheListener; +import org.apache.curator.framework.recipes.cache.PathChildrenCache; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.hadoop.gateway.config.GatewayConfig; +import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationMessages; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.EntryListener; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; +import org.apache.hadoop.gateway.service.config.remote.config.RemoteConfigurationRegistriesAccessor; +import org.apache.hadoop.gateway.services.ServiceLifecycleException; +import org.apache.hadoop.gateway.services.security.AliasService; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * RemoteConfigurationRegistryClientService implementation that employs the Curator ZooKeeper client framework. + */ +class CuratorClientService implements ZooKeeperClientService { + + private static final String LOGIN_CONTEXT_NAME_PROPERTY = ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY; + + private static final String DEFAULT_LOGIN_CONTEXT_NAME = "Client"; + + private static final RemoteConfigurationMessages log = + MessagesFactory.get(RemoteConfigurationMessages.class); + + private Map<String, RemoteConfigurationRegistryClient> clients = new HashMap<>(); + + private AliasService aliasService = null; + + + @Override + public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException { + + List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>(); + + // Load the remote registry configurations + registryConfigs.addAll(RemoteConfigurationRegistriesAccessor.getRemoteRegistryConfigurations(config)); + + // Configure registry authentication + RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, aliasService); + + if (registryConfigs.size() > 1) { + // Warn about current limit on number of supported client configurations + log.multipleRemoteRegistryConfigurations(); + } + + // Create the clients + for (RemoteConfigurationRegistryConfig registryConfig : registryConfigs) { + if (TYPE.equalsIgnoreCase(registryConfig.getRegistryType())) { + RemoteConfigurationRegistryClient registryClient = createClient(registryConfig); + clients.put(registryConfig.getName(), registryClient); + } + } + } + + @Override + public void setAliasService(AliasService aliasService) { + this.aliasService = aliasService; + } + + @Override + public void start() throws ServiceLifecycleException { + } + + @Override + public void stop() throws ServiceLifecycleException { + } + + @Override + public RemoteConfigurationRegistryClient get(String name) { + return clients.get(name); + } + + + private RemoteConfigurationRegistryClient createClient(RemoteConfigurationRegistryConfig config) { + ACLProvider aclProvider; + if (config.isSecureRegistry()) { + configureSasl(config); + aclProvider = new SASLOwnerACLProvider(config.getPrincipal()); + } else { + // Clear SASL system property + System.clearProperty(LOGIN_CONTEXT_NAME_PROPERTY); + aclProvider = new DefaultACLProvider(); + } + + CuratorFramework client = CuratorFrameworkFactory.builder() + .connectString(config.getConnectionString()) + .retryPolicy(new ExponentialBackoffRetry(1000, 3)) + .aclProvider(aclProvider) + .build(); + client.start(); + + return (new ClientAdapter(client, config)); + } + + + private void configureSasl(RemoteConfigurationRegistryConfig config) { + String registryName = config.getName(); + if (registryName == null) { + registryName = DEFAULT_LOGIN_CONTEXT_NAME; + } + System.setProperty(LOGIN_CONTEXT_NAME_PROPERTY, registryName); + } + + + private static final class ClientAdapter implements RemoteConfigurationRegistryClient { + + private static final String DEFAULT_ENCODING = "UTF-8"; + + private CuratorFramework delegate; + + private RemoteConfigurationRegistryConfig config; + + private Map<String, NodeCache> entryNodeCaches = new HashMap<>(); + + ClientAdapter(CuratorFramework delegate, RemoteConfigurationRegistryConfig config) { + this.delegate = delegate; + this.config = config; + } + + @Override + public String getAddress() { + return config.getConnectionString(); + } + + @Override + public boolean entryExists(String path) { + Stat s = null; + try { + s = delegate.checkExists().forPath(path); + } catch (Exception e) { + // Ignore + } + return (s != null); + } + + @Override + public List<RemoteConfigurationRegistryClient.EntryACL> getACL(String path) { + List<RemoteConfigurationRegistryClient.EntryACL> acl = new ArrayList<>(); + try { + List<ACL> zkACL = delegate.getACL().forPath(path); + if (zkACL != null) { + for (ACL aclEntry : zkACL) { + RemoteConfigurationRegistryClient.EntryACL entryACL = new ZooKeeperACLAdapter(aclEntry); + acl.add(entryACL); + } + } + } catch (Exception e) { + log.errorHandlingRemoteConfigACL(path, e); + } + return acl; + } + + @Override + public List<String> listChildEntries(String path) { + List<String> result = null; + try { + result = delegate.getChildren().forPath(path); + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + return result; + } + + @Override + public void addChildEntryListener(String path, ChildEntryListener listener) throws Exception { + PathChildrenCache childCache = new PathChildrenCache(delegate, path, false); + childCache.getListenable().addListener(new ChildEntryListenerAdapter(this, listener)); + childCache.start(); + } + + @Override + public void addEntryListener(String path, EntryListener listener) throws Exception { + NodeCache nodeCache = new NodeCache(delegate, path); + nodeCache.getListenable().addListener(new EntryListenerAdapter(this, nodeCache, listener)); + nodeCache.start(); + entryNodeCaches.put(path, nodeCache); + } + + @Override + public void removeEntryListener(String path) throws Exception { + NodeCache nodeCache = entryNodeCaches.remove(path); + if (nodeCache != null) { + nodeCache.close(); + } + } + + @Override + public String getEntryData(String path) { + return getEntryData(path, DEFAULT_ENCODING); + } + + @Override + public String getEntryData(String path, String encoding) { + String result = null; + try { + byte[] data = delegate.getData().forPath(path); + if (data != null) { + result = new String(data, Charset.forName(encoding)); + } + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + return result; + } + + @Override + public void createEntry(String path) { + try { + if (delegate.checkExists().forPath(path) == null) { + delegate.create().forPath(path); + } + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + } + + @Override + public void createEntry(String path, String data) { + createEntry(path, data, DEFAULT_ENCODING); + } + + @Override + public void createEntry(String path, String data, String encoding) { + try { + createEntry(path); + setEntryData(path, data, encoding); + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + } + + @Override + public int setEntryData(String path, String data) { + return setEntryData(path, data, DEFAULT_ENCODING); + } + + @Override + public int setEntryData(String path, String data, String encoding) { + int version = 0; + try { + Stat s = delegate.setData().forPath(path, data.getBytes(Charset.forName(encoding))); + if (s != null) { + version = s.getVersion(); + } + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + return version; + } + + @Override + public void deleteEntry(String path) { + try { + delegate.delete().forPath(path); + } catch (Exception e) { + log.errorInteractingWithRemoteConfigRegistry(e); + } + } + } + + /** + * SASL ACLProvider + */ + private static class SASLOwnerACLProvider implements ACLProvider { + + private final List<ACL> saslACL; + + private SASLOwnerACLProvider(String principal) { + this.saslACL = Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("sasl", principal))); + } + + @Override + public List<ACL> getDefaultAcl() { + return saslACL; + } + + @Override + public List<ACL> getAclForPath(String path) { + return getDefaultAcl(); + } + } + + + private static final class ChildEntryListenerAdapter implements PathChildrenCacheListener { + + private RemoteConfigurationRegistryClient client; + private ChildEntryListener delegate; + + ChildEntryListenerAdapter(RemoteConfigurationRegistryClient client, ChildEntryListener delegate) { + this.client = client; + this.delegate = delegate; + } + + @Override + public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) + throws Exception { + ChildData childData = pathChildrenCacheEvent.getData(); + if (childData != null) { + delegate.childEvent(client, + adaptType(pathChildrenCacheEvent.getType()), + childData.getPath()); + } + } + + private ChildEntryListener.Type adaptType(PathChildrenCacheEvent.Type type) { + ChildEntryListener.Type adapted = null; + + switch(type) { + case CHILD_ADDED: + adapted = ChildEntryListener.Type.ADDED; + break; + case CHILD_REMOVED: + adapted = ChildEntryListener.Type.REMOVED; + break; + case CHILD_UPDATED: + adapted = ChildEntryListener.Type.UPDATED; + break; + } + + return adapted; + } + } + + private static final class EntryListenerAdapter implements NodeCacheListener { + + private RemoteConfigurationRegistryClient client; + private EntryListener delegate; + private NodeCache nodeCache; + + EntryListenerAdapter(RemoteConfigurationRegistryClient client, NodeCache nodeCache, EntryListener delegate) { + this.client = client; + this.nodeCache = nodeCache; + this.delegate = delegate; + } + + @Override + public void nodeChanged() throws Exception { + String path = null; + byte[] data = null; + + ChildData cd = nodeCache.getCurrentData(); + if (cd != null) { + path = cd.getPath(); + data = cd.getData(); + } + + if (path != null) { + delegate.entryChanged(client, path, data); + } + } + } + + /** + * ACL adapter + */ + private static final class ZooKeeperACLAdapter implements RemoteConfigurationRegistryClient.EntryACL { + private String type; + private String id; + private Object permissions; + + ZooKeeperACLAdapter(ACL acl) { + this.permissions = acl.getPerms(); + this.type = acl.getId().getScheme(); + this.id = acl.getId().getId(); + } + + @Override + public String getId() { + return id; + } + + @Override + public String getType() { + return type; + } + + @Override + public Object getPermissions() { + return permissions; + } + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java new file mode 100644 index 0000000..d51d7d5 --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java @@ -0,0 +1,169 @@ +/** + * 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.hadoop.gateway.service.config.remote.zk; + +import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationMessages; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; +import org.apache.hadoop.gateway.services.security.AliasService; +import org.apache.hadoop.gateway.services.security.AliasServiceException; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Configuration decorator that adds SASL JAAS configuration to whatever JAAS config is already applied. + */ +class RemoteConfigurationRegistryJAASConfig extends Configuration { + + // Underlying SASL mechanisms supported + enum SASLMechanism { + Kerberos, + Digest + } + + static final Map<String, String> digestLoginModules = new HashMap<>(); + static { + digestLoginModules.put("ZOOKEEPER", "org.apache.zookeeper.server.auth.DigestLoginModule"); + } + + private static final RemoteConfigurationMessages log = MessagesFactory.get(RemoteConfigurationMessages.class); + + // Cache the current JAAS configuration + private Configuration delegate = Configuration.getConfiguration(); + + private AliasService aliasService; + + private Map<String, AppConfigurationEntry[]> contextEntries = new HashMap<>(); + + static RemoteConfigurationRegistryJAASConfig configure(List<RemoteConfigurationRegistryConfig> configs, AliasService aliasService) { + return new RemoteConfigurationRegistryJAASConfig(configs, aliasService); + } + + private RemoteConfigurationRegistryJAASConfig(List<RemoteConfigurationRegistryConfig> configs, AliasService aliasService) { + this.aliasService = aliasService; + + // Populate context entries + List<AppConfigurationEntry> appConfigEntries = new ArrayList<>(); + for (RemoteConfigurationRegistryConfig config : configs) { + if (config.isSecureRegistry()) { + contextEntries.put(config.getName(), createEntries(config)); + } + } + + // If there is at least one context entry, then set this as the client configuration + if (!contextEntries.isEmpty()) { + // TODO: PJZ: ZooKeeper 3.6.0 will have per-client JAAS Configuration support; Upgrade ASAP!! + // For now, set this as the static JAAS configuration + Configuration.setConfiguration(this); + } + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + AppConfigurationEntry[] result = null; + + // First, try the delegate's context entries + result = delegate.getAppConfigurationEntry(name); + if (result == null || result.length < 1) { + // Try our additional context entries + result = contextEntries.get(name); + } + + return result; + } + + private AppConfigurationEntry[] createEntries(RemoteConfigurationRegistryConfig config) { + // Only supporting a single app config entry per configuration/context + AppConfigurationEntry[] result = new AppConfigurationEntry[1]; + result[0] = createEntry(config); + return result; + } + + private AppConfigurationEntry createEntry(RemoteConfigurationRegistryConfig config) { + AppConfigurationEntry entry = null; + + Map<String, String> opts = new HashMap<>(); + SASLMechanism saslMechanism = getSASLMechanism(config.getAuthType()); + switch (saslMechanism) { + case Digest: + // Digest auth options + opts.put("username", config.getPrincipal()); + + char[] credential = null; + if (aliasService != null) { + try { + credential = aliasService.getPasswordFromAliasForGateway(config.getCredentialAlias()); + } catch (AliasServiceException e) { + log.unresolvedCredentialAlias(config.getCredentialAlias()); + } + } else { + throw new IllegalArgumentException("The AliasService is required to resolve credential aliases."); + } + + if (credential != null) { + opts.put("password", new String(credential)); + } + break; + case Kerberos: + opts.put("isUseTicketCache", String.valueOf(config.isUseTicketCache())); + opts.put("isUseKeyTab", String.valueOf(config.isUseKeyTab())); + opts.put("keyTab", config.getKeytab()); + opts.put("principal", config.getPrincipal()); + } + + entry = new AppConfigurationEntry(getLoginModuleName(config.getRegistryType(), saslMechanism), + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + opts); + + return entry; + } + + private static String getLoginModuleName(String registryType, SASLMechanism saslMechanism) { + String loginModuleName = null; + + switch (saslMechanism) { + case Kerberos: + if (System.getProperty("java.vendor").contains("IBM")) { + loginModuleName = "com.ibm.security.auth.module.Krb5LoginModule"; + } else { + loginModuleName = "com.sun.security.auth.module.Krb5LoginModule"; + } + break; + case Digest: + loginModuleName = digestLoginModules.get(registryType.toUpperCase()); + } + return loginModuleName; + } + + private static SASLMechanism getSASLMechanism(String authType) { + SASLMechanism result = null; + for (SASLMechanism at : SASLMechanism.values()) { + if (at.name().equalsIgnoreCase(authType)) { + result = at; + break; + } + } + return result; + } + + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java new file mode 100644 index 0000000..c4add4a --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java @@ -0,0 +1,25 @@ +/** + * 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.hadoop.gateway.service.config.remote.zk; + +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService; + +public interface ZooKeeperClientService extends RemoteConfigurationRegistryClientService { + + String TYPE = "ZooKeeper"; + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java new file mode 100644 index 0000000..f30d3da --- /dev/null +++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java @@ -0,0 +1,34 @@ +/** + * 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.hadoop.gateway.service.config.remote.zk; + +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider; + + +public class ZooKeeperClientServiceProvider implements RemoteConfigurationRegistryClientServiceProvider { + + @Override + public String getType() { + return ZooKeeperClientService.TYPE; + } + + @Override + public ZooKeeperClientService newInstance() { + return new CuratorClientService(); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider b/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider new file mode 100644 index 0000000..7f2312a --- /dev/null +++ b/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider @@ -0,0 +1,19 @@ +########################################################################## +# 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. +########################################################################## + +org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java new file mode 100644 index 0000000..a33fcc2 --- /dev/null +++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java @@ -0,0 +1,184 @@ +/** + * 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.hadoop.gateway.service.config.remote.config; + +import org.apache.hadoop.gateway.config.GatewayConfig; +import org.easymock.EasyMock; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class DefaultRemoteConfigurationRegistriesTest { + + /** + * Test a single registry configuration with digest auth configuration. + */ + @Test + public void testPropertiesRemoteConfigurationRegistriesSingleDigest() throws Exception { + Map<String, Properties> testProperties = new HashMap<>(); + Properties p = new Properties(); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "hostx:2181"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "zkDigestUser"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "digest"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS, "zkDigestAlias"); + testProperties.put("testDigest", p); + + doTestPropertiesRemoteConfigurationRegistries(testProperties); + } + + + /** + * Test a single registry configuration with kerberos auth configuration. + */ + @Test + public void testPropertiesRemoteConfigurationRegistriesSingleKerberos() throws Exception { + Map<String, Properties> testProperties = new HashMap<>(); + Properties p = new Properties(); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "hostx:2181"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "zkUser"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "kerberos"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB, "/home/user/remoteregistry.keytab"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB, "true"); + p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE, "false"); + testProperties.put("testKerb", p); + + doTestPropertiesRemoteConfigurationRegistries(testProperties); + } + + /** + * Test multiple registry configuration with varying auth configurations. + */ + @Test + public void testPropertiesRemoteConfigurationRegistriesMultipleMixed() throws Exception { + Map<String, Properties> testProperties = new HashMap<>(); + + Properties kerb = new Properties(); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host1:2181"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_NAMESPACE, "/knox/config"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "kerbPrincipal"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "kerberos"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB, "/home/user/mykrb.keytab"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB, "true"); + kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE, "false"); + testProperties.put("testKerb1", kerb); + + Properties digest = new Properties(); + digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper"); + digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host2:2181"); + digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "digestPrincipal"); + digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "digest"); + digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS, "digestPwdAlias"); + testProperties.put("testDigest1", digest); + + Properties unsecured = new Properties(); + unsecured.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper"); + unsecured.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host2:2181"); + testProperties.put("testUnsecured", unsecured); + + doTestPropertiesRemoteConfigurationRegistries(testProperties); + } + + + /** + * Perform the actual test. + * + * @param testProperties The test properties + */ + private void doTestPropertiesRemoteConfigurationRegistries(Map<String, Properties> testProperties) throws Exception { + // Mock gateway config + GatewayConfig gc = mockGatewayConfig(testProperties); + + // Create the RemoteConfigurationRegistries object to be tested from the GatewayConfig + RemoteConfigurationRegistries registries = new DefaultRemoteConfigurationRegistries(gc); + + // Basic validation + assertNotNull(registries); + List<RemoteConfigurationRegistry> registryConfigs = registries.getRegistryConfigurations(); + assertNotNull(registryConfigs); + assertEquals(testProperties.size(), registryConfigs.size()); + + // Validate the contents of the created object + for (RemoteConfigurationRegistry regConfig : registryConfigs) { + validateRemoteRegistryConfig(regConfig.getName(), testProperties.get(regConfig.getName()), regConfig); + } + } + + + /** + * Create a mock GatewayConfig based on the specified test properties. + * + * @param testProperties The test properties to set on the config + */ + private GatewayConfig mockGatewayConfig(Map<String, Properties> testProperties) { + // Mock gateway config + GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class); + List<String> configNames = new ArrayList<>(); + for (String registryName : testProperties.keySet()) { + configNames.add(registryName); + + String propertyValueString = ""; + Properties props = testProperties.get(registryName); + Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + String propertyName = (String) names.nextElement(); + propertyValueString += propertyName + "=" + props.get(propertyName); + if (names.hasMoreElements()) { + propertyValueString += ";"; + } + } + EasyMock.expect(gc.getRemoteRegistryConfiguration(registryName)) + .andReturn(propertyValueString) + .anyTimes(); + } + EasyMock.expect(gc.getRemoteRegistryConfigurationNames()).andReturn(configNames).anyTimes(); + EasyMock.replay(gc); + + return gc; + } + + + /** + * Validate the specified RemoteConfigurationRegistry based on the expected test properties. + */ + private void validateRemoteRegistryConfig(String configName, + Properties expected, + RemoteConfigurationRegistry registryConfig) throws Exception { + assertEquals(configName, registryConfig.getName()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE), registryConfig.getRegistryType()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS), registryConfig.getConnectionString()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_NAMESPACE), registryConfig.getNamespace()); + assertEquals(registryConfig.isSecureRegistry(), expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE) != null); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE), registryConfig.getAuthType()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL), registryConfig.getPrincipal()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS), registryConfig.getCredentialAlias()); + assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB), registryConfig.getKeytab()); + assertEquals(Boolean.valueOf((String)expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB)), registryConfig.isUseKeyTab()); + assertEquals(Boolean.valueOf((String)expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE)), registryConfig.isUseTicketCache()); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java new file mode 100644 index 0000000..386e332 --- /dev/null +++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java @@ -0,0 +1,108 @@ +/** + * 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.hadoop.gateway.service.config.remote.config; + +import org.apache.commons.io.FileUtils; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig; +import org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import static org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils.*; + +public class RemoteConfigurationRegistryConfigParserTest { + + @Test + public void testExternalXMLParsing() throws Exception { + final String CONN_STR = "http://my.zookeeper.host:2181"; + + Map<String, Map<String, String>> testRegistryConfigurations = new HashMap<>(); + + Map<String, String> config1 = new HashMap<>(); + config1.put(PROPERTY_TYPE, "ZooKeeper"); + config1.put(PROPERTY_NAME, "registry1"); + config1.put(PROPERTY_ADDRESS, CONN_STR); + config1.put(PROPERTY_SECURE, "true"); + config1.put(PROPERTY_AUTH_TYPE, "Digest"); + config1.put(PROPERTY_PRINCIPAL, "knox"); + config1.put(PROPERTY_CRED_ALIAS, "zkCredential"); + testRegistryConfigurations.put(config1.get("name"), config1); + + Map<String, String> config2 = new HashMap<>(); + config2.put(PROPERTY_TYPE, "ZooKeeper"); + config2.put(PROPERTY_NAME, "MyKerberos"); + config2.put(PROPERTY_ADDRESS, CONN_STR); + config2.put(PROPERTY_SECURE, "true"); + config2.put(PROPERTY_AUTH_TYPE, "Kerberos"); + config2.put(PROPERTY_PRINCIPAL, "knox"); + File myKeyTab = File.createTempFile("mytest", "keytab"); + config2.put(PROPERTY_KEYTAB, myKeyTab.getAbsolutePath()); + config2.put(PROPERTY_USE_KEYTAB, "false"); + config2.put(PROPERTY_USE_TICKET_CACHE, "true"); + testRegistryConfigurations.put(config2.get("name"), config2); + + Map<String, String> config3 = new HashMap<>(); + config3.put(PROPERTY_TYPE, "ZooKeeper"); + config3.put(PROPERTY_NAME, "anotherRegistry"); + config3.put(PROPERTY_ADDRESS, "whatever:1281"); + testRegistryConfigurations.put(config3.get("name"), config3); + + String configXML = + RemoteRegistryConfigTestUtils.createRemoteConfigRegistriesXML(testRegistryConfigurations.values()); + + File registryConfigFile = File.createTempFile("remote-registries", "xml"); + try { + FileUtils.writeStringToFile(registryConfigFile, configXML); + + List<RemoteConfigurationRegistryConfig> configs = + RemoteConfigurationRegistriesParser.getConfig(registryConfigFile.getAbsolutePath()); + assertNotNull(configs); + assertEquals(testRegistryConfigurations.keySet().size(), configs.size()); + + for (RemoteConfigurationRegistryConfig registryConfig : configs) { + Map<String, String> expected = testRegistryConfigurations.get(registryConfig.getName()); + assertNotNull(expected); + validateParsedRegistryConfiguration(registryConfig, expected); + } + } finally { + registryConfigFile.delete(); + } + } + + private void validateParsedRegistryConfiguration(RemoteConfigurationRegistryConfig config, + Map<String, String> expected) throws Exception { + assertEquals(expected.get(PROPERTY_TYPE), config.getRegistryType()); + assertEquals(expected.get(PROPERTY_ADDRESS), config.getConnectionString()); + assertEquals(expected.get(PROPERTY_NAME), config.getName()); + assertEquals(expected.get(PROPERTY_NAMESAPCE), config.getNamespace()); + assertEquals(Boolean.valueOf(expected.get(PROPERTY_SECURE)), config.isSecureRegistry()); + assertEquals(expected.get(PROPERTY_AUTH_TYPE), config.getAuthType()); + assertEquals(expected.get(PROPERTY_PRINCIPAL), config.getPrincipal()); + assertEquals(expected.get(PROPERTY_CRED_ALIAS), config.getCredentialAlias()); + assertEquals(expected.get(PROPERTY_KEYTAB), config.getKeytab()); + assertEquals(Boolean.valueOf(expected.get(PROPERTY_USE_KEYTAB)), config.isUseKeyTab()); + assertEquals(Boolean.valueOf(expected.get(PROPERTY_USE_TICKET_CACHE)), config.isUseTicketCache()); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java new file mode 100644 index 0000000..35919d0 --- /dev/null +++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java @@ -0,0 +1,117 @@ +/** + * 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.hadoop.gateway.service.config.remote.util; + +import java.util.Collection; +import java.util.Map; + +public class RemoteRegistryConfigTestUtils { + + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_NAME = "name"; + public static final String PROPERTY_ADDRESS = "address"; + public static final String PROPERTY_NAMESAPCE = "namespace"; + public static final String PROPERTY_SECURE = "secure"; + public static final String PROPERTY_AUTH_TYPE = "authType"; + public static final String PROPERTY_PRINCIPAL = "principal"; + public static final String PROPERTY_CRED_ALIAS = "credentialAlias"; + public static final String PROPERTY_KEYTAB = "keyTab"; + public static final String PROPERTY_USE_KEYTAB = "useKeyTab"; + public static final String PROPERTY_USE_TICKET_CACHE = "useTicketCache"; + + public static String createRemoteConfigRegistriesXML(Collection<Map<String, String>> configProperties) { + String result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<remote-configuration-registries>\n"; + + for (Map<String, String> props : configProperties) { + String authType = props.get(PROPERTY_AUTH_TYPE); + if ("Kerberos".equalsIgnoreCase(authType)) { + result += + createRemoteConfigRegistryXMLWithKerberosAuth(props.get(PROPERTY_TYPE), + props.get(PROPERTY_NAME), + props.get(PROPERTY_ADDRESS), + props.get(PROPERTY_PRINCIPAL), + props.get(PROPERTY_KEYTAB), + Boolean.valueOf(props.get(PROPERTY_USE_KEYTAB)), + Boolean.valueOf(props.get(PROPERTY_USE_TICKET_CACHE))); + } else if ("Digest".equalsIgnoreCase(authType)) { + result += + createRemoteConfigRegistryXMLWithDigestAuth(props.get(PROPERTY_TYPE), + props.get(PROPERTY_NAME), + props.get(PROPERTY_ADDRESS), + props.get(PROPERTY_PRINCIPAL), + props.get(PROPERTY_CRED_ALIAS)); + } else { + result += createRemoteConfigRegistryXMLNoAuth(props.get(PROPERTY_TYPE), + props.get(PROPERTY_NAME), + props.get(PROPERTY_ADDRESS)); + } + } + + result += "</remote-configuration-registries>\n"; + + return result; + } + + public static String createRemoteConfigRegistryXMLWithKerberosAuth(String type, + String name, + String address, + String principal, + String keyTab, + boolean userKeyTab, + boolean useTicketCache) { + return " <remote-configuration-registry>\n" + + " <name>" + name + "</name>\n" + + " <type>" + type + "</type>\n" + + " <address>" + address + "</address>\n" + + " <secure>true</secure>\n" + + " <auth-type>" + "Kerberos" + "</auth-type>\n" + + " <principal>" + principal + "</principal>\n" + + " <keytab>" + keyTab + "</keytab>\n" + + " <use-keytab>" + String.valueOf(userKeyTab) + "</use-keytab>\n" + + " <use-ticket-cache>" + String.valueOf(useTicketCache) + "</use-ticket-cache>\n" + + " </remote-configuration-registry>\n"; + } + + public static String createRemoteConfigRegistryXMLWithDigestAuth(String type, + String name, + String address, + String principal, + String credentialAlias) { + return " <remote-configuration-registry>\n" + + " <name>" + name + "</name>\n" + + " <type>" + type + "</type>\n" + + " <address>" + address + "</address>\n" + + " <secure>true</secure>\n" + + " <auth-type>" + "Digest" + "</auth-type>\n" + + " <principal>" + principal + "</principal>\n" + + " <credential-alias>" + credentialAlias + "</credential-alias>\n" + + " </remote-configuration-registry>\n"; + } + + + public static String createRemoteConfigRegistryXMLNoAuth(String type, + String name, + String address) { + return " <remote-configuration-registry>\n" + + " <name>" + name + "</name>\n" + + " <type>" + type + "</type>\n" + + " <address>" + address + "</address>\n" + + " </remote-configuration-registry>\n"; + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java new file mode 100644 index 0000000..0292ee3 --- /dev/null +++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java @@ -0,0 +1,424 @@ +/** + * 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.hadoop.gateway.service.config.remote.zk; + +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.hadoop.gateway.config.GatewayConfig; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient; +import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService; +import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceFactory; +import org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils; +import org.apache.hadoop.gateway.services.security.AliasService; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.easymock.EasyMock; +import org.junit.Test; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class RemoteConfigurationRegistryClientServiceTest { + + /** + * Test a configuration for an unsecured remote registry, included in the gateway configuration. + */ + @Test + public void testUnsecuredZooKeeperWithSimpleRegistryConfig() throws Exception { + final String REGISTRY_CLIENT_NAME = "unsecured-zk-registry-name"; + final String PRINCIPAL = null; + final String PWD = null; + final String CRED_ALIAS = null; + + // Configure and start a secure ZK cluster + TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD); + + try { + // Create the setup client for the test cluster, and initialize the test znodes + CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL); + + // Mock configuration + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + final String registryConfigValue = + GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString(); + EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME)) + .andReturn(registryConfigValue) + .anyTimes(); + EasyMock.expect(config.getRemoteRegistryConfigurationNames()) + .andReturn(Collections.singletonList(REGISTRY_CLIENT_NAME)).anyTimes(); + EasyMock.replay(config); + + doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD); + } finally { + zkCluster.stop(); + } + } + + /** + * Test multiple configurations for an unsecured remote registry. + */ + @Test + public void testMultipleUnsecuredZooKeeperWithSimpleRegistryConfig() throws Exception { + final String REGISTRY_CLIENT_NAME_1 = "zkclient1"; + final String REGISTRY_CLIENT_NAME_2 = "zkclient2"; + final String PRINCIPAL = null; + final String PWD = null; + final String CRED_ALIAS = null; + + // Configure and start a secure ZK cluster + TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD); + + try { + // Create the setup client for the test cluster, and initialize the test znodes + CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL); + + // Mock configuration + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + final String registryConfigValue1 = + GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString(); + EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME_1)) + .andReturn(registryConfigValue1).anyTimes(); + final String registryConfigValue2 = + GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString(); + EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME_2)) + .andReturn(registryConfigValue2).anyTimes(); + EasyMock.expect(config.getRemoteRegistryConfigurationNames()) + .andReturn(Arrays.asList(REGISTRY_CLIENT_NAME_1, REGISTRY_CLIENT_NAME_2)).anyTimes(); + EasyMock.replay(config); + + // Create the client service instance + RemoteConfigurationRegistryClientService clientService = + RemoteConfigurationRegistryClientServiceFactory.newInstance(config); + assertEquals("Wrong registry client service type.", clientService.getClass(), CuratorClientService.class); + clientService.setAliasService(null); + clientService.init(config, null); + clientService.start(); + + RemoteConfigurationRegistryClient client1 = clientService.get(REGISTRY_CLIENT_NAME_1); + assertNotNull(client1); + + RemoteConfigurationRegistryClient client2 = clientService.get(REGISTRY_CLIENT_NAME_2); + assertNotNull(client2); + + doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME_1, clientService, false); + doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME_2, clientService, false); + } finally { + zkCluster.stop(); + } + } + + /** + * Test a configuration for a secure remote registry, included in the gateway configuration. + */ + @Test + public void testZooKeeperWithSimpleRegistryConfig() throws Exception { + final String AUTH_TYPE = "digest"; + final String REGISTRY_CLIENT_NAME = "zk-registry-name"; + final String PRINCIPAL = "knox"; + final String PWD = "knoxtest"; + final String CRED_ALIAS = "zkCredential"; + + // Configure and start a secure ZK cluster + TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD); + + try { + // Create the setup client for the test cluster, and initialize the test znodes + CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL); + + // Mock configuration + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + final String registryConfigValue = + GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString() + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE + "=" + AUTH_TYPE + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL + "=" + PRINCIPAL + ";" + + GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS + "=" + CRED_ALIAS; + EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME)) + .andReturn(registryConfigValue) + .anyTimes(); + EasyMock.expect(config.getRemoteRegistryConfigurationNames()) + .andReturn(Collections.singletonList(REGISTRY_CLIENT_NAME)).anyTimes(); + EasyMock.replay(config); + + doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD); + } finally { + zkCluster.stop(); + } + } + + /** + * Test the remote registry configuration external to, and referenced from, the gateway configuration, for a secure + * client. + */ + @Test + public void testZooKeeperWithSingleExternalRegistryConfig() throws Exception { + final String AUTH_TYPE = "digest"; + final String REGISTRY_CLIENT_NAME = "my-zookeeper_registryNAME"; + final String PRINCIPAL = "knox"; + final String PWD = "knoxtest"; + final String CRED_ALIAS = "zkCredential"; + + // Configure and start a secure ZK cluster + TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD); + + File tmpRegConfigFile = null; + + try { + // Create the setup client for the test cluster, and initialize the test znodes + CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL); + + // Mock configuration + Map<String, String> registryConfigProps = new HashMap<>(); + registryConfigProps.put("type", ZooKeeperClientService.TYPE); + registryConfigProps.put("name", REGISTRY_CLIENT_NAME); + registryConfigProps.put("address", zkCluster.getConnectString()); + registryConfigProps.put("secure", "true"); + registryConfigProps.put("authType", AUTH_TYPE); + registryConfigProps.put("principal", PRINCIPAL); + registryConfigProps.put("credentialAlias", CRED_ALIAS); + String registryConfigXML = + RemoteRegistryConfigTestUtils.createRemoteConfigRegistriesXML(Collections.singleton(registryConfigProps)); + tmpRegConfigFile = File.createTempFile("myRemoteRegistryConfig", "xml"); + FileUtils.writeStringToFile(tmpRegConfigFile, registryConfigXML); + + System.setProperty("org.apache.knox.gateway.remote.registry.config.file", tmpRegConfigFile.getAbsolutePath()); + + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + EasyMock.replay(config); + + doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD); + } finally { + zkCluster.stop(); + if (tmpRegConfigFile != null && tmpRegConfigFile.exists()) { + tmpRegConfigFile.delete(); + } + System.clearProperty("org.apache.knox.gateway.remote.registry.config.file"); + } + } + + /** + * Setup and start a secure test ZooKeeper cluster. + */ + private TestingCluster setupAndStartSecureTestZooKeeper(String principal, String digestPassword) throws Exception { + final boolean applyAuthentication = (principal != null); + + // Configure security for the ZK cluster instances + Map<String, Object> customInstanceSpecProps = new HashMap<>(); + + if (applyAuthentication) { + customInstanceSpecProps.put("authProvider.1", "org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + customInstanceSpecProps.put("requireClientAuthScheme", "sasl"); + } + + // Define the test cluster + List<InstanceSpec> instanceSpecs = new ArrayList<>(); + for (int i = 0 ; i < 3 ; i++) { + InstanceSpec is = new InstanceSpec(null, -1, -1, -1, false, (i+1), -1, -1, customInstanceSpecProps); + instanceSpecs.add(is); + } + TestingCluster zkCluster = new TestingCluster(instanceSpecs); + + if (applyAuthentication) { + // Setup ZooKeeper server SASL + Map<String, String> digestOptions = new HashMap<>(); + digestOptions.put("user_" + principal, digestPassword); + final AppConfigurationEntry[] serverEntries = + {new AppConfigurationEntry("org.apache.zookeeper.server.auth.DigestLoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + digestOptions)}; + Configuration.setConfiguration(new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + return ("Server".equalsIgnoreCase(name)) ? serverEntries : null; + } + }); + } + + // Start the cluster + zkCluster.start(); + + return zkCluster; + } + + /** + * Create a ZooKeeper client with SASL digest auth configured, and initialize the test znodes. + */ + private CuratorFramework initializeTestClientAndZNodes(TestingCluster zkCluster, String principal) throws Exception { + // Create the client for the test cluster + CuratorFramework setupClient = CuratorFrameworkFactory.builder() + .connectString(zkCluster.getConnectString()) + .retryPolicy(new ExponentialBackoffRetry(100, 3)) + .build(); + assertNotNull(setupClient); + setupClient.start(); + + List<ACL> acls = new ArrayList<>(); + if (principal != null) { + acls.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", principal))); + } else { + acls.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE)); + } + setupClient.create().creatingParentsIfNeeded().withACL(acls).forPath("/knox/config/descriptors"); + setupClient.create().creatingParentsIfNeeded().withACL(acls).forPath("/knox/config/shared-providers"); + + List<ACL> negativeACLs = new ArrayList<>(); + if (principal != null) { + negativeACLs.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", "notyou"))); + } else { + negativeACLs.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE)); + } + setupClient.create().creatingParentsIfNeeded().withACL(negativeACLs).forPath("/someotherconfig"); + + return setupClient; + } + + private void doTestZooKeeperClient(final CuratorFramework setupClient, + final String testClientName, + final GatewayConfig config, + final String credentialAlias, + final String digestPassword) throws Exception { + boolean isSecureTest = (credentialAlias != null && digestPassword != null); + + // Mock alias service + AliasService aliasService = EasyMock.createNiceMock(AliasService.class); + EasyMock.expect(aliasService.getPasswordFromAliasForGateway(credentialAlias)) + .andReturn(isSecureTest ? digestPassword.toCharArray() : null) + .anyTimes(); + EasyMock.replay(aliasService); + + // Create the client service instance + RemoteConfigurationRegistryClientService clientService = + RemoteConfigurationRegistryClientServiceFactory.newInstance(config); + assertEquals("Wrong registry client service type.", clientService.getClass(), CuratorClientService.class); + clientService.setAliasService(aliasService); + clientService.init(config, null); + clientService.start(); + + doTestZooKeeperClient(setupClient, testClientName, clientService, isSecureTest); + } + + /** + * Test secure ZooKeeper client interactions. + * + * @param setupClient The client used for interacting with ZooKeeper independent from the registry client service. + * @param testClientName The name of the client to use from the registry client service. + * @param clientService The RemoteConfigurationRegistryClientService + * @param isSecureTest Flag to indicate whether this is a secure interaction test + */ + private void doTestZooKeeperClient(final CuratorFramework setupClient, + final String testClientName, + final RemoteConfigurationRegistryClientService clientService, + boolean isSecureTest) throws Exception { + + RemoteConfigurationRegistryClient client = clientService.get(testClientName); + assertNotNull(client); + List<String> descriptors = client.listChildEntries("/knox/config/descriptors"); + assertNotNull(descriptors); + for (String descriptor : descriptors) { + System.out.println("Descriptor: " + descriptor); + } + + List<String> providerConfigs = client.listChildEntries("/knox/config/shared-providers"); + assertNotNull(providerConfigs); + for (String providerConfig : providerConfigs) { + System.out.println("Provider config: " + providerConfig); + } + + List<String> someotherConfig = client.listChildEntries("/someotherconfig"); + if (isSecureTest) { + assertNull("Expected null because of the ACL mismatch.", someotherConfig); + } else { + assertNotNull(someotherConfig); + } + + // Test listeners + final String MY_NEW_ZNODE = "/clientServiceTestNode"; + final String MY_NEW_DATA_ZNODE = MY_NEW_ZNODE + "/mydata"; + + if (setupClient.checkExists().forPath(MY_NEW_ZNODE) != null) { + setupClient.delete().deletingChildrenIfNeeded().forPath(MY_NEW_ZNODE); + } + + final List<String> listenerLog = new ArrayList<>(); + client.addChildEntryListener(MY_NEW_ZNODE, (c, type, path) -> { + listenerLog.add("EXTERNAL: " + type.toString() + ":" + path); + if (ChildEntryListener.Type.ADDED.equals(type)) { + try { + c.addEntryListener(path, (cc, p, d) -> listenerLog.add("EXTERNAL: " + p + ":" + (d != null ? new String(d) : "null"))); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + client.createEntry(MY_NEW_ZNODE); + client.createEntry(MY_NEW_DATA_ZNODE, "more test data"); + String testData = client.getEntryData(MY_NEW_DATA_ZNODE); + assertNotNull(testData); + assertEquals("more test data", testData); + + assertTrue(client.entryExists(MY_NEW_DATA_ZNODE)); + client.setEntryData(MY_NEW_DATA_ZNODE, "still more data"); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // + } + + client.setEntryData(MY_NEW_DATA_ZNODE, "changed completely"); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // + } + + client.deleteEntry(MY_NEW_DATA_ZNODE); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // + } + + assertFalse(listenerLog.isEmpty()); + } + +}