diff --git common/src/java/org/apache/hadoop/hive/conf/HiveConf.java common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 53b9b0c6962c9b1cd2eef1cb71687ec0245cfac3..f6aa56b1defffbac77cd13eece95ef076ed38809 100644 --- common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -299,7 +299,8 @@ private static URL checkConfigFile(File f) { HiveConf.ConfVars.METASTORE_HBASE_AGGR_STATS_MEMORY_TTL, HiveConf.ConfVars.METASTORE_HBASE_AGGR_STATS_INVALIDATOR_FREQUENCY, HiveConf.ConfVars.METASTORE_HBASE_AGGR_STATS_HBASE_TTL, - HiveConf.ConfVars.METASTORE_HBASE_FILE_METADATA_THREADS + HiveConf.ConfVars.METASTORE_HBASE_FILE_METADATA_THREADS, + HiveConf.ConfVars.METASTORE_DROPTABLE_STRICT }; /** @@ -940,7 +941,10 @@ private static void populateLlapDaemonVarsSet(Set llapDaemonVarsSetLocal METASTORE_METRICS("hive.metastore.metrics.enabled", false, "Enable metrics on the metastore."), METASTORE_INIT_METADATA_COUNT_ENABLED("hive.metastore.initial.metadata.count.enabled", true, "Enable a metadata count at metastore startup for metrics."), - + METASTORE_DROPTABLE_STRICT("hive.metastore.droptable.strict", false, + "If false: errors during droptable command are swallowed (chance of data staying on disk without" + + " corresponding table in HMS)\n If true: errors are thrown back to HMS client, droptable" + + " transactions are rolled back"), // Metastore SSL settings HIVE_METASTORE_USE_SSL("hive.metastore.use.SSL", false, "Set this to true for using SSL encryption in HMS server."), diff --git itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestHiveMetaStore.java itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestHiveMetaStore.java index af125c38236582ba532f5e3de3d2ba724f38b101..772ce29b3e0829214b8e8c01689c9f32510855e2 100644 --- itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestHiveMetaStore.java +++ itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestHiveMetaStore.java @@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hive.common.FileUtils; import org.apache.hadoop.hive.conf.HiveConf; @@ -1170,6 +1171,138 @@ public void testDatabaseLocation() throws Throwable { } } + public void testDropTableFail() throws Exception { + client.conf.setBoolVar(HiveConf.ConfVars.METASTORE_DROPTABLE_STRICT, true); + String dbName = "faildb"; + String tblName = "failtbl"; + Table table = setupTestDropTableFail(dbName, tblName, false); + + Path tablePath = new Path(table.getSd().getLocation()); + + boolean exceptionThrown = false; + try { + warehouse.getFs(tablePath).setPermission(tablePath.getParent(), + new FsPermission(FsAction.READ_EXECUTE, FsAction.NONE, FsAction.NONE)); + client.dropTable(dbName, tblName); + } catch (MetaException e) { + exceptionThrown = true; + } finally { + warehouse.getFs(tablePath).setPermission(tablePath.getParent(), + new FsPermission(FsAction.ALL, FsAction.NONE, FsAction.NONE)); + silentDropDatabase(dbName); + client.conf.unset(ConfVars.METASTORE_DROPTABLE_STRICT.varname); + } + assertTrue("Expected MetaException", exceptionThrown); + } + + public void testDropTableFailPartitionsAreKept() throws Exception { + client.conf.setBoolVar(HiveConf.ConfVars.METASTORE_DROPTABLE_STRICT, true); + String dbName = "faildb"; + String tblName = "failtbl"; + Table table = setupTestDropTableFail(dbName, tblName, true); + + Path tablePath = new Path(table.getSd().getLocation()); + + boolean exceptionThrown = false; + List partNames = null; + try { + warehouse.getFs(tablePath).setPermission(tablePath.getParent(), + new FsPermission(FsAction.READ_EXECUTE, FsAction.NONE, FsAction.NONE)); + client.dropTable(dbName, tblName, true, false, true); + } catch (MetaException e) { + exceptionThrown = true; + } finally { + warehouse.getFs(tablePath).setPermission(tablePath.getParent(), + new FsPermission(FsAction.ALL, FsAction.NONE, FsAction.NONE)); + partNames = client.listPartitionNames(dbName, tblName, (short) 10); + silentDropDatabase(dbName); + client.conf.unset(ConfVars.METASTORE_DROPTABLE_STRICT.varname); + } + assertTrue("Expected MetaException", exceptionThrown); + assertTrue(partNames.contains("pc1=p1")); + } + + public void testCanDropCorruptedTable() throws Exception { + client.conf.setBoolVar(HiveConf.ConfVars.METASTORE_DROPTABLE_STRICT, true); + doCanDropCorruptedTableTest(); + client.conf.unset(ConfVars.METASTORE_DROPTABLE_STRICT.varname); + doCanDropCorruptedTableTest(); + } + + private void doCanDropCorruptedTableTest() throws Exception { + String dbName = "faildb"; + String tblName = "failtbl"; + + Table table = setupTestDropTableFail(dbName, tblName, true); + + Path tablePath = new Path(table.getSd().getLocation()); + + String tmpDir = System.getProperty("java.io.tmpdir") + Path.SEPARATOR + "parts" + Path.SEPARATOR; + String part2loc = tmpDir + "part2"; + String part3loc = tmpDir + "part3"; + add_partition(client, table, Lists.newArrayList("p2ext"), part2loc, false); + add_partition(client, table, Lists.newArrayList("p3ext"), part3loc, false); + + List partNames = client.listPartitionNames(dbName, tblName, (short)10); + + //Removing partition part3 by hand + warehouse.deleteDir(new Path(part3loc),true); + + //Dropping table should clean up, remove all remaining partitions and table dir + client.dropTable(dbName, tblName); + + //Table dir is empty + Assert.assertTrue(warehouse.isEmpty(new Path(client.getDatabase(dbName).getLocationUri()))); + //Partitions dir is empty + Assert.assertTrue(warehouse.isEmpty(new Path(tmpDir))); + silentDropDatabase(dbName); + } + + private Table setupTestDropTableFail(String dbName, String tblName, boolean isParted) throws + Exception { + silentDropDatabase(dbName); + + Database db = new Database(); + db.setName(dbName); + client.createDatabase(db); + + ArrayList cols = new ArrayList(1); + cols.add(new FieldSchema("col1", serdeConstants.STRING_TYPE_NAME, "")); + + Table tbl = new Table(); + tbl.setDbName(dbName); + tbl.setTableName(tblName); + StorageDescriptor sd = new StorageDescriptor(); + tbl.setSd(sd); + sd.setCols(cols); + sd.setCompressed(false); + sd.setNumBuckets(1); + sd.setParameters(new HashMap()); + sd.setBucketCols(new ArrayList()); + sd.setSerdeInfo(new SerDeInfo()); + sd.getSerdeInfo().setName(tbl.getTableName()); + sd.getSerdeInfo().setParameters(new HashMap()); + sd.getSerdeInfo().getParameters() + .put(serdeConstants.SERIALIZATION_FORMAT, "1"); + sd.getSerdeInfo().setSerializationLib(LazySimpleSerDe.class.getName()); + sd.setInputFormat(HiveInputFormat.class.getName()); + sd.setOutputFormat(HiveOutputFormat.class.getName()); + sd.setSortCols(new ArrayList()); + + if (isParted) { + ArrayList partCols = new ArrayList(3); + partCols.add(new FieldSchema("pc1", serdeConstants.STRING_TYPE_NAME, "")); + tbl.setPartitionKeys(partCols); + } + + client.createTable(tbl); + + if (isParted) { + add_partition(client, tbl, Lists.newArrayList("p1"), "part1"); + } + + return client.getTable(dbName, tblName); + } public void testSimpleTypeApi() throws Exception { try { @@ -2407,7 +2540,7 @@ private void checkFilter(HiveMetaStoreClient client, String dbName, } private void add_partition(HiveMetaStoreClient client, Table table, - List vals, String location) throws InvalidObjectException, + List vals, String location, boolean asSubDirectory) throws InvalidObjectException, AlreadyExistsException, MetaException, TException { Partition part = new Partition(); @@ -2417,11 +2550,21 @@ private void add_partition(HiveMetaStoreClient client, Table table, part.setParameters(new HashMap()); part.setSd(table.getSd().deepCopy()); part.getSd().setSerdeInfo(table.getSd().getSerdeInfo()); - part.getSd().setLocation(table.getSd().getLocation() + location); + if (asSubDirectory) { + part.getSd().setLocation(table.getSd().getLocation() + location); + } else { + part.getSd().setLocation(location); + } client.add_partition(part); } + private void add_partition(HiveMetaStoreClient client, Table table, + List vals, String location) throws InvalidObjectException, + AlreadyExistsException, MetaException, TException { + add_partition(client, table, vals, location, true); + } + /** * Tests {@link HiveMetaStoreClient#newSynchronizedClient}. Does not * actually test multithreading, but does verify that the proxy diff --git metastore/src/java/org/apache/hadoop/hive/metastore/HiveMetaStore.java metastore/src/java/org/apache/hadoop/hive/metastore/HiveMetaStore.java index f8c3c4e48db0df9d6c18801bcd61f9e5dc6eb7c2..2a60871e73655d22e39b8a6e4988a9d41f9a2045 100644 --- metastore/src/java/org/apache/hadoop/hive/metastore/HiveMetaStore.java +++ metastore/src/java/org/apache/hadoop/hive/metastore/HiveMetaStore.java @@ -1675,6 +1675,13 @@ private boolean drop_table_core(final RawStore ms, final String dbname, final St throw new MetaException(indexName == null ? "Unable to drop table " + tableName: "Unable to drop index table " + tableName + " for index " + indexName); } else { + if (deleteData && !isExternal) { + // Data needs deletion. Check if trash may be skipped. + // Delete the data in the partitions which have other locations + deletePartitionData(partPaths, ifPurge); + // Delete the data in the table + deleteTableData(tblPath, ifPurge); + } if (transactionalListeners.size() > 0) { DropTableEvent dropTableEvent = new DropTableEvent(tbl, true, deleteData, this); dropTableEvent.setEnvironmentContext(envContext); @@ -1687,13 +1694,6 @@ private boolean drop_table_core(final RawStore ms, final String dbname, final St } finally { if (!success) { ms.rollbackTransaction(); - } else if (deleteData && !isExternal) { - // Data needs deletion. Check if trash may be skipped. - // Delete the data in the partitions which have other locations - deletePartitionData(partPaths, ifPurge); - // Delete the data in the table - deleteTableData(tblPath, ifPurge); - // ok even if the data is not deleted } for (MetaStoreEventListener listener : listeners) { DropTableEvent dropTableEvent = new DropTableEvent(tbl, success, deleteData, this); @@ -1748,8 +1748,9 @@ private void checkTrashPurgeCombination(Path pathToData, String objectName, bool * Deletes the data in a table's location, if it fails logs an error * * @param tablePath + * @throws MetaException rethrows the exception if ConfVars.METASTORE_DROPTABLE_STRICT is true */ - private void deleteTableData(Path tablePath) { + private void deleteTableData(Path tablePath) throws MetaException { deleteTableData(tablePath, false); } @@ -1759,15 +1760,21 @@ private void deleteTableData(Path tablePath) { * @param tablePath * @param ifPurge completely purge the table (skipping trash) while removing * data from warehouse + * @throws MetaException rethrows the exception if ConfVars.METASTORE_DROPTABLE_STRICT is true */ - private void deleteTableData(Path tablePath, boolean ifPurge) { + private void deleteTableData(Path tablePath, boolean ifPurge) throws MetaException { if (tablePath != null) { try { wh.deleteDir(tablePath, true, ifPurge); - } catch (Exception e) { + } catch (MetaException e) { + String msg = e.getMessage(); LOG.error("Failed to delete table directory: " + tablePath + - " " + e.getMessage()); + " " + msg); + if (hiveConf.getBoolVar(ConfVars.METASTORE_DROPTABLE_STRICT)) { + e.setMessage(msg.concat(" Caution - table content might be corrupted.")); + throw e; + } } } } @@ -1777,27 +1784,34 @@ private void deleteTableData(Path tablePath, boolean ifPurge) { * and for each that fails logs an error. * * @param partPaths + * @throws MetaException rethrows the exception if ConfVars.METASTORE_DROPTABLE_STRICT is true */ - private void deletePartitionData(List partPaths) { + private void deletePartitionData(List partPaths) throws MetaException { deletePartitionData(partPaths, false); } /** - * Give a list of partitions' locations, tries to delete each one + * Given a list of partitions' locations, tries to delete each one * and for each that fails logs an error. * * @param partPaths * @param ifPurge completely purge the partition (skipping trash) while * removing data from warehouse + * @throws MetaException rethrows the exception if ConfVars.METASTORE_DROPTABLE_STRICT is true */ - private void deletePartitionData(List partPaths, boolean ifPurge) { + private void deletePartitionData(List partPaths, boolean ifPurge) throws MetaException { if (partPaths != null && !partPaths.isEmpty()) { for (Path partPath : partPaths) { try { wh.deleteDir(partPath, true, ifPurge); - } catch (Exception e) { + } catch (MetaException e) { + String msg = e.getMessage(); LOG.error("Failed to delete partition directory: " + partPath + - " " + e.getMessage()); + " " + msg); + if (hiveConf.getBoolVar(ConfVars.METASTORE_DROPTABLE_STRICT)) { + e.setMessage(msg.concat(" Caution - table content might be corrupted.")); + throw e; + } } } }