blob: d316ecc1264f1d5022663bd36fe437146e5835df [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* 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.distributed.ConfigurationProperties.SSL_KEYSTORE;
import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_PASSWORD;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doCallRealMethod;
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 static org.mockito.Mockito.when;
import java.io.File;
import java.util.Properties;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.result.CommandResult;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.cli.shell.Gfsh;
import org.apache.geode.management.internal.cli.shell.OperationInvoker;
import org.apache.geode.management.internal.i18n.CliStrings;
import org.apache.geode.test.junit.rules.GfshParserRule;
@SuppressWarnings("deprecation")
public class ConnectCommandTest {
@ClassRule
public static GfshParserRule gfshParserRule = new GfshParserRule();
private ConnectCommand connectCommand;
private Gfsh gfsh;
private CommandResult result;
private ResultModel resultModel;
private OperationInvoker operationInvoker;
private Properties properties;
private ArgumentCaptor<File> fileCaptor;
@Before
public void before() {
properties = new Properties();
gfsh = mock(Gfsh.class);
operationInvoker = mock(OperationInvoker.class);
when(gfsh.getOperationInvoker()).thenReturn(operationInvoker);
// using spy instead of mock because we want to call the real method when we do connect
connectCommand = spy(ConnectCommand.class);
when(connectCommand.getGfsh()).thenReturn(gfsh);
doReturn(properties).when(connectCommand).loadProperties(any());
result = mock(CommandResult.class);
resultModel = mock(ResultModel.class);
when(connectCommand.httpConnect(any(), any(), anyBoolean())).thenReturn(resultModel);
when(connectCommand.jmxConnect(any(), anyBoolean(), any(), any(), anyBoolean()))
.thenReturn(resultModel);
fileCaptor = ArgumentCaptor.forClass(File.class);
}
@Test
public void whenGfshIsAlreadyConnected() {
when(gfsh.isConnectedAndReady()).thenReturn(true);
gfshParserRule.executeAndAssertThat(connectCommand, "connect")
.containsOutput("Already connected to");
}
@Test
public void whenGfshIsNotConnected() {
when(gfsh.isConnectedAndReady()).thenReturn(false);
when(resultModel.getStatus()).thenReturn(Result.Status.OK);
gfshParserRule.executeAndAssertThat(connectCommand, "connect")
.statusIsSuccess();
}
@Test
public void tokenIsGiven() {
when(resultModel.getStatus()).thenReturn(Result.Status.OK);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token=FOO_BAR")
.statusIsSuccess();
}
@Test
public void tokenIsGivenWithUserName() {
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token=FOO_BAR --user")
.containsOutput("--token cannot be combined with --user or --password")
.statusIsError();
}
@Test
public void tokenIsGivenWithPassword() {
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token=FOO_BAR --password")
.containsOutput("--token cannot be combined with --user or --password")
.statusIsError();
}
@Test
public void tokenIsGivenWithUserNameAndPassword() {
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token=FOO_BAR --user --password")
.containsOutput("--token cannot be combined with --user or --password")
.statusIsError();
}
@Test
public void tokenIsGivenWithNoValue() {
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token")
.containsOutput("--token requires a value, for example --token=foo")
.statusIsError();
}
@Test
public void givenTokenIsSetInProperties() {
doReturn(properties).when(connectCommand).resolveSslProperties(any(), anyBoolean(), any(),
any());
gfshParserRule.executeAndAssertThat(connectCommand, "connect --token=FOO_BAR");
assertThat(properties.getProperty("security-token")).isEqualTo("FOO_BAR");
}
@Test
public void promptForPasswordIfUsernameIsGiven() {
doReturn(properties).when(connectCommand).resolveSslProperties(any(), anyBoolean(), any(),
any());
result = gfshParserRule.executeCommandWithInstance(connectCommand, "connect --user=user");
verify(gfsh).readPassword(CliStrings.CONNECT__PASSWORD + ": ");
assertThat(properties.getProperty("security-username")).isEqualTo("user");
assertThat(properties.getProperty("security-password")).isEqualTo("");
}
@Test
public void notPromptForPasswordIfUsernameIsGiven() {
doReturn(properties).when(connectCommand).resolveSslProperties(any(), anyBoolean(), any(),
any());
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --user=user --password=pass");
verify(gfsh, times(0)).readPassword(CliStrings.CONNECT__PASSWORD + ": ");
assertThat(properties.getProperty("security-username")).isEqualTo("user");
assertThat(properties.getProperty("security-password")).isEqualTo("pass");
}
@Test
public void notPromptForPasswordIfuserNameIsGivenInFile() {
// username specified in property file won't prompt for password
properties.setProperty("security-username", "user");
doReturn(properties).when(connectCommand).loadProperties(any(File.class));
result = gfshParserRule.executeCommandWithInstance(connectCommand, "connect");
verify(gfsh, times(0)).readPassword(CliStrings.CONNECT__PASSWORD + ": ");
assertThat(properties).doesNotContainKey("security-password");
}
@Test
public void plainConnectNotLoadFileNotPrompt() {
result = gfshParserRule.executeCommandWithInstance(connectCommand, "connect");
// will not try to load from any file
verify(connectCommand).loadProperties(null, null);
// will not try to prompt
verify(gfsh, times(0)).readText(any());
verify(gfsh, times(0)).readPassword(any());
}
@Test
public void connectUseSsl() {
result = gfshParserRule.executeCommandWithInstance(connectCommand, "connect --use-ssl");
// will not try to load from any file
verify(connectCommand).loadProperties(null, null);
// gfsh will prompt for the all the ssl properties
verify(gfsh).readText("key-store: ");
verify(gfsh).readPassword("key-store-password: ");
verify(gfsh).readText("key-store-type(default: JKS): ");
verify(gfsh).readText("trust-store: ");
verify(gfsh).readPassword("trust-store-password: ");
verify(gfsh).readText("trust-store-type(default: JKS): ");
verify(gfsh).readText("ssl-ciphers(default: any): ");
verify(gfsh).readText("ssl-protocols(default: any): ");
// verify the resulting properties has correct values
assertThat(properties).hasSize(9);
assertThat(properties.getProperty("ssl-keystore")).isEqualTo("");
assertThat(properties.getProperty("ssl-keystore-password")).isEqualTo("");
assertThat(properties.getProperty("ssl-keystore-type")).isEqualTo("JKS");
assertThat(properties.getProperty("ssl-truststore")).isEqualTo("");
assertThat(properties.getProperty("ssl-truststore-password")).isEqualTo("");
assertThat(properties.getProperty("ssl-truststore-type")).isEqualTo("JKS");
assertThat(properties.getProperty("ssl-ciphers")).isEqualTo("any");
assertThat(properties.getProperty("ssl-protocols")).isEqualTo("any");
assertThat(properties.getProperty("ssl-enabled-components")).isEqualTo("all");
}
@Test
public void securityFileContainsSSLPropsAndNoUseSSL() {
properties.setProperty(SSL_KEYSTORE, "keystore");
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --security-properties-file=test");
// will try to load from this file
verify(connectCommand).loadProperties(any(), fileCaptor.capture());
assertThat(fileCaptor.getValue()).hasName("test");
// it will prompt for missing properties
verify(gfsh, times(6)).readText(any());
verify(gfsh, times(2)).readPassword(any());
}
@Test
public void securityFileContainsNoSSLPropsAndNoUseSSL() {
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --security-properties-file=test");
// will try to load from this file
verify(connectCommand).loadProperties(any(), fileCaptor.capture());
assertThat(fileCaptor.getValue()).hasName("test");
// it will prompt for missing properties
verify(gfsh, times(0)).readText(any());
verify(gfsh, times(0)).readPassword(any());
}
@Test
public void connectUseLegacySecurityPropertiesFile() {
properties.setProperty(
org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_SSL_KEYSTORE,
"jmx-keystore");
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --security-properties-file=test --key-store=keystore --key-store-password=password");
// wil try to load from this file
verify(connectCommand).loadProperties(fileCaptor.capture());
assertThat(fileCaptor.getValue()).hasName("test");
// it will not prompt for missing properties
verify(gfsh, times(0)).readText(any());
verify(gfsh, times(0)).readPassword(any());
// the command option will be ignored
assertThat(properties).hasSize(1);
assertThat(properties
.get(org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_SSL_KEYSTORE))
.isEqualTo("jmx-keystore");
}
@Test
public void connectUseSecurityPropertiesFile_promptForMissing() {
properties.setProperty(SSL_KEYSTORE, "keystore");
properties.setProperty(SSL_KEYSTORE_PASSWORD, "password");
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --security-properties-file=test");
// since nothing is loaded, will prompt for all missing values
verify(gfsh, times(6)).readText(any());
verify(gfsh, times(1)).readPassword(any());
}
@Test
public void connectUseSecurityPropertiesFileAndOption_promptForMissing() {
properties.setProperty(SSL_KEYSTORE, "keystore");
properties.setProperty(SSL_KEYSTORE_PASSWORD, "password");
result = gfshParserRule.executeCommandWithInstance(connectCommand,
"connect --security-properties-file=test --key-store=keystore2 --trust-store=truststore2");
// since nothing is loaded, will prompt for all missing values
verify(gfsh, times(5)).readText(any());
verify(gfsh, times(1)).readPassword(any());
assertThat(properties).hasSize(9);
assertThat(properties.getProperty("ssl-keystore")).isEqualTo("keystore2");
assertThat(properties.getProperty("ssl-keystore-password")).isEqualTo("password");
assertThat(properties.getProperty("ssl-keystore-type")).isEqualTo("JKS");
assertThat(properties.getProperty("ssl-truststore")).isEqualTo("truststore2");
assertThat(properties.getProperty("ssl-truststore-password")).isEqualTo("");
assertThat(properties.getProperty("ssl-truststore-type")).isEqualTo("JKS");
assertThat(properties.getProperty("ssl-ciphers")).isEqualTo("any");
assertThat(properties.getProperty("ssl-protocols")).isEqualTo("any");
assertThat(properties.getProperty("ssl-enabled-components")).isEqualTo("all");
}
@Test
public void containsLegacySSLConfigTest_ssl() {
properties.setProperty(SSL_KEYSTORE, "keystore");
assertThat(ConnectCommand.containsLegacySSLConfig(properties)).isFalse();
}
@Test
public void containsLegacySSLConfigTest_cluster() {
properties.setProperty(
org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_KEYSTORE,
"cluster-keystore");
assertThat(ConnectCommand.containsLegacySSLConfig(properties)).isTrue();
}
@Test
public void containsLegacySSLConfigTest_jmx() {
properties.setProperty(
org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_SSL_KEYSTORE,
"jmx-keystore");
assertThat(ConnectCommand.containsLegacySSLConfig(properties)).isTrue();
}
@Test
public void containsLegacySSLConfigTest_http() {
properties.setProperty(
org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_SSL_KEYSTORE,
"http-keystore");
assertThat(ConnectCommand.containsLegacySSLConfig(properties)).isTrue();
}
@Test
public void loadPropertiesWithNull() {
doCallRealMethod().when(connectCommand).loadProperties(any());
assertThat(connectCommand.loadProperties(null, null)).isEmpty();
}
@Test
public void isSslImpliedByOptions() {
assertThat(connectCommand.isSslImpliedBySslOptions((String) null)).isFalse();
assertThat(connectCommand.isSslImpliedBySslOptions((String[]) null)).isFalse();
assertThat(connectCommand.isSslImpliedBySslOptions(null, null, null)).isFalse();
assertThat(connectCommand.isSslImpliedBySslOptions(null, "test")).isTrue();
}
@Test
public void resolveSslProperties() {
// assume properties loaded from either file has an ssl property
properties.setProperty(SSL_KEYSTORE, "keystore");
properties = connectCommand.resolveSslProperties(gfsh, false, null, null);
assertThat(properties).hasSize(9);
properties.clear();
properties.setProperty(SSL_KEYSTORE, "keystore");
properties =
connectCommand.resolveSslProperties(gfsh, false, null, null, "keystore2", "password");
assertThat(properties).hasSize(9);
assertThat(properties.getProperty(SSL_KEYSTORE)).isEqualTo("keystore2");
assertThat(properties.getProperty(SSL_KEYSTORE_PASSWORD)).isEqualTo("password");
}
@Test
public void connectToManagerWithOlderMajorVersionAllowed() {
when(gfsh.getVersion()).thenReturn("2.2");
when(operationInvoker.getRemoteVersion()).thenReturn("1.10");
when(gfsh.getGeodeSerializationVersion()).thenReturn("2.2");
when(operationInvoker.getRemoteGeodeSerializationVersion()).thenReturn("1.10");
when(operationInvoker.isConnected()).thenReturn(true);
ResultModel resultModel = new ResultModel();
when(connectCommand.jmxConnect(any(), anyBoolean(), any(), any(), anyBoolean()))
.thenReturn(resultModel);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsSuccess()
.doesNotContainOutput("Cannot use a 2.2 gfsh client to connect to a 1.10 cluster.");
}
@Test
public void connectToManagerWithNewerMajorVersionNotAllowed() {
when(gfsh.getVersion()).thenReturn("1.2");
when(operationInvoker.getRemoteVersion()).thenReturn("2.2");
when(gfsh.getGeodeSerializationVersion()).thenReturn("1.2");
when(operationInvoker.getRemoteGeodeSerializationVersion()).thenReturn("2.2");
when(operationInvoker.isConnected()).thenReturn(true);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsError()
.containsOutput("Cannot use a 1.2 gfsh client to connect to a 2.2 cluster.");
}
@Test
public void connectToManager1_10() {
when(operationInvoker.getRemoteVersion()).thenReturn("1.10");
when(gfsh.getGeodeSerializationVersion()).thenReturn("1.14");
when(operationInvoker.getRemoteGeodeSerializationVersion())
.thenThrow(new RuntimeException("serialization version not available"));
when(operationInvoker.isConnected()).thenReturn(true);
ResultModel resultModel = new ResultModel();
when(connectCommand.jmxConnect(any(), anyBoolean(), any(), any(), anyBoolean()))
.thenReturn(resultModel);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsSuccess()
.containsOutput("You are connected to a cluster of version: 1.10");
}
@Test
public void connectToOlderManagerWithNoRemoteVersion() {
when(gfsh.getVersion()).thenReturn("1.14");
when(gfsh.getGeodeSerializationVersion()).thenReturn("1.14");
when(operationInvoker.getRemoteVersion())
.thenThrow(new RuntimeException("release version not available"));
when(operationInvoker.isConnected()).thenReturn(true);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsError()
.containsOutput("Cannot use a 1.14 gfsh client to connect to this cluster.");
}
@Test
public void connectToManagerBefore1_10() {
when(gfsh.getVersion()).thenReturn("1.14");
when(gfsh.getGeodeSerializationVersion()).thenReturn("1.14");
when(operationInvoker.getRemoteVersion()).thenReturn("1.9");
when(operationInvoker.getRemoteGeodeSerializationVersion())
.thenThrow(new RuntimeException("serialization version not available"));
when(operationInvoker.isConnected()).thenReturn(true);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsError()
.containsOutput("Cannot use a 1.14 gfsh client to connect to a 1.9 cluster");
}
@Test
public void connectToManagerBySerializationVersion() {
when(gfsh.getVersion()).thenReturn("0.0.0");
when(gfsh.getGeodeSerializationVersion()).thenReturn("1.14.0");
when(operationInvoker.getRemoteVersion()).thenReturn("0.0.0");
when(operationInvoker.getRemoteGeodeSerializationVersion()).thenReturn("1.14.0");
when(operationInvoker.isConnected()).thenReturn(true);
ResultModel resultModel = new ResultModel();
when(connectCommand.jmxConnect(any(), anyBoolean(), any(), any(), anyBoolean()))
.thenReturn(resultModel);
gfshParserRule.executeAndAssertThat(connectCommand, "connect --locator=localhost:4040")
.statusIsSuccess()
.doesNotContainOutput("Cannot use a 0.0.0 gfsh client to connect to a 0.0.0 cluster");
}
@Test
public void isCompatibleWOneDotX() {
assertThat(ConnectCommand.shouldConnect("1", null, null)).isFalse();
assertThat(ConnectCommand.shouldConnect("1", "1.5.0", null)).isFalse();
assertThat(ConnectCommand.shouldConnect("1", "1.9.0", null)).isFalse();
assertThat(ConnectCommand.shouldConnect("1", "1.10.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "1.11.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "9.9.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "9.9.0", "9.9.0")).isFalse();
assertThat(ConnectCommand.shouldConnect("1", "1.12.0", "1.12.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "1.13.0", "1.13.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "1.14.0", "1.14.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("1", "2.0.0", "2.0.0")).isFalse();
assertThat(ConnectCommand.shouldConnect("1", "badstring", "badstring")).isFalse();
}
@Test
public void isCompatibleTwoDotX() {
assertThat(ConnectCommand.shouldConnect("1", null, null)).isFalse();
assertThat(ConnectCommand.shouldConnect("2", "1.5.0", null)).isFalse();
assertThat(ConnectCommand.shouldConnect("2", "1.9.0", null)).isFalse();
assertThat(ConnectCommand.shouldConnect("2", "1.10.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "1.11.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "9.9.0", null)).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "9.9.0", "9.9.0")).isFalse();
assertThat(ConnectCommand.shouldConnect("2", "1.12.0", "1.12.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "1.13.0", "1.13.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "1.14.0", "1.14.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "2.0.0", "2.0.0")).isTrue();
assertThat(ConnectCommand.shouldConnect("2", "badstring", "badstring")).isFalse();
}
}