/* * Copyright © 2014-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.data2.datafabric.dataset.service; import co.cask.cdap.api.dataset.module.DatasetDefinitionRegistry; import co.cask.cdap.api.dataset.module.DatasetModule; import co.cask.cdap.api.metrics.MetricsCollectionService; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.CConfigurationUtil; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.guice.ConfigModule; import co.cask.cdap.common.guice.LocationRuntimeModule; import co.cask.cdap.common.io.Locations; import co.cask.cdap.common.metrics.NoOpMetricsCollectionService; import co.cask.cdap.common.namespace.NamespacedLocationFactory; import co.cask.cdap.common.utils.DirUtils; import co.cask.cdap.data.dataset.SystemDatasetInstantiatorFactory; import co.cask.cdap.data.runtime.SystemDatasetRuntimeModule; import co.cask.cdap.data2.datafabric.dataset.DatasetMetaTableUtil; import co.cask.cdap.data2.datafabric.dataset.RemoteDatasetFramework; import co.cask.cdap.data2.datafabric.dataset.instance.DatasetInstanceManager; import co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetAdminOpHTTPHandler; import co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetAdminService; import co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetOpExecutorService; import co.cask.cdap.data2.datafabric.dataset.service.executor.InMemoryDatasetOpExecutor; import co.cask.cdap.data2.datafabric.dataset.service.mds.MDSDatasetsRegistry; import co.cask.cdap.data2.datafabric.dataset.type.DatasetTypeManager; import co.cask.cdap.data2.dataset2.DatasetDefinitionRegistryFactory; import co.cask.cdap.data2.dataset2.DefaultDatasetDefinitionRegistry; import co.cask.cdap.data2.dataset2.InMemoryDatasetFramework; import co.cask.cdap.data2.dataset2.InMemoryNamespaceStore; import co.cask.cdap.data2.metadata.store.NoOpMetadataStore; import co.cask.cdap.data2.metrics.DatasetMetricsReporter; import co.cask.cdap.data2.transaction.DelegatingTransactionSystemClientService; import co.cask.cdap.data2.transaction.TransactionSystemClientService; import co.cask.cdap.explore.client.DiscoveryExploreClient; import co.cask.cdap.explore.client.ExploreFacade; import co.cask.cdap.internal.test.AppJarHelper; import co.cask.cdap.proto.DatasetModuleMeta; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.NamespaceMeta; import co.cask.cdap.store.NamespaceStore; import co.cask.common.http.HttpRequest; import co.cask.common.http.HttpRequests; import co.cask.common.http.HttpResponse; import co.cask.common.http.ObjectResponse; import co.cask.http.HttpHandler; import co.cask.tephra.TransactionExecutorFactory; import co.cask.tephra.TransactionManager; import co.cask.tephra.inmemory.InMemoryTxSystemClient; import co.cask.tephra.runtime.TransactionInMemoryModule; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.io.Files; import com.google.common.util.concurrent.Uninterruptibles; import com.google.gson.reflect.TypeToken; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import org.apache.commons.httpclient.HttpStatus; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.twill.common.Threads; import org.apache.twill.discovery.InMemoryDiscoveryService; import org.apache.twill.discovery.ServiceDiscovered; import org.apache.twill.filesystem.LocalLocationFactory; import org.apache.twill.filesystem.Location; import org.apache.twill.filesystem.LocationFactory; import org.apache.twill.internal.Services; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Base class for unit-tests that require running of {@link DatasetService} */ public abstract class DatasetServiceTestBase { private InMemoryDiscoveryService discoveryService; private DatasetOpExecutorService opExecutorService; private DatasetService service; private LocationFactory locationFactory; private NamespaceStore namespaceStore; protected TransactionManager txManager; protected RemoteDatasetFramework dsFramework; protected InMemoryDatasetFramework inMemoryDatasetFramework; private int port = -1; @ClassRule public static TemporaryFolder tmpFolder = new TemporaryFolder(); @Before public void before() throws Exception { CConfiguration cConf = CConfiguration.create(); File dataDir = new File(tmpFolder.newFolder(), "data"); cConf.set(Constants.CFG_LOCAL_DATA_DIR, dataDir.getAbsolutePath()); if (!DirUtils.mkdirs(dataDir)) { throw new RuntimeException(String.format("Could not create DatasetFramework output dir %s", dataDir)); } cConf.set(Constants.Dataset.Manager.OUTPUT_DIR, dataDir.getAbsolutePath()); cConf.set(Constants.Dataset.Manager.ADDRESS, "localhost"); cConf.setBoolean(Constants.Dangerous.UNRECOVERABLE_RESET, true); // Starting DatasetService service discoveryService = new InMemoryDiscoveryService(); MetricsCollectionService metricsCollectionService = new NoOpMetricsCollectionService(); // Tx Manager to support working with datasets Configuration txConf = HBaseConfiguration.create(); CConfigurationUtil.copyTxProperties(cConf, txConf); txManager = new TransactionManager(txConf); txManager.startAndWait(); InMemoryTxSystemClient txSystemClient = new InMemoryTxSystemClient(txManager); TransactionSystemClientService txSystemClientService = new DelegatingTransactionSystemClientService(txSystemClient); final Injector injector = Guice.createInjector( new ConfigModule(cConf), new LocationRuntimeModule().getInMemoryModules(), new SystemDatasetRuntimeModule().getInMemoryModules(), new TransactionInMemoryModule()); DatasetDefinitionRegistryFactory registryFactory = new DatasetDefinitionRegistryFactory() { @Override public DatasetDefinitionRegistry create() { DefaultDatasetDefinitionRegistry registry = new DefaultDatasetDefinitionRegistry(); injector.injectMembers(registry); return registry; } }; locationFactory = injector.getInstance(LocationFactory.class); NamespacedLocationFactory namespacedLocationFactory = injector.getInstance(NamespacedLocationFactory.class); dsFramework = new RemoteDatasetFramework(cConf, discoveryService, registryFactory); SystemDatasetInstantiatorFactory datasetInstantiatorFactory = new SystemDatasetInstantiatorFactory(locationFactory, dsFramework, cConf); DatasetAdminService datasetAdminService = new DatasetAdminService(dsFramework, cConf, locationFactory, datasetInstantiatorFactory, new NoOpMetadataStore()); ImmutableSet<HttpHandler> handlers = ImmutableSet.<HttpHandler>of(new DatasetAdminOpHTTPHandler(datasetAdminService)); opExecutorService = new DatasetOpExecutorService(cConf, discoveryService, metricsCollectionService, handlers); opExecutorService.startAndWait(); ImmutableMap<String, DatasetModule> modules = ImmutableMap.<String, DatasetModule>builder() .putAll(injector.getInstance(Key.get(new TypeLiteral<Map<String, DatasetModule>>() { }, Names.named("defaultDatasetModules")))) .putAll(DatasetMetaTableUtil.getModules()) .build(); TransactionExecutorFactory txExecutorFactory = injector.getInstance(TransactionExecutorFactory.class); inMemoryDatasetFramework = new InMemoryDatasetFramework(registryFactory, modules, cConf); MDSDatasetsRegistry mdsDatasetsRegistry = new MDSDatasetsRegistry(txSystemClientService, inMemoryDatasetFramework); ExploreFacade exploreFacade = new ExploreFacade(new DiscoveryExploreClient(cConf, discoveryService), cConf); namespaceStore = new InMemoryNamespaceStore(); namespaceStore.create(NamespaceMeta.DEFAULT); DatasetInstanceService instanceService = new DatasetInstanceService( new DatasetTypeManager(cConf, mdsDatasetsRegistry, locationFactory, // we don't need any default modules in this test Collections.<String, DatasetModule>emptyMap()), new DatasetInstanceManager(mdsDatasetsRegistry), new InMemoryDatasetOpExecutor(dsFramework), exploreFacade, cConf, txExecutorFactory, registryFactory, namespaceStore); service = new DatasetService(cConf, namespacedLocationFactory, discoveryService, discoveryService, new DatasetTypeManager(cConf, mdsDatasetsRegistry, locationFactory, // we don't need any default modules in this test Collections.<String, DatasetModule>emptyMap()), metricsCollectionService, new InMemoryDatasetOpExecutor(dsFramework), mdsDatasetsRegistry, new HashSet<DatasetMetricsReporter>(), instanceService, new LocalStorageProviderNamespaceAdmin(cConf, namespacedLocationFactory, exploreFacade), namespaceStore); // Start dataset service, wait for it to be discoverable service.start(); final CountDownLatch startLatch = new CountDownLatch(1); discoveryService.discover(Constants.Service.DATASET_MANAGER).watchChanges(new ServiceDiscovered.ChangeListener() { @Override public void onChange(ServiceDiscovered serviceDiscovered) { if (!Iterables.isEmpty(serviceDiscovered)) { startLatch.countDown(); } } }, Threads.SAME_THREAD_EXECUTOR); startLatch.await(5, TimeUnit.SECONDS); // this usually happens while creating a namespace, however not doing that in data fabric tests Locations.mkdirsIfNotExists(namespacedLocationFactory.get(Id.Namespace.DEFAULT)); } @After public void after() throws Exception { Services.chainStop(service, opExecutorService, txManager); namespaceStore.delete(Id.Namespace.DEFAULT); Locations.deleteQuietly(locationFactory.create(Id.Namespace.DEFAULT.getId())); } private synchronized int getPort() { int attempts = 0; while (port < 0 && attempts++ < 10) { ServiceDiscovered discovered = discoveryService.discover(Constants.Service.DATASET_MANAGER); if (!discovered.iterator().hasNext()) { Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); continue; } port = discovered.iterator().next().getSocketAddress().getPort(); } return port; } protected URL getUrl(String path) throws MalformedURLException { return getUrl(Id.Namespace.DEFAULT.getId(), path); } protected URL getUrl(String namespace, String path) throws MalformedURLException { return new URL(String.format("http://localhost:%d/%s/namespaces/%s%s", getPort(), Constants.Gateway.API_VERSION_3_TOKEN, namespace, path)); } protected URL getStorageProviderNamespaceAdminUrl(String namespace, String operation) throws MalformedURLException { String resource = String.format("%s/namespaces/%s/data/admin/%s", Constants.Gateway.API_VERSION_3, namespace, operation); return new URL("http://" + "localhost" + ":" + getPort() + resource); } protected Location createModuleJar(Class moduleClass, Location...bundleEmbeddedJars) throws IOException { LocationFactory lf = new LocalLocationFactory(tmpFolder.newFolder()); File[] embeddedJars = new File[bundleEmbeddedJars.length]; for (int i = 0; i < bundleEmbeddedJars.length; i++) { File file = tmpFolder.newFile(); Files.copy(Locations.newInputSupplier(bundleEmbeddedJars[i]), file); embeddedJars[i] = file; } return AppJarHelper.createDeploymentJar(lf, moduleClass, embeddedJars); } protected HttpResponse deployModule(String moduleName, Class moduleClass) throws Exception { return deployModule(Id.DatasetModule.from(Id.Namespace.DEFAULT, moduleName), moduleClass); } protected HttpResponse deployModule(Id.DatasetModule module, Class moduleClass) throws Exception { Location moduleJar = createModuleJar(moduleClass); HttpRequest request = HttpRequest.put(getUrl(module.getNamespaceId(), "/data/modules/" + module.getId())) .addHeader("X-Class-Name", moduleClass.getName()) .withBody(Locations.newInputSupplier(moduleJar)).build(); return HttpRequests.execute(request); } // creates a bundled jar with moduleClass and list of bundleEmbeddedJar files, moduleName and moduleClassName are // used to make request for deploying module. protected int deployModuleBundled(String moduleName, String moduleClassName, Class moduleClass, Location...bundleEmbeddedJars) throws IOException { Location moduleJar = createModuleJar(moduleClass, bundleEmbeddedJars); HttpRequest request = HttpRequest.put(getUrl("/data/modules/" + moduleName)) .addHeader("X-Class-Name", moduleClassName) .withBody(Locations.newInputSupplier(moduleJar)).build(); return HttpRequests.execute(request).getResponseCode(); } protected ObjectResponse<List<DatasetModuleMeta>> getModules() throws IOException { return getModules(Id.Namespace.DEFAULT); } protected ObjectResponse<List<DatasetModuleMeta>> getModules(Id.Namespace namespace) throws IOException { return ObjectResponse.fromJsonBody(makeModulesRequest(namespace), new TypeToken<List<DatasetModuleMeta>>() { }.getType()); } protected HttpResponse makeModulesRequest(Id.Namespace namespaceId) throws IOException { HttpRequest request = HttpRequest.get(getUrl(namespaceId.getId(), "/data/modules")).build(); return HttpRequests.execute(request); } protected HttpResponse deleteModule(String moduleName) throws Exception { return deleteModule(Id.DatasetModule.from(Id.Namespace.DEFAULT, moduleName)); } protected HttpResponse deleteModule(Id.DatasetModule module) throws Exception { return HttpRequests.execute( HttpRequest.delete(getUrl(module.getNamespaceId(), "/data/modules/" + module.getId())).build()); } protected HttpResponse deleteModules() throws IOException { return deleteModules(Id.Namespace.DEFAULT); } protected HttpResponse deleteModules(Id.Namespace namespace) throws IOException { return HttpRequests.execute(HttpRequest.delete(getUrl(namespace.getId(), "/data/modules/")).build()); } protected void assertNamespaceNotFound(HttpResponse response, Id.Namespace namespaceId) { Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getResponseCode()); Assert.assertTrue(response.getResponseBodyAsString().contains(namespaceId.toString())); } }