/* * Copyright © 2015-2016 Cask Data, Inc. * * Licensed 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 co.cask.cdap.test; import co.cask.cdap.api.app.Application; import co.cask.cdap.cli.util.InstanceURIParser; import co.cask.cdap.client.ApplicationClient; import co.cask.cdap.client.DatasetClient; import co.cask.cdap.client.MetaClient; import co.cask.cdap.client.MetricsClient; import co.cask.cdap.client.MonitorClient; import co.cask.cdap.client.NamespaceClient; import co.cask.cdap.client.ProgramClient; import co.cask.cdap.client.StreamClient; import co.cask.cdap.client.config.ClientConfig; import co.cask.cdap.client.config.ConnectionConfig; import co.cask.cdap.client.util.RESTClient; import co.cask.cdap.common.UnauthenticatedException; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.data2.datafabric.DefaultDatasetNamespace; import co.cask.cdap.proto.ApplicationRecord; import co.cask.cdap.proto.ConfigEntry; import co.cask.cdap.proto.DatasetSpecificationSummary; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.NamespaceMeta; import co.cask.cdap.proto.StreamDetail; import co.cask.cdap.proto.id.ApplicationId; import co.cask.cdap.security.authentication.client.AccessToken; import co.cask.cdap.security.authentication.client.AuthenticationClient; import co.cask.cdap.security.authentication.client.basic.BasicAuthenticationClient; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.rules.TemporaryFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Abstract class for writing Integration tests for CDAP. Provides utility methods to use in Integration tests. * Users should extend this class in their test classes. */ public abstract class IntegrationTestBase { private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestBase.class); private static final long SERVICE_CHECK_TIMEOUT_SECONDS = TimeUnit.MINUTES.toSeconds(10); @ClassRule public static final TemporaryFolder TEMP_FOLDER = new TemporaryFolder(); private AccessToken accessToken; @Before public void setUp() throws Exception { checkSystemServices(); assertUnrecoverableResetEnabled(); assertIsClear(); } protected void checkSystemServices() throws TimeoutException, InterruptedException { Callable<Boolean> cdapAvailable = new Callable<Boolean>() { @Override public Boolean call() throws Exception { // first wait for all system services to be 'OK' if (!getMonitorClient().allSystemServicesOk()) { return false; } // Check that the dataset service is up, and also that the default namespace exists // Using list and checking if default namespace exists, as opposed to using get() // so we don't have to unnecessarily add a try-catch for NamespaceNotFoundException, since that exception is // not handled in checkServicesWithRetry. List<NamespaceMeta> namespaces = getNamespaceClient().list(); if (namespaces.size() == 0) { return false; } if (namespaces.contains(NamespaceMeta.DEFAULT)) { return true; } throw new IllegalStateException("Default namespace not found. Instead found unexpected namespaces: " + namespaces); } }; String errorMessage = String.format("CDAP Services are not available. Retried for %s seconds.", SERVICE_CHECK_TIMEOUT_SECONDS); try { checkServicesWithRetry(cdapAvailable, errorMessage); } catch (Throwable e) { Throwable rootCause = Throwables.getRootCause(e); if (rootCause instanceof UnauthenticatedException) { // security is enabled, we need to get access token before checking system services try { accessToken = fetchAccessToken(); } catch (IOException ex) { throw Throwables.propagate(ex); } checkServicesWithRetry(cdapAvailable, errorMessage); } else { throw Throwables.propagate(rootCause); } } LOG.info("CDAP Services are up and running!"); } /** * Uses BasicAuthenticationClient to fetch {@link AccessToken} - this implementation can be overridden if desired. * * @return {@link AccessToken} * @throws IOException * @throws TimeoutException if a timeout occurs while getting an access token */ protected AccessToken fetchAccessToken() throws IOException, TimeoutException, InterruptedException { Properties properties = new Properties(); properties.setProperty("security.auth.client.username", System.getProperty("cdap.username")); properties.setProperty("security.auth.client.password", System.getProperty("cdap.password")); final AuthenticationClient authClient = new BasicAuthenticationClient(); authClient.configure(properties); ConnectionConfig connectionConfig = getClientConfig().getConnectionConfig(); authClient.setConnectionInfo(connectionConfig.getHostname(), connectionConfig.getPort(), false); checkServicesWithRetry(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return authClient.getAccessToken() != null; } }, "Unable to connect to Authentication service to obtain access token, Connection info : " + connectionConfig); return authClient.getAccessToken(); } private void checkServicesWithRetry(Callable<Boolean> callable, String exceptionMessage) throws TimeoutException, InterruptedException { Stopwatch sw = new Stopwatch().start(); do { try { if (callable.call()) { return; } } catch (IOException e) { // We want to suppress and retry on IOException } catch (Throwable e) { // Also suppress and retry if the root cause is IOException Throwable rootCause = Throwables.getRootCause(e); if (!(rootCause instanceof IOException)) { // Throw if root cause is any other exception e.g. UnauthenticatedException throw Throwables.propagate(rootCause); } } TimeUnit.SECONDS.sleep(1); } while (sw.elapsedTime(TimeUnit.SECONDS) <= SERVICE_CHECK_TIMEOUT_SECONDS); // when we have passed the timeout and the check for services is not successful throw new TimeoutException(exceptionMessage); } private void assertUnrecoverableResetEnabled() throws IOException, UnauthenticatedException { ConfigEntry configEntry = getMetaClient().getCDAPConfig().get(Constants.Dangerous.UNRECOVERABLE_RESET); Preconditions.checkNotNull(configEntry, "Missing key from CDAP Configuration: {}", Constants.Dangerous.UNRECOVERABLE_RESET); Preconditions.checkState(Boolean.parseBoolean(configEntry.getValue()), "UnrecoverableReset not enabled."); } @After public void tearDown() throws Exception { getTestManager().clear(); assertIsClear(); } protected TestManager getTestManager() { try { return new IntegrationTestManager(getClientConfig(), getRestClient(), TEMP_FOLDER.newFolder()); } catch (IOException e) { throw Throwables.propagate(e); } } /** * Reads the CDAP instance URI from the system property "instanceUri". * "instanceUri" should be specified in the format [host]:[port]. * Defaults to "localhost:10000". */ protected String getInstanceURI() { return System.getProperty("instanceUri", "localhost:10000"); } /** * CDAP access token for making requests to secure CDAP instances. */ public AccessToken getAccessToken() { return accessToken; } private void assertIsClear() throws Exception { Id.Namespace namespace = Id.Namespace.DEFAULT; // only namespace existing should be 'default' NamespaceClient namespaceClient = getNamespaceClient(); List<NamespaceMeta> list = namespaceClient.list(); Assert.assertEquals(1, list.size()); Assert.assertEquals(NamespaceMeta.DEFAULT, list.get(0)); assertNoApps(namespace); assertNoUserDatasets(namespace); assertNoStreams(namespace); // TODO: check metrics, etc. } protected ClientConfig getClientConfig() { ClientConfig.Builder builder = new ClientConfig.Builder(); builder.setConnectionConfig(InstanceURIParser.DEFAULT.parse( URI.create(getInstanceURI()).toString())); if (accessToken != null) { builder.setAccessToken(accessToken); } String verifySSL = System.getProperty("verifySSL"); if (verifySSL != null) { builder.setVerifySSLCert(Boolean.valueOf(verifySSL)); } builder.setDefaultConnectTimeout(120000); builder.setDefaultReadTimeout(120000); builder.setUploadConnectTimeout(0); builder.setUploadReadTimeout(0); return builder.build(); } protected RESTClient getRestClient() { return new RESTClient(getClientConfig()); } protected MetaClient getMetaClient() { return new MetaClient(getClientConfig(), getRestClient()); } protected NamespaceClient getNamespaceClient() { return new NamespaceClient(getClientConfig(), getRestClient()); } @SuppressWarnings("unused") protected MetricsClient getMetricsClient() { return new MetricsClient(getClientConfig(), getRestClient()); } protected MonitorClient getMonitorClient() { return new MonitorClient(getClientConfig(), getRestClient()); } protected ApplicationClient getApplicationClient() { return new ApplicationClient(getClientConfig(), getRestClient()); } @SuppressWarnings("unused") protected ProgramClient getProgramClient() { return new ProgramClient(getClientConfig(), getRestClient()); } protected StreamClient getStreamClient() { return new StreamClient(getClientConfig(), getRestClient()); } protected DatasetClient getDatasetClient() { return new DatasetClient(getClientConfig(), getRestClient()); } protected Id.Namespace createNamespace(String name) throws Exception { Id.Namespace namespace = new Id.Namespace(name); NamespaceMeta namespaceMeta = new NamespaceMeta.Builder().setName(namespace).build(); getTestManager().createNamespace(namespaceMeta); return namespace; } protected ApplicationManager deployApplication(Id.Namespace namespace, Class<? extends Application> applicationClz, File...bundleEmbeddedJars) throws IOException { return getTestManager().deployApplication(namespace, applicationClz, bundleEmbeddedJars); } protected ApplicationManager deployApplication(Class<? extends Application> applicationClz) throws IOException { return deployApplication(Id.Namespace.DEFAULT, applicationClz); } protected ApplicationManager getApplicationManager(ApplicationId applicationId) throws Exception { return getTestManager().getApplicationManager(applicationId); } private boolean isUserDataset(DatasetSpecificationSummary specification) { final DefaultDatasetNamespace dsNamespace = new DefaultDatasetNamespace(CConfiguration.create()); return !dsNamespace.contains(specification.getName(), Id.Namespace.SYSTEM.getId()); } private void assertNoUserDatasets(Id.Namespace namespace) throws Exception { DatasetClient datasetClient = getDatasetClient(); List<DatasetSpecificationSummary> datasets = datasetClient.list(namespace); Iterable<DatasetSpecificationSummary> userDatasets = Iterables.filter( datasets, new Predicate<DatasetSpecificationSummary>() { @Override public boolean apply(DatasetSpecificationSummary input) { return isUserDataset(input); } }); Iterable<String> userDatasetNames = Iterables.transform( userDatasets, new Function<DatasetSpecificationSummary, String>() { @Override public String apply(DatasetSpecificationSummary input) { return input.getName(); } }); Assert.assertFalse("Must have no user datasets, but found the following user datasets: " + Joiner.on(", ").join(userDatasetNames), userDatasets.iterator().hasNext()); } private void assertNoApps(Id.Namespace namespace) throws Exception { ApplicationClient applicationClient = getApplicationClient(); List<ApplicationRecord> applicationRecords = applicationClient.list(namespace); List<String> applicationIds = Lists.newArrayList(); for (ApplicationRecord applicationRecord : applicationRecords) { applicationIds.add(applicationRecord.getName()); } Assert.assertTrue("Must have no deployed apps, but found the following apps: " + Joiner.on(", ").join(applicationIds), applicationRecords.isEmpty()); } private void assertNoStreams(Id.Namespace namespace) throws Exception { List<StreamDetail> streams = getStreamClient().list(namespace); List<String> streamNames = Lists.newArrayList(); for (StreamDetail stream : streams) { streamNames.add(stream.getName()); } Assert.assertTrue("Must have no streams, but found the following streams: " + Joiner.on(", ").join(streamNames), streamNames.isEmpty()); } }