http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestAgentProviderService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestAgentProviderService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestAgentProviderService.java new file mode 100644 index 0000000..7fceac7 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestAgentProviderService.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 + * + * 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.slider.server.appmaster.web.rest.publisher; + +import org.apache.hadoop.yarn.api.records.Container; +import org.apache.slider.common.tools.SliderFileSystem; +import org.apache.slider.providers.agent.AgentProviderService; +import org.apache.slider.server.appmaster.actions.QueueAccess; +import org.apache.slider.server.appmaster.state.StateAccessForProviders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class TestAgentProviderService extends AgentProviderService { + protected static final Logger log = + LoggerFactory.getLogger(TestAgentProviderService.class); + + public TestAgentProviderService() { + super(); + log.info("TestAgentProviderService created"); + } + + @Override + public void bind(StateAccessForProviders stateAccessor, + QueueAccess queueAccess, + List<Container> liveContainers) { + super.bind(stateAccessor, queueAccess, liveContainers); + Map<String,String> dummyProps = new HashMap<String, String>(); + dummyProps.put("prop1", "val1"); + dummyProps.put("prop2", "val2"); + log.info("publishing dummy-site.xml with values {}", dummyProps); + publishApplicationInstanceData("dummy-site", "dummy configuration", + dummyProps.entrySet()); + // publishing global config for testing purposes + publishApplicationInstanceData("global", "global configuration", + stateAccessor.getAppConfSnapshot() + .getGlobalOptions().entrySet()); + } + +}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestSliderProviderFactory.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestSliderProviderFactory.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestSliderProviderFactory.java new file mode 100644 index 0000000..f49e15a --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/web/rest/publisher/TestSliderProviderFactory.java @@ -0,0 +1,40 @@ +/* + * 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.slider.server.appmaster.web.rest.publisher; + +import org.apache.slider.providers.ProviderService; +import org.apache.slider.providers.agent.AgentProviderFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +public class TestSliderProviderFactory extends AgentProviderFactory{ + protected static final Logger log = + LoggerFactory.getLogger(TestSliderProviderFactory.class); + + public TestSliderProviderFactory() { + log.info("Created TestSliderProviderFactory"); + } + + @Override + public ProviderService createServerProvider() { + log.info("Creating TestAgentProviderService"); + return new TestAgentProviderService(); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/servicemonitor/TestPortProbe.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/servicemonitor/TestPortProbe.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/servicemonitor/TestPortProbe.java new file mode 100644 index 0000000..a93ec57 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/servicemonitor/TestPortProbe.java @@ -0,0 +1,37 @@ +/* + * 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.slider.server.servicemonitor; + +import org.junit.Assert; +import org.apache.hadoop.conf.Configuration; +import org.junit.Test; + +public class TestPortProbe extends Assert { + /** + * Assert that a port probe failed if the port is closed + * @throws Throwable + */ + @Test + public void testPortProbeFailsClosedPort() throws Throwable { + PortProbe probe = new PortProbe("127.0.0.1", 65500, 100, "", new Configuration()); + probe.init(); + ProbeStatus status = probe.ping(true); + assertFalse("Expected a failure but got successful result: " + status, + status.isSuccess()); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestCertificateManager.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestCertificateManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestCertificateManager.java new file mode 100644 index 0000000..7a4a586 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestCertificateManager.java @@ -0,0 +1,540 @@ +/* + * 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.slider.server.services.security; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.security.alias.JavaKeyStoreProvider; +import org.apache.slider.Slider; +import org.apache.slider.common.SliderKeys; +import org.apache.slider.common.SliderXmlConfKeys; +import org.apache.slider.core.conf.AggregateConf; +import org.apache.slider.core.conf.MapOperations; +import org.apache.slider.core.exceptions.SliderException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + +/** + * + */ +public class TestCertificateManager { + @Rule + public TemporaryFolder workDir = new TemporaryFolder(); + private File secDir; + private CertificateManager certMan; + + @Before + public void setup() throws Exception { + certMan = new CertificateManager(); + MapOperations compOperations = new MapOperations(); + secDir = new File(workDir.getRoot(), SliderKeys.SECURITY_DIR); + File keystoreFile = new File(secDir, SliderKeys.KEYSTORE_FILE_NAME); + compOperations.put(SliderXmlConfKeys.KEY_KEYSTORE_LOCATION, + keystoreFile.getAbsolutePath()); + certMan.initialize(compOperations, "cahost", null, null); + } + + @Test + public void testServerCertificateGenerated() throws Exception { + File serverCrt = new File(secDir, SliderKeys.CRT_FILE_NAME); + Assert.assertTrue("Server CRD does not exist:" + serverCrt, + serverCrt.exists()); + } + + @Test + public void testAMKeystoreGenerated() throws Exception { + File keystoreFile = new File(secDir, SliderKeys.KEYSTORE_FILE_NAME); + Assert.assertTrue("Keystore does not exist: " + keystoreFile, + keystoreFile.exists()); + InputStream is = null; + try { + + is = new FileInputStream(keystoreFile); + KeyStore keystore = KeyStore.getInstance("pkcs12"); + String password = SecurityUtils.getKeystorePass(); + keystore.load(is, password.toCharArray()); + + Certificate certificate = keystore.getCertificate( + keystore.aliases().nextElement()); + Assert.assertNotNull(certificate); + + if (certificate instanceof X509Certificate) { + X509Certificate x509cert = (X509Certificate) certificate; + + // Get subject + Principal principal = x509cert.getSubjectDN(); + String subjectDn = principal.getName(); + Assert.assertEquals("wrong DN", + "CN=cahost", + subjectDn); + + // Get issuer + principal = x509cert.getIssuerDN(); + String issuerDn = principal.getName(); + Assert.assertEquals("wrong Issuer DN", + "CN=cahost", + issuerDn); + } + } finally { + if(null != is) { + is.close(); + } + } + } + + @Test + public void testContainerCertificateGeneration() throws Exception { + certMan.generateContainerCertificate("testhost", "container1"); + Assert.assertTrue("container certificate not generated", + new File(secDir, "container1.crt").exists()); + } + + @Test + public void testContainerKeystoreGeneration() throws Exception { + SecurityStore keystoreFile = certMan.generateContainerKeystore("testhost", + "container1", + "component1", + "password"); + validateKeystore(keystoreFile.getFile(), "testhost", "cahost"); + } + + private void validateKeystore(File keystoreFile, String certHostname, + String issuerHostname) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + Assert.assertTrue("container keystore not generated", + keystoreFile.exists()); + + InputStream is = null; + try { + + is = new FileInputStream(keystoreFile); + KeyStore keystore = KeyStore.getInstance("pkcs12"); + String password = "password"; + keystore.load(is, password.toCharArray()); + + Certificate certificate = keystore.getCertificate( + keystore.aliases().nextElement()); + Assert.assertNotNull(certificate); + + if (certificate instanceof X509Certificate) { + X509Certificate x509cert = (X509Certificate) certificate; + + // Get subject + Principal principal = x509cert.getSubjectDN(); + String subjectDn = principal.getName(); + Assert.assertEquals("wrong DN", "CN=" + certHostname + ", OU=container1", + subjectDn); + + // Get issuer + principal = x509cert.getIssuerDN(); + String issuerDn = principal.getName(); + Assert.assertEquals("wrong Issuer DN", + "CN=" + issuerHostname, + issuerDn); + } + } finally { + if(null != is) { + is.close(); + } + } + } + + @Test + public void testContainerKeystoreGenerationViaStoresGenerator() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = new MapOperations(); + instanceDefinition.getAppConf().components.put("component1", compOps); + compOps.put(SliderKeys.COMP_KEYSTORE_PASSWORD_PROPERTY_KEY, + "app1.component1.password.property"); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + instanceDefinition.getAppConf().global.put( + "app1.component1.password.property", "password"); + instanceDefinition.resolve(); + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + validateKeystore(files[0].getFile(), "testhost", "cahost"); + } + + @Test + public void testContainerKeystoreGenerationViaStoresGeneratorUsingGlobalProps() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = new MapOperations(); + instanceDefinition.getAppConf().components.put("component1", compOps); + compOps.put(SliderKeys.COMP_KEYSTORE_PASSWORD_PROPERTY_KEY, + "app1.component1.password.property"); + instanceDefinition.getAppConf().global.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + compOps.put( + "app1.component1.password.property", "password"); + instanceDefinition.resolve(); + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + validateKeystore(files[0].getFile(), "testhost", "cahost"); + } + + @Test + public void testContainerKeystoreGenerationViaStoresGeneratorOverrideGlobalSetting() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = setupComponentOptions(true, null, + "app1.component1.password.property", + null, null); + instanceDefinition.getAppConf().components.put("component1", compOps); + instanceDefinition.getAppConf().global.put( + "app1.component1.password.property", "password"); + instanceDefinition.getAppConf().global.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "false"); + instanceDefinition.resolve(); + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + validateKeystore(files[0].getFile(), "testhost", "cahost"); + } + + @Test + public void testContainerTrusttoreGeneration() throws Exception { + SecurityStore keystoreFile = + certMan.generateContainerKeystore("testhost", + "container1", + "component1", + "keypass"); + Assert.assertTrue("container keystore not generated", + keystoreFile.getFile().exists()); + SecurityStore truststoreFile = + certMan.generateContainerTruststore("container1", + "component1", "trustpass" + ); + Assert.assertTrue("container truststore not generated", + truststoreFile.getFile().exists()); + + validateTruststore(keystoreFile.getFile(), truststoreFile.getFile()); + } + + @Test + public void testContainerGenerationUsingStoresGeneratorNoTruststore() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = new MapOperations(); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + compOps.put(SliderKeys.COMP_KEYSTORE_PASSWORD_ALIAS_KEY, + "test.keystore.password"); + + setupCredentials(instanceDefinition, "test.keystore.password", null); + + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + File keystoreFile = CertificateManager.getContainerKeystoreFilePath( + "container1", "component1"); + Assert.assertTrue("container keystore not generated", + keystoreFile.exists()); + + Assert.assertTrue("keystore not in returned list", + Arrays.asList(files).contains(new SecurityStore(keystoreFile, + SecurityStore.StoreType.keystore))); + File truststoreFile = + CertificateManager.getContainerTruststoreFilePath("component1", + "container1"); + Assert.assertFalse("container truststore generated", + truststoreFile.exists()); + Assert.assertFalse("truststore in returned list", + Arrays.asList(files).contains(new SecurityStore(truststoreFile, + SecurityStore.StoreType.truststore))); + + } + + @Test + public void testContainerGenerationUsingStoresGeneratorJustTruststoreWithDefaultAlias() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = setupComponentOptions(true); + + setupCredentials(instanceDefinition, null, + SliderKeys.COMP_TRUSTSTORE_PASSWORD_ALIAS_DEFAULT); + + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + File keystoreFile = CertificateManager.getContainerKeystoreFilePath( + "container1", "component1"); + Assert.assertFalse("container keystore generated", + keystoreFile.exists()); + Assert.assertFalse("keystore in returned list", + Arrays.asList(files).contains(keystoreFile)); + File truststoreFile = + CertificateManager.getContainerTruststoreFilePath("component1", + "container1"); + Assert.assertTrue("container truststore not generated", + truststoreFile.exists()); + Assert.assertTrue("truststore not in returned list", + Arrays.asList(files).contains(new SecurityStore(truststoreFile, + SecurityStore.StoreType.truststore))); + + } + + @Test + public void testContainerTrusttoreGenerationUsingStoresGenerator() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = setupComponentOptions(true, + "test.keystore.password", + null, + "test.truststore.password", + null); + + setupCredentials(instanceDefinition, "test.keystore.password", + "test.truststore.password"); + + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 2, files.length); + File keystoreFile = CertificateManager.getContainerKeystoreFilePath( + "container1", "component1"); + Assert.assertTrue("container keystore not generated", + keystoreFile.exists()); + Assert.assertTrue("keystore not in returned list", + Arrays.asList(files).contains(new SecurityStore(keystoreFile, + SecurityStore.StoreType.keystore))); + File truststoreFile = + CertificateManager.getContainerTruststoreFilePath("component1", + "container1"); + Assert.assertTrue("container truststore not generated", + truststoreFile.exists()); + Assert.assertTrue("truststore not in returned list", + Arrays.asList(files).contains(new SecurityStore(truststoreFile, + SecurityStore.StoreType.truststore))); + + validateTruststore(keystoreFile, truststoreFile); + } + + private void setupCredentials(AggregateConf instanceDefinition, + String keyAlias, String trustAlias) + throws Exception { + Configuration conf = new Configuration(); + final Path jksPath = new Path(SecurityUtils.getSecurityDir(), "test.jks"); + final String ourUrl = + JavaKeyStoreProvider.SCHEME_NAME + "://file" + jksPath.toUri(); + + File file = new File(SecurityUtils.getSecurityDir(), "test.jks"); + file.delete(); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + + instanceDefinition.getAppConf().credentials.put(ourUrl, new ArrayList<String>()); + + CredentialProvider provider = + CredentialProviderFactory.getProviders(conf).get(0); + + // create new aliases + try { + + if (keyAlias != null) { + char[] storepass = {'k', 'e', 'y', 'p', 'a', 's', 's'}; + provider.createCredentialEntry( + keyAlias, storepass); + } + + if (trustAlias != null) { + char[] trustpass = {'t', 'r', 'u', 's', 't', 'p', 'a', 's', 's'}; + provider.createCredentialEntry( + trustAlias, trustpass); + } + + // write out so that it can be found in checks + provider.flush(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + private MapOperations setupComponentOptions(boolean storesRequired) { + return this.setupComponentOptions(storesRequired, null, null, null, null); + } + + private MapOperations setupComponentOptions(boolean storesRequired, + String keyAlias, + String keyPwd, + String trustAlias, + String trustPwd) { + MapOperations compOps = new MapOperations(); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, + Boolean.toString(storesRequired)); + if (keyAlias != null) { + compOps.put(SliderKeys.COMP_KEYSTORE_PASSWORD_ALIAS_KEY, + "test.keystore.password"); + } + if (trustAlias != null) { + compOps.put(SliderKeys.COMP_TRUSTSTORE_PASSWORD_ALIAS_KEY, + "test.truststore.password"); + } + if (keyPwd != null) { + compOps.put(SliderKeys.COMP_KEYSTORE_PASSWORD_PROPERTY_KEY, + keyPwd); + } + if (trustPwd != null) { + compOps.put(SliderKeys.COMP_TRUSTSTORE_PASSWORD_PROPERTY_KEY, + trustPwd); + } + return compOps; + } + + @Test + public void testContainerStoresGenerationKeystoreOnly() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = new MapOperations(); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + + setupCredentials(instanceDefinition, + SliderKeys.COMP_KEYSTORE_PASSWORD_ALIAS_DEFAULT, null); + + SecurityStore[] + files = StoresGenerator.generateSecurityStores("testhost", + "container1", + "component1", + instanceDefinition, + compOps); + assertEquals("wrong number of stores", 1, files.length); + File keystoreFile = CertificateManager.getContainerKeystoreFilePath( + "container1", "component1"); + Assert.assertTrue("container keystore not generated", + keystoreFile.exists()); + Assert.assertTrue("keystore not in returned list", + Arrays.asList(files).contains(new SecurityStore(keystoreFile, + SecurityStore.StoreType.keystore))); + File truststoreFile = + CertificateManager.getContainerTruststoreFilePath("component1", + "container1"); + Assert.assertFalse("container truststore generated", + truststoreFile.exists()); + Assert.assertFalse("truststore in returned list", + Arrays.asList(files).contains(new SecurityStore(truststoreFile, + SecurityStore.StoreType.truststore))); + + } + + @Test + public void testContainerStoresGenerationMisconfiguration() throws Exception { + AggregateConf instanceDefinition = new AggregateConf(); + MapOperations compOps = new MapOperations(); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + + setupCredentials(instanceDefinition, "cant.be.found", null); + + try { + StoresGenerator.generateSecurityStores("testhost", "container1", + "component1", instanceDefinition, + compOps); + Assert.fail("SliderException should have been generated"); + } catch (SliderException e) { + // ignore - should be thrown + } + } + + private void validateTruststore(File keystoreFile, File truststoreFile) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + InputStream keyis = null; + InputStream trustis = null; + try { + + // create keystore + keyis = new FileInputStream(keystoreFile); + KeyStore keystore = KeyStore.getInstance("pkcs12"); + String password = "keypass"; + keystore.load(keyis, password.toCharArray()); + + // obtain server cert + Certificate certificate = keystore.getCertificate( + keystore.aliases().nextElement()); + Assert.assertNotNull(certificate); + + // create trust store from generated trust store file + trustis = new FileInputStream(truststoreFile); + KeyStore truststore = KeyStore.getInstance("pkcs12"); + password = "trustpass"; + truststore.load(trustis, password.toCharArray()); + + // validate keystore cert using trust store + TrustManagerFactory + trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(truststore); + + for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) { + if (trustManager instanceof X509TrustManager) { + X509TrustManager x509TrustManager = (X509TrustManager)trustManager; + x509TrustManager.checkServerTrusted( + new X509Certificate[] {(X509Certificate) certificate}, + "RSA_EXPORT"); + } + } + + } finally { + if(null != keyis) { + keyis.close(); + } + if(null != trustis) { + trustis.close(); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestMultiThreadedStoreGeneration.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestMultiThreadedStoreGeneration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestMultiThreadedStoreGeneration.java new file mode 100644 index 0000000..2e2ffce --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/security/TestMultiThreadedStoreGeneration.java @@ -0,0 +1,156 @@ +/* + * 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.slider.server.services.security; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.security.alias.JavaKeyStoreProvider; +import org.apache.slider.common.SliderKeys; +import org.apache.slider.common.SliderXmlConfKeys; +import org.apache.slider.core.conf.AggregateConf; +import org.apache.slider.core.conf.MapOperations; +import org.apache.slider.core.exceptions.SliderException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.assertTrue; + +/** + * + */ +public class TestMultiThreadedStoreGeneration { + + public static final int NUM_THREADS = 30; + @Rule + public TemporaryFolder workDir = new TemporaryFolder();; + + private void setupCredentials(AggregateConf instanceDefinition, + String keyAlias, String trustAlias) + throws Exception { + Configuration conf = new Configuration(); + final Path jksPath = new Path(SecurityUtils.getSecurityDir(), "test.jks"); + final String ourUrl = + JavaKeyStoreProvider.SCHEME_NAME + "://file" + jksPath.toUri(); + + File file = new File(SecurityUtils.getSecurityDir(), "test.jks"); + file.delete(); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + + instanceDefinition.getAppConf().credentials.put(ourUrl, new ArrayList<String>()); + + CredentialProvider provider = + CredentialProviderFactory.getProviders(conf).get(0); + + // create new aliases + try { + + if (keyAlias != null) { + char[] storepass = {'k', 'e', 'y', 'p', 'a', 's', 's'}; + provider.createCredentialEntry( + keyAlias, storepass); + } + + if (trustAlias != null) { + char[] trustpass = {'t', 'r', 'u', 's', 't', 'p', 'a', 's', 's'}; + provider.createCredentialEntry( + trustAlias, trustpass); + } + + // write out so that it can be found in checks + provider.flush(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + + @Test + public void testMultiThreadedStoreGeneration() throws Exception { + + CertificateManager certMan = new CertificateManager(); + MapOperations compOperations = new MapOperations(); + File secDir = new File(workDir.getRoot(), SliderKeys.SECURITY_DIR); + File keystoreFile = new File(secDir, SliderKeys.KEYSTORE_FILE_NAME); + compOperations.put(SliderXmlConfKeys.KEY_KEYSTORE_LOCATION, + keystoreFile.getAbsolutePath()); + certMan.initialize(compOperations, "cahost", null, null); + + final CountDownLatch latch = new CountDownLatch(1); + final List<SecurityStore> stores = new ArrayList<>(); + List<Thread> threads = new ArrayList<>(); + final AggregateConf instanceDefinition = new AggregateConf(); + + setupCredentials(instanceDefinition, + SliderKeys.COMP_KEYSTORE_PASSWORD_ALIAS_DEFAULT, null); + final MapOperations compOps = new MapOperations(); + compOps.put(SliderKeys.COMP_STORES_REQUIRED_KEY, "true"); + + for (int i=0; i<NUM_THREADS; ++i) { + final int finalI = i; + Runnable runner = new Runnable() { + public void run() { + System.out.println ("----> In run"); + try { + latch.await(); + SecurityStore[] stores1 = StoresGenerator.generateSecurityStores( + "testhost", + "container" + finalI, + "component" + finalI, + instanceDefinition, + compOps); + System.out.println ("----> stores1" + stores1); + List<SecurityStore> + securityStores = + Arrays.asList(stores1); + stores.addAll(securityStores); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (SliderException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + Thread thread = new Thread(runner, "TestThread" + i); + threads.add(thread); + thread.start(); + } + latch.countDown(); + for (Thread t : threads) { + t.join(); + } + + for (int i=0; i < NUM_THREADS; i++) { + assertTrue("keystore " + i + " not generated", stores.get(i).getFile().exists()); + } + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/MockService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/MockService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/MockService.java new file mode 100644 index 0000000..588f621 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/MockService.java @@ -0,0 +1,80 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.service.AbstractService; +import org.apache.hadoop.service.ServiceStateException; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MockService extends AbstractService { + private final boolean fail; + private final int lifespan; + private final ExecutorService executorService = + Executors.newSingleThreadExecutor(); + + MockService() { + this("mock", false, -1); + } + + MockService(String name, boolean fail, int lifespan) { + super(name); + this.fail = fail; + this.lifespan = lifespan; + } + + @Override + protected void serviceStart() throws Exception { + //act on the lifespan here + if (lifespan > 0) { + executorService.submit(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(lifespan); + } catch (InterruptedException ignored) { + + } + finish(); + } + }); + } else { + if (lifespan == 0) { + finish(); + } else { + //continue until told not to + } + } + } + + void finish() { + if (fail) { + ServiceStateException e = + new ServiceStateException(getName() + " failed"); + + noteFailure(e); + stop(); + throw e; + } else { + stop(); + } + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ParentWorkflowTestBase.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ParentWorkflowTestBase.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ParentWorkflowTestBase.java new file mode 100644 index 0000000..a11a1cf --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ParentWorkflowTestBase.java @@ -0,0 +1,70 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.service.Service; + +/** + * Extends {@link WorkflowServiceTestBase} with parent-specific operations + * and logic to build up and run the parent service + */ +public abstract class ParentWorkflowTestBase extends WorkflowServiceTestBase { + + /** + * Wait a second for the service parent to stop + * @param parent the service to wait for + */ + protected void waitForParentToStop(ServiceParent parent) { + waitForParentToStop(parent, 1000); + } + + /** + * Wait for the service parent to stop + * @param parent the service to wait for + * @param timeout time in milliseconds + */ + protected void waitForParentToStop(ServiceParent parent, int timeout) { + boolean stop = parent.waitForServiceToStop(timeout); + if (!stop) { + logState(parent); + fail("Service failed to stop : after " + timeout + " millis " + parent); + } + } + + /** + * Subclasses are require to implement this and return an instance of a + * ServiceParent + * @param services a possibly empty list of services + * @return an inited -but -not-started- service parent instance + */ + protected abstract ServiceParent buildService(Service... services); + + /** + * Use {@link #buildService(Service...)} to create service and then start it + * @param services + * @return + */ + protected ServiceParent startService(Service... services) { + ServiceParent parent = buildService(services); + //expect service to start and stay started + parent.start(); + return parent; + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ProcessCommandFactory.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ProcessCommandFactory.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ProcessCommandFactory.java new file mode 100644 index 0000000..4a19417 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/ProcessCommandFactory.java @@ -0,0 +1,96 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.util.Shell; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A source of commands, with the goal being to allow for adding different + * implementations for different platforms + */ +public class ProcessCommandFactory { + + protected ProcessCommandFactory() { + } + + /** + * The command to list a directory + * @param dir directory + * @return commands + */ + public List<String> ls(File dir) { + List<String> commands; + if (!Shell.WINDOWS) { + commands = Arrays.asList("ls","-1", dir.getAbsolutePath()); + } else { + commands = Arrays.asList("cmd", "/c", "dir", dir.getAbsolutePath()); + } + return commands; + } + + /** + * Echo some text to stdout + * @param text text + * @return commands + */ + public List<String> echo(String text) { + List<String> commands = new ArrayList<String>(5); + commands.add("echo"); + commands.add(text); + return commands; + } + + /** + * print env variables + * @return commands + */ + public List<String> env() { + List<String> commands; + if (!Shell.WINDOWS) { + commands = Arrays.asList("env"); + } else { + commands = Arrays.asList("cmd", "/c", "set"); + } + return commands; + } + + /** + * execute a command that returns with an error code that will + * be converted into a number + * @return commands + */ + public List<String> exitFalse() { + List<String> commands = new ArrayList<String>(2); + commands.add("false"); + return commands; + } + + /** + * Create a process command factory for this OS + * @return + */ + public static ProcessCommandFactory createProcessCommandFactory() { + return new ProcessCommandFactory(); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/SimpleRunnable.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/SimpleRunnable.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/SimpleRunnable.java new file mode 100644 index 0000000..1f330f4 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/SimpleRunnable.java @@ -0,0 +1,46 @@ +/* + * 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.slider.server.services.workflow; + +/** + * Test runnable that can be made to exit, or throw an exception + * during its run + */ +class SimpleRunnable implements Runnable { + boolean throwException = false; + + + SimpleRunnable() { + } + + SimpleRunnable(boolean throwException) { + this.throwException = throwException; + } + + @Override + public synchronized void run() { + try { + if (throwException) { + throw new RuntimeException("SimpleRunnable"); + } + } finally { + this.notify(); + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowClosingService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowClosingService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowClosingService.java new file mode 100644 index 0000000..39516b7 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowClosingService.java @@ -0,0 +1,116 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.conf.Configuration; +import org.junit.Test; + +import java.io.Closeable; +import java.io.IOException; + +public class TestWorkflowClosingService extends WorkflowServiceTestBase { + + @Test + public void testSimpleClose() throws Throwable { + ClosingService<OpenClose> svc = instance(false); + OpenClose openClose = svc.getCloseable(); + assertFalse(openClose.closed); + svc.stop(); + assertTrue(openClose.closed); + } + + @Test + public void testNullClose() throws Throwable { + ClosingService<OpenClose> svc = new ClosingService<OpenClose>("", null); + svc.init(new Configuration()); + svc.start(); + assertNull(svc.getCloseable()); + svc.stop(); + } + + @Test + public void testFailingClose() throws Throwable { + ClosingService<OpenClose> svc = instance(false); + OpenClose openClose = svc.getCloseable(); + openClose.raiseExceptionOnClose = true; + svc.stop(); + assertTrue(openClose.closed); + Throwable cause = svc.getFailureCause(); + assertNotNull(cause); + + //retry should be a no-op + svc.close(); + } + + @Test + public void testDoubleClose() throws Throwable { + ClosingService<OpenClose> svc = instance(false); + OpenClose openClose = svc.getCloseable(); + openClose.raiseExceptionOnClose = true; + svc.stop(); + assertTrue(openClose.closed); + Throwable cause = svc.getFailureCause(); + assertNotNull(cause); + openClose.closed = false; + svc.stop(); + assertEquals(cause, svc.getFailureCause()); + } + + /** + * This does not recurse forever, as the service has already entered the + * STOPPED state before the inner close tries to stop it -that operation + * is a no-op + * @throws Throwable + */ + @Test + public void testCloseSelf() throws Throwable { + ClosingService<ClosingService> svc = + new ClosingService<ClosingService>(""); + svc.setCloseable(svc); + svc.stop(); + } + + + private ClosingService<OpenClose> instance(boolean raiseExceptionOnClose) { + ClosingService<OpenClose> svc = new ClosingService<OpenClose>(new OpenClose( + raiseExceptionOnClose)); + svc.init(new Configuration()); + svc.start(); + return svc; + } + + private static class OpenClose implements Closeable { + public boolean closed = false; + public boolean raiseExceptionOnClose; + + private OpenClose(boolean raiseExceptionOnClose) { + this.raiseExceptionOnClose = raiseExceptionOnClose; + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (raiseExceptionOnClose) { + throw new IOException("OpenClose"); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowCompositeService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowCompositeService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowCompositeService.java new file mode 100644 index 0000000..5780149 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowCompositeService.java @@ -0,0 +1,113 @@ +/* + * 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.slider.server.services.workflow; + + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.service.Service; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestWorkflowCompositeService extends ParentWorkflowTestBase { + private static final Logger + log = LoggerFactory.getLogger(TestWorkflowCompositeService.class); + + @Test + public void testSingleChild() throws Throwable { + Service parent = startService(new MockService()); + parent.stop(); + } + + @Test + public void testSingleChildTerminating() throws Throwable { + ServiceParent parent = + startService(new MockService("1", false, 100)); + waitForParentToStop(parent); + } + + @Test + public void testSingleChildFailing() throws Throwable { + ServiceParent parent = + startService(new MockService("1", true, 100)); + waitForParentToStop(parent); + assert parent.getFailureCause() != null; + } + + @Test + public void testTwoChildren() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", false, 100); + ServiceParent parent = startService(one, two); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + } + + @Test + public void testCallableChild() throws Throwable { + + MockService one = new MockService("one", false, 100); + CallableHandler handler = new CallableHandler("hello"); + WorkflowCallbackService<String> ens = + new WorkflowCallbackService<String>("handler", handler, 100, true); + MockService two = new MockService("two", false, 100); + ServiceParent parent = startService(one, ens, two); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(ens); + assertStopped(two); + assertTrue(handler.notified); + String s = ens.getScheduledFuture().get(); + assertEquals("hello", s); + } + + @Test + public void testNestedComposite() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", false, 100); + ServiceParent parent = buildService(one, two); + ServiceParent outer = startService(parent); + assertTrue(outer.waitForServiceToStop(1000)); + assertStopped(one); + assertStopped(two); + } + + @Test + public void testFailingComposite() throws Throwable { + MockService one = new MockService("one", true, 10); + MockService two = new MockService("two", false, 1000); + ServiceParent parent = startService(one, two); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + assertNotNull(one.getFailureCause()); + assertNotNull(parent.getFailureCause()); + assertEquals(one.getFailureCause(), parent.getFailureCause()); + } + + @Override + public ServiceParent buildService(Service... services) { + ServiceParent parent = + new WorkflowCompositeService("test", services); + parent.init(new Configuration()); + return parent; + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowExecutorService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowExecutorService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowExecutorService.java new file mode 100644 index 0000000..dc160d9 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowExecutorService.java @@ -0,0 +1,66 @@ +/* + * 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.slider.server.services.workflow; + +import org.junit.Test; + +import java.util.concurrent.ExecutorService; + + +/** + * Basic tests for executor service + */ +public class TestWorkflowExecutorService extends WorkflowServiceTestBase { + + @Test + public void testAsyncRun() throws Throwable { + + ExecutorSvc svc = run(new ExecutorSvc()); + ServiceTerminatingRunnable runnable = new ServiceTerminatingRunnable(svc, + new SimpleRunnable()); + + // synchronous in-thread execution + svc.execute(runnable); + Thread.sleep(1000); + assertStopped(svc); + } + + @Test + public void testFailureRun() throws Throwable { + + ExecutorSvc svc = run(new ExecutorSvc()); + ServiceTerminatingRunnable runnable = new ServiceTerminatingRunnable(svc, + new SimpleRunnable(true)); + + // synchronous in-thread execution + svc.execute(runnable); + Thread.sleep(1000); + assertStopped(svc); + assertNotNull(runnable.getException()); + } + + private static class ExecutorSvc + extends WorkflowExecutorService<ExecutorService> { + private ExecutorSvc() { + super("ExecutorService", + ServiceThreadFactory.singleThreadExecutor("test", true)); + } + + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowRpcService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowRpcService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowRpcService.java new file mode 100644 index 0000000..38cd9e1 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowRpcService.java @@ -0,0 +1,107 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Writable; +import org.apache.hadoop.ipc.RPC; +import org.apache.hadoop.ipc.Server; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class TestWorkflowRpcService extends WorkflowServiceTestBase { + + @Test + public void testCreateMockRPCService() throws Throwable { + MockRPC rpc = new MockRPC(); + rpc.start(); + assertTrue(rpc.started); + rpc.getListenerAddress(); + rpc.stop(); + assertTrue(rpc.stopped); + } + + @Test + public void testLifecycle() throws Throwable { + MockRPC rpc = new MockRPC(); + WorkflowRpcService svc = new WorkflowRpcService("test", rpc); + run(svc); + assertTrue(rpc.started); + svc.getConnectAddress(); + svc.stop(); + assertTrue(rpc.stopped); + } + + @Test + public void testStartFailure() throws Throwable { + MockRPC rpc = new MockRPC(); + rpc.failOnStart = true; + WorkflowRpcService svc = new WorkflowRpcService("test", rpc); + svc.init(new Configuration()); + try { + svc.start(); + fail("expected an exception"); + } catch (RuntimeException e) { + assertEquals("failOnStart", e.getMessage()); + } + svc.stop(); + assertTrue(rpc.stopped); + } + + private static class MockRPC extends Server { + + public boolean stopped; + public boolean started; + public boolean failOnStart; + + private MockRPC() throws IOException { + super("localhost", 0, null, 1, new Configuration()); + } + + @Override + public synchronized void start() { + if (failOnStart) { + throw new RuntimeException("failOnStart"); + } + started = true; + super.start(); + } + + @Override + public synchronized void stop() { + stopped = true; + super.stop(); + } + + @Override + public synchronized InetSocketAddress getListenerAddress() { + return super.getListenerAddress(); + } + + @Override + public Writable call(RPC.RpcKind rpcKind, + String protocol, + Writable param, + long receiveTime) throws Exception { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowSequenceService.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowSequenceService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowSequenceService.java new file mode 100644 index 0000000..581e3ed --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowSequenceService.java @@ -0,0 +1,151 @@ +/* + * 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.slider.server.services.workflow; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.service.Service; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestWorkflowSequenceService extends ParentWorkflowTestBase { + private static final Logger + log = LoggerFactory.getLogger(TestWorkflowSequenceService.class); + + @Test + public void testSingleSequence() throws Throwable { + ServiceParent parent = startService(new MockService()); + parent.stop(); + } + + @Test + public void testEmptySequence() throws Throwable { + ServiceParent parent = startService(); + waitForParentToStop(parent); + } + + @Test + public void testSequence() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", false, 100); + ServiceParent parent = startService(one, two); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + assert ((WorkflowSequenceService) parent).getPreviousService().equals(two); + } + + @Test + public void testCallableChild() throws Throwable { + + MockService one = new MockService("one", false, 100); + CallableHandler handler = new CallableHandler("hello"); + WorkflowCallbackService<String> ens = + new WorkflowCallbackService<String>("handler", handler, 100, true); + MockService two = new MockService("two", false, 100); + ServiceParent parent = startService(one, ens, two); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(ens); + assertStopped(two); + assertTrue(handler.notified); + String s = ens.getScheduledFuture().get(); + assertEquals("hello", s); + } + + + @Test + public void testFailingSequence() throws Throwable { + MockService one = new MockService("one", true, 100); + MockService two = new MockService("two", false, 100); + WorkflowSequenceService parent = + (WorkflowSequenceService) startService(one, two); + waitForParentToStop(parent); + assertStopped(one); + assertInState(two, Service.STATE.NOTINITED); + assertEquals(one, parent.getPreviousService()); + } + + + @Test + public void testFailInStartNext() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", true, 0); + MockService three = new MockService("3", false, 0); + ServiceParent parent = startService(one, two, three); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + Throwable failureCause = two.getFailureCause(); + assertNotNull(failureCause); + Throwable parentFailureCause = parent.getFailureCause(); + assertNotNull(parentFailureCause); + assertEquals(parentFailureCause, failureCause); + assertInState(three, Service.STATE.NOTINITED); + } + + @Test + public void testSequenceInSequence() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", false, 100); + ServiceParent parent = buildService(one, two); + ServiceParent outer = startService(parent); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + } + + @Test + public void testVarargsConstructor() throws Throwable { + MockService one = new MockService("one", false, 100); + MockService two = new MockService("two", false, 100); + ServiceParent parent = new WorkflowSequenceService("test", one, two); + parent.init(new Configuration()); + parent.start(); + waitForParentToStop(parent); + assertStopped(one); + assertStopped(two); + } + + + @Test + public void testAddChild() throws Throwable { + MockService one = new MockService("one", false, 5000); + MockService two = new MockService("two", false, 100); + ServiceParent parent = startService(one, two); + CallableHandler handler = new CallableHandler("hello"); + WorkflowCallbackService<String> ens = + new WorkflowCallbackService<String>("handler", handler, 100, true); + parent.addService(ens); + waitForParentToStop(parent, 10000); + assertStopped(one); + assertStopped(two); + assertStopped(ens); + assertStopped(two); + assertEquals("hello", ens.getScheduledFuture().get()); + } + + public WorkflowSequenceService buildService(Service... services) { + WorkflowSequenceService parent = + new WorkflowSequenceService("test", services); + parent.init(new Configuration()); + return parent; + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowServiceTerminatingRunnable.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowServiceTerminatingRunnable.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowServiceTerminatingRunnable.java new file mode 100644 index 0000000..5b7a6f9 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowServiceTerminatingRunnable.java @@ -0,0 +1,64 @@ +/* + * 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.slider.server.services.workflow; + +import org.junit.Test; + + +public class TestWorkflowServiceTerminatingRunnable extends WorkflowServiceTestBase { + + @Test + public void testNoservice() throws Throwable { + + try { + new ServiceTerminatingRunnable(null, new SimpleRunnable()); + fail("unexpected "); + } catch (IllegalArgumentException e) { + + // expected + } + } + + + @Test + public void testBasicRun() throws Throwable { + + WorkflowCompositeService svc = run(new WorkflowCompositeService()); + ServiceTerminatingRunnable runnable = new ServiceTerminatingRunnable(svc, + new SimpleRunnable()); + + // synchronous in-thread execution + runnable.run(); + assertStopped(svc); + } + + @Test + public void testFailureRun() throws Throwable { + + WorkflowCompositeService svc = run(new WorkflowCompositeService()); + ServiceTerminatingRunnable runnable = + new ServiceTerminatingRunnable(svc, new SimpleRunnable(true)); + + // synchronous in-thread execution + runnable.run(); + assertStopped(svc); + assertNotNull(runnable.getException()); + } + +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/a4beaeb8/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/WorkflowServiceTestBase.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/WorkflowServiceTestBase.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/WorkflowServiceTestBase.java new file mode 100644 index 0000000..f38bd9d --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/services/workflow/WorkflowServiceTestBase.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 + * + * 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.slider.server.services.workflow; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.service.Service; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.junit.rules.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.Callable; + +/** + * Test base for workflow service tests. + */ +public abstract class WorkflowServiceTestBase extends Assert { + private static final Logger + log = LoggerFactory.getLogger(WorkflowServiceTestBase.class); + + /** + * Set the timeout for every test + */ + @Rule + public Timeout testTimeout = new Timeout(15000); + + @Rule + public TestName name = new TestName(); + + @Before + public void nameThread() { + Thread.currentThread().setName("JUnit"); + } + + + protected void assertInState(Service service, Service.STATE expected) { + Service.STATE actual = service.getServiceState(); + if (actual != expected) { + fail("Service " + service.getName() + " in state " + actual + + " -expected " + expected); + } + } + + protected void assertStopped(Service service) { + assertInState(service, Service.STATE.STOPPED); + } + + protected void logState(ServiceParent p) { + logService(p); + for (Service s : p.getServices()) { + logService(s); + } + } + + protected void logService(Service s) { + log.info(s.toString()); + Throwable failureCause = s.getFailureCause(); + if (failureCause != null) { + log.info("Failed in state {} with {}", s.getFailureState(), + failureCause); + } + } + + /** + * Init and start a service + * @param svc the service + * @return the service + */ + protected <S extends Service> S run(S svc) { + svc.init(new Configuration()); + svc.start(); + return svc; + } + + /** + * Handler for callable events + */ + public static class CallableHandler implements Callable<String> { + public volatile boolean notified = false; + public final String result; + + public CallableHandler(String result) { + this.result = result; + } + + @Override + public String call() throws Exception { + log.info("CallableHandler::call"); + notified = true; + return result; + } + } + + /** + * Assert that a string is in an output list. Fails fast if the output + * list is empty + * @param text text to scan for + * @param output list of output lines. + */ + public void assertStringInOutput(String text, List<String> output) { + assertTrue("Empty output list", !output.isEmpty()); + boolean found = false; + StringBuilder builder = new StringBuilder(); + for (String s : output) { + builder.append(s.toLowerCase(Locale.ENGLISH)).append('\n'); + if (s.contains(text)) { + found = true; + break; + } + } + + if (!found) { + String message = + "Text \"" + text + "\" not found in " + output.size() + " lines\n"; + fail(message + builder.toString()); + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org