This is an automated email from the ASF dual-hosted git repository. sai_boorlagadda pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push: new 8354b61 GEODE-3875: gfsh command to create jndi binding (#1475) 8354b61 is described below commit 8354b61afb363db3eea44e31a496c1a4372e1d60 Author: Sai Boorlagadda <sai.boorlaga...@gmail.com> AuthorDate: Fri Feb 23 06:15:16 2018 -0800 GEODE-3875: gfsh command to create jndi binding (#1475) --- .../internal/datasource/AbstractPoolCache.java | 11 +- .../datasource/ConfiguredDataSourceProperties.java | 11 +- .../internal/datasource/ManagedPoolCacheImpl.java | 2 +- .../internal/datasource/TranxPoolCacheImpl.java | 2 +- .../apache/geode/internal/jndi/JNDIInvoker.java | 9 +- .../cli/commands/CommandAvailabilityIndicator.java | 3 +- .../cli/commands/CreateJndiBindingCommand.java | 258 +++++++++++++ .../cli/converters/ConfigPropertyConverter.java | 58 +++ .../cli/functions/CreateJndiBindingFunction.java | 38 ++ .../cli/functions/JndiBindingConfiguration.java | 217 +++++++++++ .../configuration/domain/Configuration.java | 13 +- .../sanctioned-geode-core-serializables.txt | 3 + .../CreateAsyncEventQueueCommandDUnitTest.java | 3 +- .../CreateDefinedIndexesCommandDUnitTest.java | 5 +- .../CreateJndiBindingCommandDUnitTest.java | 106 ++++++ .../cli/commands/CreateJndiBindingCommandTest.java | 408 +++++++++++++++++++++ .../converters/ConfigPropertyConverterTest.java | 75 ++++ 17 files changed, 1197 insertions(+), 25 deletions(-) diff --git a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java index 2af7f47..ff1ba43 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java +++ b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java @@ -39,11 +39,6 @@ import org.apache.geode.internal.logging.LoggingThreadGroup; * connection pools. The class also implements the Serializable interface. The pool maintain a list * for keeping the available connections(not assigned to user) and the active connections(assigned * to user) This is a thread safe class. - * - * Second Version .Modified the synchronization code & objects on which locks were being taken. - * Changed the logic of retrieval of connection & returning of connection. The beahviour of cleaner - * thread has been modified such that it waits on activeCache if it is empty. Prevention of - * deadlocks & optmization of code. */ public abstract class AbstractPoolCache implements ConnectionPoolCache, Serializable { @@ -56,11 +51,9 @@ public abstract class AbstractPoolCache implements ConnectionPoolCache, Serializ protected EventListener connEventListner; // private String error = ""; protected ConfiguredDataSourceProperties configProps; - // Asif:expirationTime is for the available - // connection which are expired in milliseconds + // expirationTime is for the available connection which are expired in milliseconds protected int expirationTime; - // Asif:timeOut is for the Active connection which are time out in - // milliseconds + // timeOut is for the Active connection which are time out in milliseconds protected int timeOut; // Client Timeout in milliseconds protected int loginTimeOut; diff --git a/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java b/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java index c947de8..e036f39 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java +++ b/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java @@ -14,17 +14,18 @@ */ package org.apache.geode.internal.datasource; +import java.io.PrintWriter; +import java.io.Serializable; + /** - * JavaBean for datasource and poold properties. + * JavaBean for datasource and pooled properties. * - * This class now contains only those paramaters which are needed by the Gemfire DataSource - * configuration. This maps to those paramaters which are specified as attributes of + * This class now contains only those parameters which are needed by the Gemfire DataSource + * configuration. This maps to those parameters which are specified as attributes of * <jndi-binding>tag. Those parameters which are specified as attributes of <property>tag are not * stored. * */ -import java.io.*; - public class ConfiguredDataSourceProperties implements Serializable { private static final long serialVersionUID = 1241739895646314739L; diff --git a/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java b/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java index 8702f70..f3cf49b 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java +++ b/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java @@ -30,7 +30,7 @@ import org.apache.geode.internal.logging.LogService; /** * This class implements a connection pool for Managed connection. Extends the AbstractPoolCache to - * inherit the pool bahavior. + * inherit the pool behavior. * */ public class ManagedPoolCacheImpl extends AbstractPoolCache { diff --git a/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java b/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java index 9a59aeb..c577471 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java +++ b/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java @@ -29,7 +29,7 @@ import org.apache.geode.internal.logging.LogService; /** * This class models a connection pool for transactional database connection. Extends the - * AbstractPoolCache to inherit the pool bahavior. + * AbstractPoolCache to inherit the pool behavior. * */ public class TranxPoolCacheImpl extends AbstractPoolCache { diff --git a/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java b/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java index 73377b9..99898c5 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java +++ b/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java @@ -330,6 +330,7 @@ public class JNDIInvoker { } else if (value.equals("SimpleDataSource")) { ds = DataSourceFactory.getSimpleDataSource(map, props); ctx.rebind("java:/" + jndiName, ds); + dataSourceList.add(ds); if (writer.fineEnabled()) writer.fine("Bound java:/" + jndiName + " to Context"); } else if (value.equals("ManagedDataSource")) { @@ -374,8 +375,8 @@ public class JNDIInvoker { public static TransactionManager getTransactionManager() { return transactionManager; } - // try to find websphere lookups since we came here - /* - * private static void print(String str) { if (DEBUG) { System.err.println(str); } } - */ + + public static int getNoOfAvailableDataSources() { + return dataSourceList.size(); + } } diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java index cfdeaa5..f6939f3 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java @@ -49,7 +49,8 @@ public class CommandAvailabilityIndicator implements CommandMarker { CliStrings.CREATE_GATEWAYRECEIVER, CliStrings.START_GATEWAYRECEIVER, CliStrings.STOP_GATEWAYRECEIVER, CliStrings.LIST_GATEWAY, CliStrings.STATUS_GATEWAYSENDER, CliStrings.STATUS_GATEWAYRECEIVER, CliStrings.LOAD_BALANCE_GATEWAYSENDER, - CliStrings.DESTROY_GATEWAYSENDER, AlterAsyncEventQueueCommand.COMMAND_NAME}) + CliStrings.DESTROY_GATEWAYSENDER, AlterAsyncEventQueueCommand.COMMAND_NAME, + CreateJndiBindingCommand.CREATE_JNDIBINDING}) public boolean clientCommandsAvailable() { Gfsh gfsh = Gfsh.getCurrentInstance(); diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java new file mode 100644 index 0000000..19abfd9 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java @@ -0,0 +1,258 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.apache.geode.management.internal.cli.result.ResultBuilder.buildResult; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.logging.log4j.Logger; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.ClusterConfigurationService; +import org.apache.geode.internal.datasource.ConfigProperty; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.Result; +import org.apache.geode.management.internal.cli.exceptions.EntityExistsException; +import org.apache.geode.management.internal.cli.functions.CliFunctionResult; +import org.apache.geode.management.internal.cli.functions.CreateJndiBindingFunction; +import org.apache.geode.management.internal.cli.functions.JndiBindingConfiguration; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.result.ResultBuilder; +import org.apache.geode.management.internal.configuration.domain.Configuration; +import org.apache.geode.management.internal.configuration.utils.XmlUtils; +import org.apache.geode.management.internal.security.ResourceOperation; +import org.apache.geode.security.ResourcePermission; + +public class CreateJndiBindingCommand implements GfshCommand { + private static final Logger logger = LogService.getLogger(); + + static final String CREATE_JNDIBINDING = "create jndi-binding"; + static final String CREATE_JNDIBINDING__HELP = + "Create a jndi binding that holds the configuration for the XA datasource."; + static final String BLOCKING_TIMEOUT_SECONDS = "blocking-timeout-seconds"; + static final String BLOCKING_TIMEOUT_SECONDS__HELP = + "This element specifies the maximum time to block while waiting for a connection before throwing an exception."; + static final String CONNECTION_POOLED_DATASOURCE_CLASS = "conn-pooled-datasource-class"; + static final String CONNECTION_POOLED_DATASOURCE_CLASS__HELP = + "This is the fully qualified name of the connection pool implementation to hold XS datasource connections."; + static final String CONNECTION_URL = "connection-url"; + static final String CONNECTION_URL__HELP = + "This is the JDBC driver connection URL string, for example, jdbc:hsqldb:hsql://localhost:1701."; + static final String IDLE_TIMEOUT_SECONDS = "idle-timeout-seconds"; + static final String IDLE_TIMEOUT_SECONDS__HELP = + "This element specifies the time a connection may be idle before being closed."; + static final String INIT_POOL_SIZE = "init-pool-size"; + static final String INIT_POOL_SIZE__HELP = + "This element specifies the initial number of connections the pool should hold."; + static final String JDBC_DRIVER_CLASS = "jdbc-driver-class"; + static final String JDBC_DRIVER_CLASS__HELP = + "This is the fully qualified name of the JDBC driver class."; + static final String JNDI_NAME = "name"; + static final String JNDI_NAME__HELP = "Name of the binding to be created."; + static final String LOGIN_TIMEOUT_SECONDS = "login-timeout-seconds"; + static final String LOGIN_TIMEOUT_SECONDS__HELP = + "Time in seconds after which the client thread for retrieving connection will experience timeout."; + static final String MANAGED_CONN_FACTORY_CLASS = "managed-conn-factory-class"; + static final String MANAGED_CONN_FACTORY_CLASS__HELP = + "This is the fully qualified name of the connection factory implementation."; + static final String MAX_POOL_SIZE = "max-pool-size"; + static final String MAX_POOL_SIZE__HELP = + "This element specifies the maximum number of connections for a pool. No more than the max-pool-size number of connections will be created in a pool."; + static final String PASSWORD = "password"; + static final String PASSWORD__HELP = + "This element specifies the default password used when creating a new connection."; + static final String TRANSACTION_TYPE = "transaction-type"; + static final String TRANSACTION_TYPE__HELP = "Type of the transaction."; + static final String TYPE = "type"; + static final String TYPE__HELP = + "Type of the XA datasource. Type of region to create. The following types are pre-defined by the product: MANAGED, SIMPLE, POOLED, XAPOOLED."; + static final String USERNAME = "username"; + static final String USERNAME__HELP = + "This element specifies the default username used when creating a new connection."; + static final String XA_DATASOURCE_CLASS = "xa-datasource-class"; + static final String XA_DATASOURCE_CLASS__HELP = + "The fully qualified name of the javax.sql.XADataSource implementation class."; + static final String IFNOTEXISTS__HELP = + "Skip the create operation when a Jndi binding with the same name already exists. The default is to overwrite the entry (false)."; + static final String DATASOURCE_CONFIG_PROPERTIES = "datasource-config-properties"; + static final String DATASOURCE_CONFIG_PROPERTIES_HELP = + "Properties for the custom XSDataSource driver. Append json string containing (name, type, value) to set any property. Eg: --datasource-config-properties={'name':'name1','type':'type1','value':'value1'},{'name':'name2','type':'type2','value':'value2'}"; + + @CliCommand(value = CREATE_JNDIBINDING, help = CREATE_JNDIBINDING__HELP) + @CliMetaData(relatedTopic = CliStrings.TOPIC_GEODE_REGION) + @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER, + operation = ResourcePermission.Operation.MANAGE) + public Result createJDNIBinding( + @CliOption(key = BLOCKING_TIMEOUT_SECONDS, + help = BLOCKING_TIMEOUT_SECONDS__HELP) Integer blockingTimeout, + @CliOption(key = CONNECTION_POOLED_DATASOURCE_CLASS, + help = CONNECTION_POOLED_DATASOURCE_CLASS__HELP) String connectionPooledDatasource, + @CliOption(key = CONNECTION_URL, mandatory = true, + help = CONNECTION_URL__HELP) String connectionUrl, + @CliOption(key = IDLE_TIMEOUT_SECONDS, help = IDLE_TIMEOUT_SECONDS__HELP) Integer idleTimeout, + @CliOption(key = INIT_POOL_SIZE, help = INIT_POOL_SIZE__HELP) Integer initPoolSize, + @CliOption(key = JDBC_DRIVER_CLASS, mandatory = true, + help = JDBC_DRIVER_CLASS__HELP) String jdbcDriver, + @CliOption(key = JNDI_NAME, mandatory = true, help = JNDI_NAME__HELP) String jndiName, + @CliOption(key = LOGIN_TIMEOUT_SECONDS, + help = LOGIN_TIMEOUT_SECONDS__HELP) Integer loginTimeout, + @CliOption(key = MANAGED_CONN_FACTORY_CLASS, + help = MANAGED_CONN_FACTORY_CLASS__HELP) String managedConnFactory, + @CliOption(key = MAX_POOL_SIZE, help = MAX_POOL_SIZE__HELP) Integer maxPoolSize, + @CliOption(key = PASSWORD, help = PASSWORD__HELP) String password, + @CliOption(key = TRANSACTION_TYPE, help = TRANSACTION_TYPE__HELP) String transactionType, + @CliOption(key = TYPE, mandatory = true, + help = TYPE__HELP) JndiBindingConfiguration.DATASOURCE_TYPE type, + @CliOption(key = USERNAME, help = USERNAME__HELP) String username, + @CliOption(key = XA_DATASOURCE_CLASS, help = XA_DATASOURCE_CLASS__HELP) String xaDataSource, + @CliOption(key = CliStrings.IFNOTEXISTS, help = IFNOTEXISTS__HELP, + specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean ifNotExists, + @CliOption(key = DATASOURCE_CONFIG_PROPERTIES, optionContext = "splittingRegex=,(?![^{]*\\})", + help = DATASOURCE_CONFIG_PROPERTIES_HELP) ConfigProperty[] dsConfigProperties) + throws IOException, SAXException, ParserConfigurationException, TransformerException { + + JndiBindingConfiguration configuration = new JndiBindingConfiguration(); + configuration.setBlockingTimeout(blockingTimeout); + configuration.setConnectionPoolDatasource(connectionPooledDatasource); + configuration.setConnectionUrl(connectionUrl); + configuration.setIdleTimeout(idleTimeout); + configuration.setInitPoolSize(initPoolSize); + configuration.setJdbcDriver(jdbcDriver); + configuration.setJndiName(jndiName); + configuration.setLoginTimeout(loginTimeout); + configuration.setManagedConnFactory(managedConnFactory); + configuration.setMaxPoolSize(maxPoolSize); + configuration.setPassword(password); + configuration.setTransactionType(transactionType); + configuration.setType(type); + configuration.setUsername(username); + configuration.setXaDatasource(xaDataSource); + if (dsConfigProperties != null && dsConfigProperties.length > 0) + configuration.setDatasourceConfigurations(Arrays.asList(dsConfigProperties)); + + Result result; + boolean persisted = false; + ClusterConfigurationService service = getSharedConfiguration(); + + if (service != null) { + if (isBindingAlreadyExists(jndiName)) + throw new EntityExistsException( + CliStrings.format("Jndi binding with jndi-name \"{0}\" already exists.", jndiName), + ifNotExists); + updateXml(configuration); + persisted = true; + } + + Set<DistributedMember> targetMembers = findMembers(null, null); + if (targetMembers.size() > 0) { + List<CliFunctionResult> jndiCreationResult = executeAndGetFunctionResult( + new CreateJndiBindingFunction(), configuration, targetMembers); + result = buildResult(jndiCreationResult); + } else { + if (persisted) + result = + ResultBuilder.createInfoResult("No members found. Cluster configuration is updated."); + else + result = ResultBuilder.createInfoResult("No members found."); + } + + result.setCommandPersisted(persisted); + + return result; + } + + boolean isBindingAlreadyExists(String jndiName) + throws IOException, SAXException, ParserConfigurationException { + + Configuration config = getSharedConfiguration().getConfiguration("cluster"); + + Document document = XmlUtils.createDocumentFromXml(config.getCacheXmlContent()); + NodeList jndiBindings = document.getElementsByTagName("jndi-binding"); + + if (jndiBindings == null || jndiBindings.getLength() == 0) { + return false; + } else { + for (int i = 0; i < jndiBindings.getLength(); i++) { + Element eachBinding = (Element) jndiBindings.item(i); + if (eachBinding.getAttribute("jndi-name").equals(jndiName)) + return true; + } + } + return false; + } + + void updateXml(JndiBindingConfiguration configuration) + throws TransformerException, IOException, SAXException, ParserConfigurationException { + // cluster group config should always be present + Configuration config = getSharedConfiguration().getConfiguration("cluster"); + + Document document = XmlUtils.createDocumentFromXml(config.getCacheXmlContent()); + Node cacheNode = document.getElementsByTagName("cache").item(0); + + NodeList jndiBindingsNode = document.getElementsByTagName("jndi-bindings"); + + if (jndiBindingsNode == null || jndiBindingsNode.getLength() == 0) { + Element jndiBindings = document.createElement("jndi-bindings"); + cacheNode.appendChild(jndiBindings); + } + + Element jndiBinding = document.createElement("jndi-binding"); + jndiBindingsNode.item(0).appendChild(jndiBinding); + + for (Object key : configuration.getParamsAsMap().keySet()) { + if (configuration.getParamsAsMap().get(key) != null) + jndiBinding.setAttribute(key.toString(), + configuration.getParamsAsMap().get(key).toString()); + } + + for (ConfigProperty configProperty : configuration.getDatasourceConfigurations()) { + Element configPropertyElement = document.createElement("config-property"); + jndiBinding.appendChild(configPropertyElement); + + Node configPropertyName = document.createElement("config-property-name"); + configPropertyName.setTextContent(configProperty.getName()); + + Node configPropertyType = document.createElement("config-property-type"); + configPropertyType.setTextContent(configProperty.getType()); + + Node configPropertyValue = document.createElement("config-property-value"); + configPropertyValue.setTextContent(configProperty.getValue()); + + configPropertyElement.appendChild(configPropertyName); + configPropertyElement.appendChild(configPropertyType); + configPropertyElement.appendChild(configPropertyValue); + } + + String newXml = XmlUtils.prettyXml(document.getFirstChild()); + config.setCacheXmlContent(newXml); + + getSharedConfiguration().getConfigurationRegion().put("cluster", config); + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java new file mode 100644 index 0000000..1f8219c --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java @@ -0,0 +1,58 @@ +/* + * 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.geode.management.internal.cli.converters; + +import java.io.IOException; +import java.util.List; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.shell.core.Completion; +import org.springframework.shell.core.Converter; +import org.springframework.shell.core.MethodTarget; + +import org.apache.geode.internal.datasource.ConfigProperty; + +/*** + * Added converter to enable auto-completion for index-type + * + */ +public class ConfigPropertyConverter implements Converter<ConfigProperty> { + + private static ObjectMapper mapper = new ObjectMapper(); + static { + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + } + + @Override + public boolean supports(Class<?> type, String optionContext) { + return ConfigProperty.class.isAssignableFrom(type); + } + + @Override + public ConfigProperty convertFromText(String value, Class<?> targetType, String optionContext) { + try { + return mapper.readValue(value, ConfigProperty.class); + } catch (IOException e) { + throw new IllegalArgumentException("invalid json: " + value); + } + } + + @Override + public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, + String existingData, String optionContext, MethodTarget target) { + return false; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java new file mode 100644 index 0000000..133acf7 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java @@ -0,0 +1,38 @@ +/* + * 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.geode.management.internal.cli.functions; + +import org.apache.geode.cache.execute.FunctionContext; +import org.apache.geode.cache.execute.ResultSender; +import org.apache.geode.internal.cache.execute.InternalFunction; +import org.apache.geode.internal.jndi.JNDIInvoker; +import org.apache.geode.management.internal.cli.i18n.CliStrings; + +public class CreateJndiBindingFunction implements InternalFunction<JndiBindingConfiguration> { + + static final String RESULT_MESSAGE = + "Initiated jndi binding \"{0}\" on \"{1}\". See server logs to verify."; + + @Override + public void execute(FunctionContext<JndiBindingConfiguration> context) { + ResultSender<Object> resultSender = context.getResultSender(); + JndiBindingConfiguration configuration = context.getArguments(); + JNDIInvoker.mapDatasource(configuration.getParamsAsMap(), + configuration.getDatasourceConfigurations()); + + resultSender.lastResult(new CliFunctionResult(context.getMemberName(), true, + CliStrings.format(RESULT_MESSAGE, configuration.getJndiName(), context.getMemberName()))); + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.java new file mode 100644 index 0000000..316ca74 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.java @@ -0,0 +1,217 @@ +/* + * 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.geode.management.internal.cli.functions; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.geode.internal.datasource.ConfigProperty; + +public class JndiBindingConfiguration implements Serializable { + + public enum DATASOURCE_TYPE { + MANAGED("ManagedDataSource"), + SIMPLE("SimpleDataSource"), + POOLED("PooledDataSource"), + XAPOOLED("XAPooledDataSource"); + + private final String type; + + DATASOURCE_TYPE(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } + + public String getName() { + return name(); + } + } + + private Integer blockingTimeout; + private String connectionPoolDatasource; + private String connectionUrl; + private Integer idleTimeout; + private Integer initPoolSize; + private String jdbcDriver; + private String jndiName; + private Integer loginTimeout; + private String managedConnFactory; + private Integer maxPoolSize; + private String password; + private String transactionType; + private DATASOURCE_TYPE type; + private String username; + private String xaDatasource; + + private List<ConfigProperty> datasourceConfigurations; + + public JndiBindingConfiguration() { + datasourceConfigurations = new ArrayList<>(); + } + + public Integer getBlockingTimeout() { + return blockingTimeout; + } + + public void setBlockingTimeout(Integer blockingTimeout) { + this.blockingTimeout = blockingTimeout; + } + + public String getConnectionPoolDatasource() { + return connectionPoolDatasource; + } + + public void setConnectionPoolDatasource(String connectionPoolDatasource) { + this.connectionPoolDatasource = connectionPoolDatasource; + } + + public String getConnectionUrl() { + return connectionUrl; + } + + public void setConnectionUrl(String connectionUrl) { + this.connectionUrl = connectionUrl; + } + + public Integer getIdleTimeout() { + return idleTimeout; + } + + public void setIdleTimeout(Integer idleTimeout) { + this.idleTimeout = idleTimeout; + } + + public Integer getInitPoolSize() { + return initPoolSize; + } + + public void setInitPoolSize(Integer initPoolSize) { + this.initPoolSize = initPoolSize; + } + + public String getJdbcDriver() { + return jdbcDriver; + } + + public void setJdbcDriver(String jdbcDriver) { + this.jdbcDriver = jdbcDriver; + } + + public String getJndiName() { + return jndiName; + } + + public void setJndiName(String jndiName) { + this.jndiName = jndiName; + } + + public Integer getLoginTimeout() { + return loginTimeout; + } + + public void setLoginTimeout(Integer loginTimeout) { + this.loginTimeout = loginTimeout; + } + + public String getManagedConnFactory() { + return managedConnFactory; + } + + public void setManagedConnFactory(String managedConnFactory) { + this.managedConnFactory = managedConnFactory; + } + + public Integer getMaxPoolSize() { + return maxPoolSize; + } + + public void setMaxPoolSize(Integer maxPoolSize) { + this.maxPoolSize = maxPoolSize; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getTransactionType() { + return transactionType; + } + + public void setTransactionType(String transactionType) { + this.transactionType = transactionType; + } + + public DATASOURCE_TYPE getType() { + return type; + } + + public void setType(DATASOURCE_TYPE type) { + this.type = type; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getXaDatasource() { + return xaDatasource; + } + + public void setXaDatasource(String xaDatasource) { + this.xaDatasource = xaDatasource; + } + + public List<ConfigProperty> getDatasourceConfigurations() { + return datasourceConfigurations; + } + + public void setDatasourceConfigurations(List<ConfigProperty> dsConfigurations) { + this.datasourceConfigurations = dsConfigurations; + } + + public Map getParamsAsMap() { + Map params = new HashMap(); + params.put("blocking-timeout-seconds", getBlockingTimeout()); + params.put("conn-pooled-datasource-class", getConnectionPoolDatasource()); + params.put("connection-url", getConnectionUrl()); + params.put("idle-timeout-seconds", getIdleTimeout()); + params.put("init-pool-size", getInitPoolSize()); + params.put("jdbc-driver-class", getJdbcDriver()); + params.put("jndi-name", getJndiName()); + params.put("login-timeout-seconds", getLoginTimeout()); + params.put("managed-conn-factory-class", getManagedConnFactory()); + params.put("max-pool-size", getMaxPoolSize()); + params.put("password", getPassword()); + params.put("transaction-type", getTransactionType()); + params.put("type", getType().getType()); + params.put("user-name", getUsername()); + params.put("xa-datasource-class", getXaDatasource()); + return params; + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java index f0ff79c..132b8a3 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java @@ -18,8 +18,9 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.HashSet; import java.util.Properties; import java.util.Set; @@ -34,7 +35,7 @@ import org.xml.sax.SAXException; import org.apache.geode.DataSerializable; import org.apache.geode.DataSerializer; -import org.apache.geode.internal.util.CollectionUtils; +import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator; import org.apache.geode.management.internal.configuration.utils.XmlUtils; /** @@ -72,6 +73,14 @@ public class Configuration implements DataSerializable { this.propertiesFileName = configName + ".properties"; this.gemfireProperties = new Properties(); this.jarNames = new HashSet<String>(); + this.cacheXmlContent = generateInitialXmlContent(); + } + + private String generateInitialXmlContent() { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + CacheXmlGenerator.generateDefault(pw); + return sw.toString(); } public String getCacheXmlContent() { diff --git a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt index 0b4bbc3..8e148e1 100644 --- a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt +++ b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt @@ -516,6 +516,7 @@ org/apache/geode/management/internal/cli/functions/CreateAsyncEventQueueFunction org/apache/geode/management/internal/cli/functions/CreateDefinedIndexesFunction,true,1 org/apache/geode/management/internal/cli/functions/CreateDiskStoreFunction,true,1 org/apache/geode/management/internal/cli/functions/CreateIndexFunction,true,1 +org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction,false org/apache/geode/management/internal/cli/functions/DataCommandFunction,true,1,optimizeForWrite:boolean org/apache/geode/management/internal/cli/functions/DeployFunction,true,1 org/apache/geode/management/internal/cli/functions/DescribeDiskStoreFunction,false @@ -544,6 +545,8 @@ org/apache/geode/management/internal/cli/functions/GetRegionsFunction,true,1 org/apache/geode/management/internal/cli/functions/GetStackTracesFunction,true,1 org/apache/geode/management/internal/cli/functions/GetSubscriptionQueueSizeFunction,true,1 org/apache/geode/management/internal/cli/functions/ImportDataFunction,true,1 +org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration,false,blockingTimeout:java/lang/Integer,connectionPoolDatasource:java/lang/String,connectionUrl:java/lang/String,datasourceConfigurations:java/util/List,idleTimeout:java/lang/Integer,initPoolSize:java/lang/Integer,jdbcDriver:java/lang/String,jndiName:java/lang/String,loginTimeout:java/lang/Integer,managedConnFactory:java/lang/String,maxPoolSize:java/lang/Integer,password:java/lang/String,transactionType:java/lang [...] +org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration$DATASOURCE_TYPE,false,type:java/lang/String org/apache/geode/management/internal/cli/functions/ListAsyncEventQueuesFunction,true,1 org/apache/geode/management/internal/cli/functions/ListDeployedFunction,true,1 org/apache/geode/management/internal/cli/functions/ListDiskStoresFunction,false diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java index 1bf0ff9..0075e90 100644 --- a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java @@ -112,7 +112,8 @@ public class CreateAsyncEventQueueCommandDUnitTest { locator.invoke(() -> { ClusterConfigurationService service = ClusterStartupRule.getLocator().getSharedConfiguration(); - assertThat(service.getConfiguration("cluster").getCacheXmlContent()).isNull(); + assertThat(service.getConfiguration("cluster").getCacheXmlContent()) + .doesNotContain("async-event-queue"); }); gfsh.executeAndAssertThat(VALID_COMMAND + " --id=queue").statusIsSuccess() diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java index c43142a..862e745 100644 --- a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java @@ -205,7 +205,10 @@ public class CreateDefinedIndexesCommandDUnitTest { ((InternalLocator) Locator.getLocator()).getSharedConfiguration(); assertThat(sharedConfig.getConfiguration("group1").getCacheXmlContent()).contains(index2Name, index1Name); - assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent()).isNullOrEmpty(); + assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent()) + .doesNotContain(index2Name); + assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent()) + .doesNotContain(index1Name); }); } } diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java new file mode 100644 index 0000000..76d293e --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java @@ -0,0 +1,106 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +import java.util.stream.Stream; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import org.apache.geode.distributed.internal.ClusterConfigurationService; +import org.apache.geode.internal.jndi.JNDIInvoker; +import org.apache.geode.management.internal.configuration.domain.Configuration; +import org.apache.geode.management.internal.configuration.utils.XmlUtils; +import org.apache.geode.test.dunit.rules.ClusterStartupRule; +import org.apache.geode.test.dunit.rules.MemberVM; +import org.apache.geode.test.junit.categories.DistributedTest; +import org.apache.geode.test.junit.rules.GfshCommandRule; +import org.apache.geode.test.junit.rules.VMProvider; + +@Category(DistributedTest.class) +public class CreateJndiBindingCommandDUnitTest { + + private static MemberVM locator, server1, server2; + + @ClassRule + public static ClusterStartupRule cluster = new ClusterStartupRule(); + + @ClassRule + public static GfshCommandRule gfsh = new GfshCommandRule(); + + + @BeforeClass + public static void before() throws Exception { + locator = cluster.startLocatorVM(0); + server1 = cluster.startServerVM(1, "group1", locator.getPort()); + server2 = cluster.startServerVM(2, "group1", locator.getPort()); + + gfsh.connectAndVerify(locator); + } + + @Test + public void testCreateJndiBinding() throws Exception { + // assert that is no datasource + VMProvider.invokeInEveryMember( + () -> assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(0)); + + // create the binding + gfsh.executeAndAssertThat( + "create jndi-binding --name=jndi1 --type=SIMPLE --jdbc-driver-class=org.apache.derby.jdbc.EmbeddedDriver --connection-url=\"jdbc:derby:newDB;create=true\"") + .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server-1", "server-2"); + + // verify cluster config is updated + locator.invoke(() -> { + ClusterConfigurationService ccService = + ClusterStartupRule.getLocator().getSharedConfiguration(); + Configuration configuration = ccService.getConfiguration("cluster"); + Document document = XmlUtils.createDocumentFromXml(configuration.getCacheXmlContent()); + NodeList jndiBindings = document.getElementsByTagName("jndi-binding"); + + assertThat(jndiBindings.getLength()).isGreaterThan(0); + + boolean found = false; + for (int i = 0; i < jndiBindings.getLength(); i++) { + Element eachBinding = (Element) jndiBindings.item(i); + if (eachBinding.getAttribute("jndi-name").equals("jndi1")) { + found = true; + break; + } + } + assertThat(found).isTrue(); + }); + + // verify datasource exists + VMProvider.invokeInEveryMember( + () -> assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1)); + + // bounce server1 + server1.stopVM(false); + server1 = cluster.startServerVM(1, locator.getPort()); + + // verify it has recreated the datasource from cluster config + server1.invoke(() -> { + assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1); + }); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java new file mode 100644 index 0000000..53062c6 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java @@ -0,0 +1,408 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.ArgumentCaptor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import org.apache.geode.cache.Region; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.internal.ClusterConfigurationService; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.internal.datasource.ConfigProperty; +import org.apache.geode.management.internal.cli.GfshParseResult; +import org.apache.geode.management.internal.cli.functions.CliFunctionResult; +import org.apache.geode.management.internal.cli.functions.CreateJndiBindingFunction; +import org.apache.geode.management.internal.cli.functions.JndiBindingConfiguration; +import org.apache.geode.management.internal.configuration.domain.Configuration; +import org.apache.geode.management.internal.configuration.utils.XmlUtils; +import org.apache.geode.test.junit.categories.UnitTest; +import org.apache.geode.test.junit.rules.GfshParserRule; + +@Category(UnitTest.class) +public class CreateJndiBindingCommandTest { + + @ClassRule + public static GfshParserRule gfsh = new GfshParserRule(); + + private CreateJndiBindingCommand command; + private InternalCache cache; + + private static String COMMAND = "create jndi-binding "; + + @Before + public void setUp() throws Exception { + cache = mock(InternalCache.class); + command = spy(CreateJndiBindingCommand.class); + doReturn(cache).when(command).getCache(); + } + + @Test + public void missingMandatory() { + gfsh.executeAndAssertThat(command, COMMAND).statusIsError().containsOutput("Invalid command"); + } + + @Test + public void wrongType() { + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=NOTSOSIMPLE --name=name --jdbc-driver-class=driver --connection-url=url ") + .statusIsError().containsOutput("Invalid command"); + } + + @Test + public void configPropertyIsProperlyParsed() { + GfshParseResult result = gfsh.parse(COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url " + + "--datasource-config-properties={'name':'name1','type':'type1','value':'value1'},{'name':'name2','type':'type2','value':'value2'}"); + + ConfigProperty[] configProperties = + (ConfigProperty[]) result.getParamValue("datasource-config-properties"); + assertThat(configProperties).hasSize(2); + assertThat(configProperties[0].getValue()).isEqualTo("value1"); + assertThat(configProperties[1].getValue()).isEqualTo("value2"); + } + + @Test + public void returnsErrorIfBindingAlreadyExistsAndIfUnspecified() + throws ParserConfigurationException, SAXException, IOException { + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doReturn(true).when(command).isBindingAlreadyExists(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url") + .statusIsError().containsOutput("already exists."); + } + + @Test + public void skipsIfBindingAlreadyExistsAndIfSpecified() + throws ParserConfigurationException, SAXException, IOException { + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doReturn(true).when(command).isBindingAlreadyExists(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url --if-not-exists") + .statusIsSuccess().containsOutput("Skipping"); + } + + @Test + public void skipsIfBindingAlreadyExistsAndIfSpecifiedTrue() + throws ParserConfigurationException, SAXException, IOException { + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doReturn(true).when(command).isBindingAlreadyExists(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url --if-not-exists=true") + .statusIsSuccess().containsOutput("Skipping"); + } + + @Test + public void returnsErrorIfBindingAlreadyExistsAndIfSpecifiedFalse() + throws ParserConfigurationException, SAXException, IOException { + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doReturn(true).when(command).isBindingAlreadyExists(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url --if-not-exists=false") + .statusIsError().containsOutput("already exists."); + } + + @Test + public void ifBindingAlreadyExistsShouldReturnTrueIfABindingExistsWithJndiName() + throws ParserConfigurationException, SAXException, IOException, TransformerException { + + Configuration clusterConfig = new Configuration("cluster"); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + Region configRegion = mock(Region.class); + + doReturn(configRegion).when(clusterConfigService).getConfigurationRegion(); + doReturn(null).when(configRegion).put(any(), any()); + doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster"); + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + + // update cluster config + JndiBindingConfiguration jndi = new JndiBindingConfiguration(); + jndi.setJndiName("jndi1"); + jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE); + command.updateXml(jndi); + + assertThat(command.isBindingAlreadyExists("jndi1")).isTrue(); + } + + @Test + public void ifBindingAlreadyExistsShouldReturnFalseIfNoBindingsTagExists() + throws ParserConfigurationException, SAXException, IOException { + + Configuration clusterConfig = new Configuration("cluster"); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster"); + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + + assertThat(command.isBindingAlreadyExists("somename")).isFalse(); + } + + @Test + public void ifBindingAlreadyExistsShouldReturnFalseIfNoBindingExists() + throws ParserConfigurationException, SAXException, IOException, TransformerException { + + Configuration clusterConfig = new Configuration("cluster"); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + Region configRegion = mock(Region.class); + + doReturn(configRegion).when(clusterConfigService).getConfigurationRegion(); + doReturn(null).when(configRegion).put(any(), any()); + doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster"); + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + + // update cluster config + JndiBindingConfiguration jndi = new JndiBindingConfiguration(); + jndi.setJndiName("jndi1"); + jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE); + command.updateXml(jndi); + + assertThat(command.isBindingAlreadyExists("jndi2")).isFalse(); + } + + @Test + public void updateXmlShouldClusterConfigurationWithJndiConfiguration() + throws IOException, ParserConfigurationException, SAXException, TransformerException { + Configuration clusterConfig = new Configuration("cluster"); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + Region configRegion = mock(Region.class); + + doReturn(configRegion).when(clusterConfigService).getConfigurationRegion(); + doReturn(null).when(configRegion).put(any(), any()); + doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster"); + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + + JndiBindingConfiguration jndi = new JndiBindingConfiguration(); + jndi.setBlockingTimeout(60); + jndi.setConnectionUrl("URL"); + jndi.setConnectionPoolDatasource("org.datasource"); + jndi.setMaxPoolSize(10); + jndi.setManagedConnFactory("connFactory"); + jndi.setLoginTimeout(100); + jndi.setJndiName("jndi1"); + jndi.setJdbcDriver("driver"); + jndi.setUsername("user1"); + jndi.setIdleTimeout(50); + jndi.setInitPoolSize(5); + jndi.setPassword("p@ssw0rd"); + jndi.setTransactionType("txntype"); + jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE); + jndi.setXaDatasource("xaDS"); + ConfigProperty prop = new ConfigProperty("somename", "somevalue", "sometype"); + jndi.setDatasourceConfigurations(Arrays.asList(new ConfigProperty[] {prop})); + command.updateXml(jndi); + + Document document = XmlUtils.createDocumentFromXml(clusterConfig.getCacheXmlContent()); + assertThat(document).isNotNull(); + assertThat(document.getElementsByTagName("jndi-bindings").item(0)).isNotNull(); + Element jndiElement = (Element) document.getElementsByTagName("jndi-binding").item(0); + assertThat(jndiElement).isNotNull(); + assertThat(jndiElement.getParentNode().getNodeName()).isEqualTo("jndi-bindings"); + assertThat(jndiElement.getAttribute("blocking-timeout-seconds")).isEqualTo("60"); + assertThat(jndiElement.getAttribute("conn-pooled-datasource-class")) + .isEqualTo("org.datasource"); + assertThat(jndiElement.getAttribute("connection-url")).isEqualTo("URL"); + assertThat(jndiElement.getAttribute("idle-timeout-seconds")).isEqualTo("50"); + assertThat(jndiElement.getAttribute("init-pool-size")).isEqualTo("5"); + assertThat(jndiElement.getAttribute("jdbc-driver-class")).isEqualTo("driver"); + assertThat(jndiElement.getAttribute("jndi-name")).isEqualTo("jndi1"); + assertThat(jndiElement.getAttribute("login-timeout-seconds")).isEqualTo("100"); + assertThat(jndiElement.getAttribute("managed-conn-factory-class")).isEqualTo("connFactory"); + assertThat(jndiElement.getAttribute("max-pool-size")).isEqualTo("10"); + assertThat(jndiElement.getAttribute("password")).isEqualTo("p@ssw0rd"); + assertThat(jndiElement.getAttribute("transaction-type")).isEqualTo("txntype"); + assertThat(jndiElement.getAttribute("type")).isEqualTo("SimpleDataSource"); + assertThat(jndiElement.getAttribute("user-name")).isEqualTo("user1"); + assertThat(jndiElement.getAttribute("xa-datasource-class")).isEqualTo("xaDS"); + + Node configProperty = document.getElementsByTagName("config-property").item(0); + assertThat(configProperty.getParentNode().getNodeName()).isEqualTo("jndi-binding"); + + Node nameProperty = document.getElementsByTagName("config-property-name").item(0); + assertThat(nameProperty).isNotNull(); + assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property"); + assertThat(nameProperty.getTextContent()).isEqualTo("somename"); + Node typeProperty = document.getElementsByTagName("config-property-type").item(0); + assertThat(typeProperty).isNotNull(); + assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property"); + assertThat(typeProperty.getTextContent()).isEqualTo("sometype"); + Node valueProperty = document.getElementsByTagName("config-property-value").item(0); + assertThat(valueProperty).isNotNull(); + assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property"); + assertThat(valueProperty.getTextContent()).isEqualTo("somevalue"); + + verify(configRegion, times(1)).put("cluster", clusterConfig); + } + + @Test + public void whenNoMembersFoundAndNoClusterConfigServiceRunningThenError() + throws IOException, ParserConfigurationException, SAXException, TransformerException { + + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(false).when(command).isBindingAlreadyExists(any()); + doNothing().when(command).updateXml(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url") + // .statusIsSuccess().containsOutput("Cluster Configuration Service is not available") + .statusIsSuccess().containsOutput("No members found").hasFailToPersistError(); + } + + @Test + public void whenNoMembersFoundAndClusterConfigRunningThenUpdateClusterConfig() + throws IOException, ParserConfigurationException, SAXException, TransformerException { + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(Collections.emptySet()).when(command).findMembers(any(), any()); + doReturn(false).when(command).isBindingAlreadyExists(any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doNothing().when(command).updateXml(any()); + + gfsh.executeAndAssertThat(command, + COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url") + .statusIsSuccess(); + } + + @Test + public void whenMembersFoundAndNoClusterConfigRunningThenOnlyInvokeFunction() + throws IOException, ParserConfigurationException, SAXException, TransformerException { + Set<DistributedMember> members = new HashSet<>(); + members.add(mock(DistributedMember.class)); + + CliFunctionResult result = new CliFunctionResult("server1", true, + "Tried creating jndi binding \"name\" on \"server1\""); + List<CliFunctionResult> results = new ArrayList<>(); + results.add(result); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(members).when(command).findMembers(any(), any()); + doReturn(false).when(command).isBindingAlreadyExists(any()); + doReturn(null).when(command).getSharedConfiguration(); + doReturn(results).when(command).executeAndGetFunctionResult(any(), any(), any()); + + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url --datasource-config-properties={'name':'name1','type':'type1','value':'value1'}") + .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server1") + .tableHasColumnOnlyWithValues("Status", + "Tried creating jndi binding \"name\" on \"server1\""); + + verify(command, times(0)).updateXml(any()); + + ArgumentCaptor<CreateJndiBindingFunction> function = + ArgumentCaptor.forClass(CreateJndiBindingFunction.class); + ArgumentCaptor<JndiBindingConfiguration> jndiConfig = + ArgumentCaptor.forClass(JndiBindingConfiguration.class); + ArgumentCaptor<Set<DistributedMember>> targetMembers = ArgumentCaptor.forClass(Set.class); + verify(command, times(1)).executeAndGetFunctionResult(function.capture(), jndiConfig.capture(), + targetMembers.capture()); + + assertThat(function.getValue()).isInstanceOf(CreateJndiBindingFunction.class); + assertThat(jndiConfig.getValue()).isNotNull(); + assertThat(jndiConfig.getValue().getJndiName()).isEqualTo("name"); + assertThat(jndiConfig.getValue().getDatasourceConfigurations().get(0).getName()) + .isEqualTo("name1"); + assertThat(targetMembers.getValue()).isEqualTo(members); + } + + @Test + public void whenMembersFoundAndClusterConfigRunningThenUpdateClusterConfigAndInvokeFunction() + throws IOException, ParserConfigurationException, SAXException, TransformerException { + Set<DistributedMember> members = new HashSet<>(); + members.add(mock(DistributedMember.class)); + + CliFunctionResult result = new CliFunctionResult("server1", true, + "Tried creating jndi binding \"name\" on \"server1\""); + List<CliFunctionResult> results = new ArrayList<>(); + results.add(result); + ClusterConfigurationService clusterConfigService = mock(ClusterConfigurationService.class); + + doReturn(members).when(command).findMembers(any(), any()); + doReturn(false).when(command).isBindingAlreadyExists(any()); + doReturn(clusterConfigService).when(command).getSharedConfiguration(); + doNothing().when(command).updateXml(any()); + doReturn(results).when(command).executeAndGetFunctionResult(any(), any(), any()); + + gfsh.executeAndAssertThat(command, + COMMAND + + " --type=SIMPLE --name=name --jdbc-driver-class=driver --connection-url=url --datasource-config-properties={'name':'name1','type':'type1','value':'value1'}") + .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server1") + .tableHasColumnOnlyWithValues("Status", + "Tried creating jndi binding \"name\" on \"server1\""); + + verify(command, times(1)).updateXml(any()); + + ArgumentCaptor<CreateJndiBindingFunction> function = + ArgumentCaptor.forClass(CreateJndiBindingFunction.class); + ArgumentCaptor<JndiBindingConfiguration> jndiConfig = + ArgumentCaptor.forClass(JndiBindingConfiguration.class); + ArgumentCaptor<Set<DistributedMember>> targetMembers = ArgumentCaptor.forClass(Set.class); + verify(command, times(1)).executeAndGetFunctionResult(function.capture(), jndiConfig.capture(), + targetMembers.capture()); + + assertThat(function.getValue()).isInstanceOf(CreateJndiBindingFunction.class); + assertThat(jndiConfig.getValue()).isNotNull(); + assertThat(jndiConfig.getValue().getJndiName()).isEqualTo("name"); + assertThat(jndiConfig.getValue().getDatasourceConfigurations().get(0).getName()) + .isEqualTo("name1"); + assertThat(targetMembers.getValue()).isEqualTo(members); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.java new file mode 100644 index 0000000..a32d3a6 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.java @@ -0,0 +1,75 @@ +/* + * 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.geode.management.internal.cli.converters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.internal.datasource.ConfigProperty; +import org.apache.geode.test.junit.categories.UnitTest; + + +@Category(UnitTest.class) +public class ConfigPropertyConverterTest { + + private ConfigPropertyConverter converter; + + @Before + public void setUp() throws Exception { + converter = new ConfigPropertyConverter(); + } + + @Test + public void validJson() { + ConfigProperty configProperty = + converter.convertFromText("{'name':'name','type':'type','value':'value'}", null, null); + assertThat(configProperty.getName()).isEqualTo("name"); + assertThat(configProperty.getType()).isEqualTo("type"); + assertThat(configProperty.getValue()).isEqualTo("value"); + } + + @Test + public void invalidJson() { + assertThatThrownBy(() -> converter.convertFromText( + "{'name':'name','type':'type','value':'value','another':'another'}", null, null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void invalidWhenEmptyString() { + assertThatThrownBy(() -> converter.convertFromText("", null, null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void validWhenTypeMissing() { + ConfigProperty configProperty = + converter.convertFromText("{'name':'name','value':'value'}", null, null); + assertThat(configProperty.getName()).isEqualTo("name"); + assertThat(configProperty.getType()).isNull(); + assertThat(configProperty.getValue()).isEqualTo("value"); + } + + @Test + public void inValidWhenTypo() { + assertThatThrownBy(() -> converter + .convertFromText("{'name':'name','typo':'type','value':'value'}", null, null)) + .isInstanceOf(IllegalArgumentException.class);; + } +} -- To stop receiving notification emails like this one, please contact sai_boorlaga...@apache.org.