/* * 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 com.facebook.presto.transaction; import com.facebook.presto.connector.ConnectorId; import com.facebook.presto.connector.informationSchema.InformationSchemaConnector; import com.facebook.presto.connector.system.SystemConnector; import com.facebook.presto.metadata.Catalog; import com.facebook.presto.metadata.CatalogManager; import com.facebook.presto.metadata.InMemoryNodeManager; import com.facebook.presto.metadata.InternalNodeManager; import com.facebook.presto.metadata.MetadataManager; import com.facebook.presto.security.AllowAllAccessControl; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.connector.Connector; import com.facebook.presto.spi.connector.ConnectorMetadata; import com.facebook.presto.testing.TestingConnectorContext; import com.facebook.presto.tpch.TpchConnectorFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import java.io.Closeable; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static com.facebook.presto.SessionTestUtils.TEST_SESSION; import static com.facebook.presto.connector.ConnectorId.createInformationSchemaConnectorId; import static com.facebook.presto.connector.ConnectorId.createSystemTablesConnectorId; import static com.facebook.presto.spi.StandardErrorCode.TRANSACTION_ALREADY_ABORTED; import static io.airlift.concurrent.MoreFutures.getFutureValue; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; public class TestTransactionManager { private static final String CATALOG_NAME = "test_catalog"; private static final ConnectorId CONNECTOR_ID = new ConnectorId(CATALOG_NAME); private static final ConnectorId SYSTEM_TABLES_ID = createSystemTablesConnectorId(CONNECTOR_ID); private static final ConnectorId INFORMATION_SCHEMA_ID = createInformationSchemaConnectorId(CONNECTOR_ID); private final ExecutorService finishingExecutor = newCachedThreadPool(daemonThreadsNamed("transaction-%s")); @AfterClass public void tearDown() throws Exception { finishingExecutor.shutdownNow(); } @Test public void testTransactionWorkflow() throws Exception { try (IdleCheckExecutor executor = new IdleCheckExecutor()) { CatalogManager catalogManager = new CatalogManager(); TransactionManager transactionManager = TransactionManager.create(new TransactionManagerConfig(), executor.getExecutor(), catalogManager, finishingExecutor); Connector c1 = new TpchConnectorFactory().create(CATALOG_NAME, ImmutableMap.of(), new TestingConnectorContext()); registerConnector(catalogManager, transactionManager, CATALOG_NAME, CONNECTOR_ID, c1); TransactionId transactionId = transactionManager.beginTransaction(false); assertEquals(transactionManager.getAllTransactionInfos().size(), 1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); assertFalse(transactionInfo.isAutoCommitContext()); assertTrue(transactionInfo.getConnectorIds().isEmpty()); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, CATALOG_NAME).get().getMetadata(); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(CONNECTOR_ID)); transactionInfo = transactionManager.getTransactionInfo(transactionId); assertEquals(transactionInfo.getConnectorIds(), ImmutableList.of(CONNECTOR_ID, INFORMATION_SCHEMA_ID, SYSTEM_TABLES_ID)); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); getFutureValue(transactionManager.asyncCommit(transactionId)); assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); } } @Test public void testAbortedTransactionWorkflow() throws Exception { try (IdleCheckExecutor executor = new IdleCheckExecutor()) { CatalogManager catalogManager = new CatalogManager(); TransactionManager transactionManager = TransactionManager.create(new TransactionManagerConfig(), executor.getExecutor(), catalogManager, finishingExecutor); Connector c1 = new TpchConnectorFactory().create(CATALOG_NAME, ImmutableMap.of(), new TestingConnectorContext()); registerConnector(catalogManager, transactionManager, CATALOG_NAME, CONNECTOR_ID, c1); TransactionId transactionId = transactionManager.beginTransaction(false); assertEquals(transactionManager.getAllTransactionInfos().size(), 1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); assertFalse(transactionInfo.isAutoCommitContext()); assertTrue(transactionInfo.getConnectorIds().isEmpty()); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, CATALOG_NAME).get().getMetadata(); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(CONNECTOR_ID)); transactionInfo = transactionManager.getTransactionInfo(transactionId); assertEquals(transactionInfo.getConnectorIds(), ImmutableList.of(CONNECTOR_ID, INFORMATION_SCHEMA_ID, SYSTEM_TABLES_ID)); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); getFutureValue(transactionManager.asyncAbort(transactionId)); assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); } } @Test public void testFailedTransactionWorkflow() throws Exception { try (IdleCheckExecutor executor = new IdleCheckExecutor()) { CatalogManager catalogManager = new CatalogManager(); TransactionManager transactionManager = TransactionManager.create(new TransactionManagerConfig(), executor.getExecutor(), catalogManager, finishingExecutor); Connector c1 = new TpchConnectorFactory().create(CATALOG_NAME, ImmutableMap.of(), new TestingConnectorContext()); registerConnector(catalogManager, transactionManager, CATALOG_NAME, CONNECTOR_ID, c1); TransactionId transactionId = transactionManager.beginTransaction(false); assertEquals(transactionManager.getAllTransactionInfos().size(), 1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); assertFalse(transactionInfo.isAutoCommitContext()); assertTrue(transactionInfo.getConnectorIds().isEmpty()); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, CATALOG_NAME).get().getMetadata(); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(CONNECTOR_ID)); transactionInfo = transactionManager.getTransactionInfo(transactionId); assertEquals(transactionInfo.getConnectorIds(), ImmutableList.of(CONNECTOR_ID, INFORMATION_SCHEMA_ID, SYSTEM_TABLES_ID)); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); transactionManager.fail(transactionId); assertEquals(transactionManager.getAllTransactionInfos().size(), 1); try { transactionManager.getCatalogMetadata(transactionId, CONNECTOR_ID); fail(); } catch (PrestoException e) { assertEquals(e.getErrorCode(), TRANSACTION_ALREADY_ABORTED.toErrorCode()); } assertEquals(transactionManager.getAllTransactionInfos().size(), 1); getFutureValue(transactionManager.asyncAbort(transactionId)); assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); } } @Test public void testExpiration() throws Exception { try (IdleCheckExecutor executor = new IdleCheckExecutor()) { TransactionManager transactionManager = TransactionManager.create( new TransactionManagerConfig() .setIdleTimeout(new Duration(1, TimeUnit.MILLISECONDS)) .setIdleCheckInterval(new Duration(5, TimeUnit.MILLISECONDS)), executor.getExecutor(), new CatalogManager(), finishingExecutor); TransactionId transactionId = transactionManager.beginTransaction(false); assertEquals(transactionManager.getAllTransactionInfos().size(), 1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); assertFalse(transactionInfo.isAutoCommitContext()); assertTrue(transactionInfo.getConnectorIds().isEmpty()); assertFalse(transactionInfo.getWrittenConnectorId().isPresent()); transactionManager.trySetInactive(transactionId); TimeUnit.MILLISECONDS.sleep(100); assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); } } private static void registerConnector( CatalogManager catalogManager, TransactionManager transactionManager, String catalogName, ConnectorId connectorId, Connector connector) { ConnectorId systemId = createSystemTablesConnectorId(connectorId); InternalNodeManager nodeManager = new InMemoryNodeManager(); MetadataManager metadata = MetadataManager.createTestMetadataManager(catalogManager); catalogManager.registerCatalog(new Catalog( catalogName, connectorId, connector, createInformationSchemaConnectorId(connectorId), new InformationSchemaConnector(catalogName, nodeManager, metadata, new AllowAllAccessControl()), systemId, new SystemConnector( systemId, nodeManager, connector.getSystemTables(), transactionId -> transactionManager.getConnectorTransaction(transactionId, connectorId)))); } private static class IdleCheckExecutor implements Closeable { private final ScheduledExecutorService executorService = newSingleThreadScheduledExecutor(daemonThreadsNamed("idle-check")); public ScheduledExecutorService getExecutor() { return executorService; } @Override public void close() throws IOException { executorService.shutdownNow(); } } }