/* * 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.executor; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.api.dataset.DatasetDefinition; import co.cask.cdap.api.dataset.DatasetProperties; import co.cask.cdap.api.dataset.table.Get; import co.cask.cdap.api.dataset.table.Put; import co.cask.cdap.api.dataset.table.Table; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.discovery.EndpointStrategy; import co.cask.cdap.common.discovery.RandomEndpointStrategy; import co.cask.cdap.common.guice.ConfigModule; import co.cask.cdap.common.guice.DiscoveryRuntimeModule; import co.cask.cdap.common.guice.IOModule; import co.cask.cdap.common.guice.KafkaClientModule; import co.cask.cdap.common.guice.LocationRuntimeModule; import co.cask.cdap.common.guice.ZKClientModule; import co.cask.cdap.common.utils.Networks; import co.cask.cdap.data.runtime.DataFabricModules; import co.cask.cdap.data.runtime.DataSetServiceModules; import co.cask.cdap.data.runtime.DataSetsModules; import co.cask.cdap.data.runtime.TransactionMetricsModule; import co.cask.cdap.data2.datafabric.dataset.service.DatasetService; import co.cask.cdap.data2.dataset2.DatasetFramework; import co.cask.cdap.explore.guice.ExploreClientModule; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.NamespaceMeta; import co.cask.cdap.store.NamespaceStore; import co.cask.cdap.store.guice.NamespaceStoreModule; import co.cask.common.http.HttpRequest; import co.cask.common.http.HttpRequests; import co.cask.common.http.HttpResponse; import co.cask.tephra.DefaultTransactionExecutor; import co.cask.tephra.TransactionAware; import co.cask.tephra.TransactionExecutor; import co.cask.tephra.TransactionManager; import co.cask.tephra.inmemory.InMemoryTxSystemClient; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.hadoop.conf.Configuration; import org.apache.twill.discovery.DiscoveryServiceClient; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.concurrent.TimeUnit; /** * Test for {@link co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetOpExecutorService}. */ public class DatasetOpExecutorServiceTest { private static final Gson GSON = new Gson(); private static final Id.Namespace namespace = Id.Namespace.from("myspace"); private static final Id.DatasetInstance bob = Id.DatasetInstance.from(namespace, "bob"); @ClassRule public static final TemporaryFolder TMP_FOLDER = new TemporaryFolder(); private DatasetService managerService; private DatasetFramework dsFramework; private EndpointStrategy endpointStrategy; private TransactionManager txManager; private NamespaceStore nsStore; @Before public void setUp() throws Exception { Configuration hConf = new Configuration(); CConfiguration cConf = CConfiguration.create(); File datasetDir = new File(TMP_FOLDER.newFolder(), "datasetUser"); Assert.assertTrue(datasetDir.mkdirs()); cConf.set(Constants.Dataset.Manager.OUTPUT_DIR, datasetDir.getAbsolutePath()); cConf.set(Constants.Dataset.Manager.ADDRESS, "localhost"); cConf.set(Constants.Dataset.Executor.ADDRESS, "localhost"); cConf.setInt(Constants.Dataset.Executor.PORT, Networks.getRandomPort()); Injector injector = Guice.createInjector( new ConfigModule(cConf, hConf), new IOModule(), new ZKClientModule(), new KafkaClientModule(), new DiscoveryRuntimeModule().getInMemoryModules(), new LocationRuntimeModule().getInMemoryModules(), new DataFabricModules().getInMemoryModules(), new DataSetsModules().getStandaloneModules(), new DataSetServiceModules().getInMemoryModules(), new TransactionMetricsModule(), new ExploreClientModule(), new NamespaceStoreModule().getInMemoryModules()); txManager = injector.getInstance(TransactionManager.class); txManager.startAndWait(); managerService = injector.getInstance(DatasetService.class); managerService.startAndWait(); dsFramework = injector.getInstance(DatasetFramework.class); // find host DiscoveryServiceClient discoveryClient = injector.getInstance(DiscoveryServiceClient.class); endpointStrategy = new RandomEndpointStrategy(discoveryClient.discover(Constants.Service.DATASET_MANAGER)); nsStore = injector.getInstance(NamespaceStore.class); nsStore.create(NamespaceMeta.DEFAULT); nsStore.create(new NamespaceMeta.Builder().setName(bob.getNamespace()).build()); } @After public void tearDown() throws Exception { dsFramework = null; managerService.stopAndWait(); managerService = null; nsStore.delete(Id.Namespace.DEFAULT); nsStore.delete(bob.getNamespace()); } @Test public void testRest() throws Exception { // check non-existence with 404 testAdminOp(bob, "exists", 404, null); // add instance, should automatically create an instance dsFramework.addInstance("table", bob, DatasetProperties.EMPTY); testAdminOp(bob, "exists", 200, true); testAdminOp("bob", "exists", 404, null); // check truncate final Table table = dsFramework.getDataset(bob, DatasetDefinition.NO_ARGUMENTS, null); Assert.assertNotNull(table); TransactionExecutor txExecutor = new DefaultTransactionExecutor(new InMemoryTxSystemClient(txManager), ImmutableList.of((TransactionAware) table)); // writing smth to table txExecutor.execute(new TransactionExecutor.Subroutine() { @Override public void apply() throws Exception { table.put(new Put("key1", "col1", "val1")); } }); // verify that we can read the data txExecutor.execute(new TransactionExecutor.Subroutine() { @Override public void apply() throws Exception { Assert.assertEquals("val1", table.get(new Get("key1", "col1")).getString("col1")); } }); testAdminOp(bob, "truncate", 200, null); // verify that data is no longer there txExecutor.execute(new TransactionExecutor.Subroutine() { @Override public void apply() throws Exception { Assert.assertTrue(table.get(new Get("key1", "col1")).isEmpty()); } }); // check upgrade testAdminOp(bob, "upgrade", 200, null); // drop and check non-existence dsFramework.deleteInstance(bob); testAdminOp(bob, "exists", 404, null); } @Test public void testUpdate() throws Exception { // check non-existence with 404 testAdminOp(bob, "exists", 404, null); // add instance, should automatically create an instance dsFramework.addInstance("table", bob, DatasetProperties.EMPTY); testAdminOp(bob, "exists", 200, true); dsFramework.updateInstance(bob, DatasetProperties.builder().add("dataset.table.ttl", "10000").build()); // check upgrade testAdminOp(bob, "upgrade", 200, null); // drop and check non-existence dsFramework.deleteInstance(bob); testAdminOp(bob, "exists", 404, null); } private void testAdminOp(String instanceName, String opName, int expectedStatus, Object expectedResult) throws URISyntaxException, IOException { testAdminOp(Id.DatasetInstance.from(Id.Namespace.DEFAULT, instanceName), opName, expectedStatus, expectedResult); } private void testAdminOp(Id.DatasetInstance datasetInstanceId, String opName, int expectedStatus, Object expectedResult) throws URISyntaxException, IOException { String path = String.format("/namespaces/%s/data/datasets/%s/admin/%s", datasetInstanceId.getNamespaceId(), datasetInstanceId.getId(), opName); URL targetUrl = resolve(path); HttpResponse response = HttpRequests.execute(HttpRequest.post(targetUrl).build()); DatasetAdminOpResponse body = getResponse(response.getResponseBody()); Assert.assertEquals(expectedStatus, response.getResponseCode()); Assert.assertEquals(expectedResult, body.getResult()); } private URL resolve(String path) throws URISyntaxException, MalformedURLException { @SuppressWarnings("ConstantConditions") InetSocketAddress socketAddress = endpointStrategy.pick(1, TimeUnit.SECONDS).getSocketAddress(); return new URL(String.format("http://%s:%d%s%s", socketAddress.getHostName(), socketAddress.getPort(), Constants.Gateway.API_VERSION_3, path)); } private DatasetAdminOpResponse getResponse(byte[] body) { return Objects.firstNonNull(GSON.fromJson(Bytes.toString(body), DatasetAdminOpResponse.class), new DatasetAdminOpResponse(null, null)); } }