/** * 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.hive.hcatalog.api.repl.exim; import com.google.common.base.Function; import com.google.common.collect.Lists; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.api.Database; import org.apache.hadoop.hive.metastore.api.FieldSchema; import org.apache.hadoop.hive.metastore.api.NotificationEvent; import org.apache.hadoop.hive.metastore.api.Partition; import org.apache.hadoop.hive.metastore.api.Table; import org.apache.hive.hcatalog.api.HCatClient; import org.apache.hive.hcatalog.api.HCatNotificationEvent; import org.apache.hive.hcatalog.api.TestHCatClient; import org.apache.hive.hcatalog.api.repl.Command; import org.apache.hive.hcatalog.api.repl.CommandTestUtils; import org.apache.hive.hcatalog.api.repl.NoopReplicationTask; import org.apache.hive.hcatalog.api.repl.ReplicationTask; import org.apache.hive.hcatalog.api.repl.ReplicationUtils; import org.apache.hive.hcatalog.api.repl.StagingDirectoryProvider; import org.apache.hive.hcatalog.api.repl.commands.DropDatabaseCommand; import org.apache.hive.hcatalog.api.repl.commands.DropPartitionCommand; import org.apache.hive.hcatalog.api.repl.commands.DropTableCommand; import org.apache.hive.hcatalog.api.repl.commands.ExportCommand; import org.apache.hive.hcatalog.api.repl.commands.ImportCommand; import org.apache.hive.hcatalog.api.repl.commands.NoopCommand; import org.apache.hive.hcatalog.common.HCatConstants; import org.apache.hive.hcatalog.common.HCatException; import org.apache.hive.hcatalog.data.schema.HCatSchemaUtils; import org.apache.hive.hcatalog.messaging.MessageFactory; import org.junit.BeforeClass; import org.junit.Test; import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class TestEximReplicationTasks{ private static MessageFactory msgFactory = MessageFactory.getInstance(); private static StagingDirectoryProvider stagingDirectoryProvider = new StagingDirectoryProvider.TrivialImpl("/tmp","/"); private static HCatClient client; @BeforeClass public static void setUpBeforeClass() throws HCatException { client = HCatClient.create(new HiveConf()); ReplicationTask.resetFactory(EximReplicationTaskFactory.class); } // Dummy mapping used for all db and table name mappings static Function<String,String> debugMapping = new Function<String,String>(){ @Nullable @Override public String apply(@Nullable String s) { if (s == null){ return null; } else { StringBuilder sb = new StringBuilder(s); return sb.toString() + sb.reverse().toString(); } } }; @Test public void testDebugMapper(){ assertEquals("BlahhalB",debugMapping.apply("Blah")); assertEquals(null, debugMapping.apply(null)); assertEquals("", debugMapping.apply("")); } @Test public void testCreateDb(){ Database db = new Database(); db.setName("testdb"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_CREATE_DATABASE_EVENT, msgFactory.buildCreateDatabaseMessage(db).toString()); event.setDbName(db.getName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyCreateDbReplicationTask(rtask); // CREATE DB currently replicated as Noop. } private static void verifyCreateDbReplicationTask(ReplicationTask rtask) { assertEquals(CreateDatabaseReplicationTask.class, rtask.getClass()); assertTrue("CreateDatabaseReplicationTask should be a noop", rtask instanceof NoopReplicationTask); assertEquals(false, rtask.needsStagingDirs()); assertEquals(true, rtask.isActionable()); for (Command c : rtask.getSrcWhCommands()){ assertEquals(NoopCommand.class,c.getClass()); } for (Command c : rtask.getDstWhCommands()){ assertEquals(NoopCommand.class,c.getClass()); } } @Test public void testDropDb() throws IOException { Database db = new Database(); db.setName("testdb"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_DROP_DATABASE_EVENT, msgFactory.buildCreateDatabaseMessage(db).toString()); event.setDbName(db.getName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyDropDbReplicationTask(rtask); } private static void verifyDropDbReplicationTask(ReplicationTask rtask) throws IOException { assertEquals(DropDatabaseReplicationTask.class, rtask.getClass()); assertEquals(false, rtask.needsStagingDirs()); assertEquals(true, rtask.isActionable()); rtask .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); for (Command c : rtask.getSrcWhCommands()){ assertEquals(NoopCommand.class, c.getClass()); } List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(DropDatabaseCommand.class, dstCommands.get(0).getClass()); DropDatabaseCommand dropDatabaseCommand = new DropDatabaseCommand( debugMapping.apply(rtask.getEvent().getDbName()), rtask.getEvent().getEventId()); assertEquals(ReplicationUtils.serializeCommand(dropDatabaseCommand), ReplicationUtils.serializeCommand(dstCommands.get(0))); } @Test public void testCreateTable() throws IOException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_CREATE_TABLE_EVENT, msgFactory.buildCreateTableMessage(t).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyCreateTableReplicationTask(rtask); } private static void verifyCreateTableReplicationTask(ReplicationTask rtask) throws IOException { assertEquals(CreateTableReplicationTask.class, rtask.getClass()); assertEquals(true, rtask.needsStagingDirs()); assertEquals(false, rtask.isActionable()); rtask .withSrcStagingDirProvider(stagingDirectoryProvider) .withDstStagingDirProvider(stagingDirectoryProvider) .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true, rtask.isActionable()); List<? extends Command> srcCommands = Lists.newArrayList(rtask.getSrcWhCommands()); assertEquals(1,srcCommands.size()); assertEquals(ExportCommand.class, srcCommands.get(0).getClass()); ExportCommand exportCommand = getExpectedExportCommand(rtask, null,false); assertEquals(ReplicationUtils.serializeCommand(exportCommand), ReplicationUtils.serializeCommand(srcCommands.get(0))); List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(ImportCommand.class, dstCommands.get(0).getClass()); ImportCommand importCommand = getExpectedImportCommand(rtask,null,false); assertEquals(ReplicationUtils.serializeCommand(importCommand), ReplicationUtils.serializeCommand(dstCommands.get(0))); } @Test public void testDropTable() throws IOException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_DROP_TABLE_EVENT, msgFactory.buildDropTableMessage(t).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyDropTableReplicationTask(rtask); } private static void verifyDropTableReplicationTask(ReplicationTask rtask) throws IOException { assertEquals(DropTableReplicationTask.class, rtask.getClass()); assertEquals(false, rtask.needsStagingDirs()); assertEquals(true, rtask.isActionable()); rtask .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); for (Command c : rtask.getSrcWhCommands()){ assertEquals(NoopCommand.class, c.getClass()); } List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(DropTableCommand.class, dstCommands.get(0).getClass()); DropTableCommand dropTableCommand = new DropTableCommand( debugMapping.apply(rtask.getEvent().getDbName()), debugMapping.apply(rtask.getEvent().getTableName()), true, rtask.getEvent().getEventId()); assertEquals(ReplicationUtils.serializeCommand(dropTableCommand), ReplicationUtils.serializeCommand(dstCommands.get(0))); } @Test public void testAlterTable() throws IOException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_ALTER_TABLE_EVENT, msgFactory.buildAlterTableMessage(t, t).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyAlterTableReplicationTask(rtask); } private static void verifyAlterTableReplicationTask(ReplicationTask rtask) throws IOException { assertEquals(AlterTableReplicationTask.class, rtask.getClass()); assertEquals(true, rtask.needsStagingDirs()); assertEquals(false, rtask.isActionable()); rtask .withSrcStagingDirProvider(stagingDirectoryProvider) .withDstStagingDirProvider(stagingDirectoryProvider) .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true, rtask.isActionable()); List<? extends Command> srcCommands = Lists.newArrayList(rtask.getSrcWhCommands()); assertEquals(1, srcCommands.size()); assertEquals(ExportCommand.class, srcCommands.get(0).getClass()); ExportCommand exportCommand = getExpectedExportCommand(rtask, null, true); assertEquals(ReplicationUtils.serializeCommand(exportCommand), ReplicationUtils.serializeCommand(srcCommands.get(0))); List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(ImportCommand.class, dstCommands.get(0).getClass()); ImportCommand importCommand = getExpectedImportCommand(rtask, null, true); assertEquals(ReplicationUtils.serializeCommand(importCommand), ReplicationUtils.serializeCommand(dstCommands.get(0))); } @Test public void testAddPartition() throws IOException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); List<FieldSchema> pkeys = HCatSchemaUtils.getFieldSchemas( HCatSchemaUtils.getHCatSchema("a:int,b:string").getFields()); t.setPartitionKeys(pkeys); List<Partition> addedPtns = new ArrayList<Partition>(); addedPtns.add(createPtn(t, Arrays.asList("120", "abc"))); addedPtns.add(createPtn(t, Arrays.asList("201", "xyz"))); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_ADD_PARTITION_EVENT, msgFactory.buildAddPartitionMessage(t, addedPtns.iterator()).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyAddPartitionReplicationTask(rtask, t, addedPtns); } private static void verifyAddPartitionReplicationTask(ReplicationTask rtask, Table table, List<Partition> addedPtns) throws IOException { assertEquals(AddPartitionReplicationTask.class, rtask.getClass()); assertEquals(true, rtask.needsStagingDirs()); assertEquals(false,rtask.isActionable()); rtask .withSrcStagingDirProvider(stagingDirectoryProvider) .withDstStagingDirProvider(stagingDirectoryProvider) .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true,rtask.isActionable()); List<? extends Command> srcCommands = Lists.newArrayList(rtask.getSrcWhCommands()); assertEquals(2,srcCommands.size()); assertEquals(ExportCommand.class, srcCommands.get(0).getClass()); assertEquals(ExportCommand.class, srcCommands.get(1).getClass()); ExportCommand exportCommand1 = getExpectedExportCommand(rtask, getPtnDesc(table,addedPtns.get(0)), false); ExportCommand exportCommand2 = getExpectedExportCommand(rtask, getPtnDesc(table,addedPtns.get(1)), false); CommandTestUtils.compareCommands(exportCommand1, srcCommands.get(0), true); CommandTestUtils.compareCommands(exportCommand2, srcCommands.get(1), true); List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(2,dstCommands.size()); assertEquals(ImportCommand.class, dstCommands.get(0).getClass()); assertEquals(ImportCommand.class, dstCommands.get(1).getClass()); ImportCommand importCommand1 = getExpectedImportCommand(rtask, getPtnDesc(table,addedPtns.get(0)), false); ImportCommand importCommand2 = getExpectedImportCommand(rtask, getPtnDesc(table,addedPtns.get(1)), false); CommandTestUtils.compareCommands(importCommand1, dstCommands.get(0), true); CommandTestUtils.compareCommands(importCommand2, dstCommands.get(1), true); } private static Map<String, String> getPtnDesc(Table t, Partition p) { assertEquals(t.getPartitionKeysSize(),p.getValuesSize()); Map<String,String> retval = new HashMap<String,String>(); Iterator<String> pval = p.getValuesIterator(); for (FieldSchema fs : t.getPartitionKeys()){ retval.put(fs.getName(),pval.next()); } return retval; } private static Partition createPtn(Table t, List<String> pvals) { Partition ptn = new Partition(); ptn.setDbName(t.getDbName()); ptn.setTableName(t.getTableName()); ptn.setValues(pvals); return ptn; } @Test public void testDropPartition() throws HCatException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); List<FieldSchema> pkeys = HCatSchemaUtils.getFieldSchemas( HCatSchemaUtils.getHCatSchema("a:int,b:string").getFields()); t.setPartitionKeys(pkeys); Partition p = createPtn(t, Arrays.asList("102", "lmn")); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_DROP_PARTITION_EVENT, msgFactory.buildDropPartitionMessage( t, Collections.singletonList(p).iterator()).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyDropPartitionReplicationTask(rtask, t, p); } private static void verifyDropPartitionReplicationTask(ReplicationTask rtask, Table table, Partition ptn) { assertEquals(DropPartitionReplicationTask.class, rtask.getClass()); assertEquals(false, rtask.needsStagingDirs()); assertEquals(true, rtask.isActionable()); rtask .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true,rtask.isActionable()); for (Command c : rtask.getSrcWhCommands()){ assertEquals(NoopCommand.class, c.getClass()); } List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(DropPartitionCommand.class, dstCommands.get(0).getClass()); DropPartitionCommand dropPartitionCommand = new DropPartitionCommand( debugMapping.apply(rtask.getEvent().getDbName()), debugMapping.apply(rtask.getEvent().getTableName()), getPtnDesc(table,ptn), true, rtask.getEvent().getEventId() ); CommandTestUtils.compareCommands(dropPartitionCommand, dstCommands.get(0), true); } @Test public void testAlterPartition() throws HCatException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); List<FieldSchema> pkeys = HCatSchemaUtils.getFieldSchemas( HCatSchemaUtils.getHCatSchema("a:int,b:string").getFields()); t.setPartitionKeys(pkeys); Partition p = createPtn(t, Arrays.asList("102", "lmn")); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_ALTER_PARTITION_EVENT, msgFactory.buildAlterPartitionMessage(t,p,p).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyAlterPartitionReplicationTask(rtask, t, p); } private static void verifyAlterPartitionReplicationTask(ReplicationTask rtask, Table table, Partition ptn) { assertEquals(AlterPartitionReplicationTask.class, rtask.getClass()); assertEquals(true, rtask.needsStagingDirs()); assertEquals(false,rtask.isActionable()); rtask .withSrcStagingDirProvider(stagingDirectoryProvider) .withDstStagingDirProvider(stagingDirectoryProvider) .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true,rtask.isActionable()); List<? extends Command> srcCommands = Lists.newArrayList(rtask.getSrcWhCommands()); assertEquals(1,srcCommands.size()); assertEquals(ExportCommand.class, srcCommands.get(0).getClass()); ExportCommand exportCommand = getExpectedExportCommand(rtask, getPtnDesc(table, ptn), true); CommandTestUtils.compareCommands(exportCommand, srcCommands.get(0), true); List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(ImportCommand.class, dstCommands.get(0).getClass()); ImportCommand importCommand = getExpectedImportCommand(rtask, getPtnDesc(table, ptn), true); CommandTestUtils.compareCommands(importCommand, dstCommands.get(0), true); } @Test public void testInsert() throws HCatException { Table t = new Table(); t.setDbName("testdb"); t.setTableName("testtable"); List<FieldSchema> pkeys = HCatSchemaUtils.getFieldSchemas( HCatSchemaUtils.getHCatSchema("a:int,b:string").getFields()); t.setPartitionKeys(pkeys); Partition p = createPtn(t, Arrays.asList("102", "lmn")); List<String> files = Arrays.asList("/tmp/test123"); NotificationEvent event = new NotificationEvent(getEventId(), getTime(), HCatConstants.HCAT_INSERT_EVENT, msgFactory.buildInsertMessage( t.getDbName(), t.getTableName(), getPtnDesc(t,p), files ).toString()); event.setDbName(t.getDbName()); event.setTableName(t.getTableName()); HCatNotificationEvent hev = new HCatNotificationEvent(event); ReplicationTask rtask = ReplicationTask.create(client,hev); assertEquals(hev.toString(), rtask.getEvent().toString()); verifyInsertReplicationTask(rtask, t, p); } private static void verifyInsertReplicationTask(ReplicationTask rtask, Table table, Partition ptn) { assertEquals(InsertReplicationTask.class, rtask.getClass()); assertEquals(true, rtask.needsStagingDirs()); assertEquals(false,rtask.isActionable()); rtask .withSrcStagingDirProvider(stagingDirectoryProvider) .withDstStagingDirProvider(stagingDirectoryProvider) .withDbNameMapping(debugMapping) .withTableNameMapping(debugMapping); assertEquals(true,rtask.isActionable()); List<? extends Command> srcCommands = Lists.newArrayList(rtask.getSrcWhCommands()); assertEquals(1,srcCommands.size()); assertEquals(ExportCommand.class, srcCommands.get(0).getClass()); ExportCommand exportCommand = getExpectedExportCommand(rtask, getPtnDesc(table, ptn), false); CommandTestUtils.compareCommands(exportCommand, srcCommands.get(0), true); List<? extends Command> dstCommands = Lists.newArrayList(rtask.getDstWhCommands()); assertEquals(1,dstCommands.size()); assertEquals(ImportCommand.class, dstCommands.get(0).getClass()); ImportCommand importCommand = getExpectedImportCommand(rtask, getPtnDesc(table, ptn), false); CommandTestUtils.compareCommands(importCommand, dstCommands.get(0), true); } private static long getEventId() { // Does not need to be unique, just non-zero distinct value to test against. return 42; } private static int getTime() { // Does not need to be actual time, just non-zero distinct value to test against. return 1729; } private static ImportCommand getExpectedImportCommand(ReplicationTask rtask, Map<String,String> ptnDesc, boolean isMetadataOnly) { String dbName = rtask.getEvent().getDbName(); String tableName = rtask.getEvent().getTableName(); return new ImportCommand( ReplicationUtils.mapIfMapAvailable(dbName, debugMapping), ReplicationUtils.mapIfMapAvailable(tableName, debugMapping), ptnDesc, stagingDirectoryProvider.getStagingDirectory( ReplicationUtils.getUniqueKey( rtask.getEvent().getEventId(), dbName, tableName, ptnDesc) ), isMetadataOnly, rtask.getEvent().getEventId() ); } private static ExportCommand getExpectedExportCommand(ReplicationTask rtask, Map<String, String> ptnDesc, boolean isMetadataOnly) { String dbName = rtask.getEvent().getDbName(); String tableName = rtask.getEvent().getTableName(); return new ExportCommand( dbName, tableName, ptnDesc, stagingDirectoryProvider.getStagingDirectory( ReplicationUtils.getUniqueKey( rtask.getEvent().getEventId(), dbName, tableName, ptnDesc) ), isMetadataOnly, rtask.getEvent().getEventId() ); } }