1
0

[HUDI-2795] Add mechanism to safely update,delete and recover table properties (#4038)

* [HUDI-2795] Add mechanism to safely update,delete and recover table properties

  - Fail safe mechanism, that lets queries succeed off a backup file
  - Readers who are not upgraded to this version of code will just fail until recovery is done.
  - Added unit tests that exercises all these scenarios.
  - Adding CLI for recovery, updation to table command.
  - [Pending] Add some hash based verfication to ensure any rare partial writes for HDFS

* Fixing upgrade/downgrade infrastructure to use new updation method
This commit is contained in:
vinoth chandar
2021-11-20 08:07:40 -08:00
committed by GitHub
parent f4b974ac7b
commit ae0c67d9fc
7 changed files with 337 additions and 73 deletions

View File

@@ -94,7 +94,7 @@ public class TestBootstrapIndex extends HoodieCommonTestHarness {
props.put(HoodieTableConfig.BOOTSTRAP_INDEX_ENABLE.key(), "false");
Properties properties = new Properties();
properties.putAll(props);
HoodieTableConfig.createHoodieProperties(metaClient.getFs(), new Path(metaClient.getMetaPath()), properties);
HoodieTableConfig.create(metaClient.getFs(), new Path(metaClient.getMetaPath()), properties);
metaClient = HoodieTableMetaClient.builder().setConf(metaClient.getHadoopConf()).setBasePath(basePath).build();
BootstrapIndex bootstrapIndex = BootstrapIndex.getBootstrapIndex(metaClient);

View File

@@ -0,0 +1,137 @@
/*
* 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.hudi.common.table;
import org.apache.hudi.common.testutils.HoodieCommonTestHarness;
import org.apache.hudi.common.util.CollectionUtils;
import org.apache.hudi.exception.HoodieIOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestHoodieTableConfig extends HoodieCommonTestHarness {
private FileSystem fs;
private Path metaPath;
private Path cfgPath;
private Path backupCfgPath;
@BeforeEach
public void setUp() throws Exception {
initPath();
fs = new Path(basePath).getFileSystem(new Configuration());
metaPath = new Path(basePath, HoodieTableMetaClient.METAFOLDER_NAME);
Properties props = new Properties();
props.setProperty(HoodieTableConfig.NAME.key(), "test-table");
HoodieTableConfig.create(fs, metaPath, props);
cfgPath = new Path(metaPath, HoodieTableConfig.HOODIE_PROPERTIES_FILE);
backupCfgPath = new Path(metaPath, HoodieTableConfig.HOODIE_PROPERTIES_FILE_BACKUP);
}
@Test
public void testCreate() throws IOException {
assertTrue(fs.exists(new Path(metaPath, HoodieTableConfig.HOODIE_PROPERTIES_FILE)));
HoodieTableConfig config = new HoodieTableConfig(fs, metaPath.toString(), null);
assertEquals(4, config.getProps().size());
}
@Test
public void testUpdate() throws IOException {
Properties updatedProps = new Properties();
updatedProps.setProperty(HoodieTableConfig.NAME.key(), "test-table2");
updatedProps.setProperty(HoodieTableConfig.PRECOMBINE_FIELD.key(), "new_field");
HoodieTableConfig.update(fs, metaPath, updatedProps);
assertTrue(fs.exists(cfgPath));
assertFalse(fs.exists(backupCfgPath));
HoodieTableConfig config = new HoodieTableConfig(fs, metaPath.toString(), null);
assertEquals(5, config.getProps().size());
assertEquals("test-table2", config.getTableName());
assertEquals("new_field", config.getPreCombineField());
}
@Test
public void testDelete() throws IOException {
Set<String> deletedProps = CollectionUtils.createSet(HoodieTableConfig.ARCHIVELOG_FOLDER.key(), "hoodie.invalid.config");
HoodieTableConfig.delete(fs, metaPath, deletedProps);
assertTrue(fs.exists(cfgPath));
assertFalse(fs.exists(backupCfgPath));
HoodieTableConfig config = new HoodieTableConfig(fs, metaPath.toString(), null);
assertEquals(3, config.getProps().size());
assertNull(config.getProps().getProperty("hoodie.invalid.config"));
assertFalse(config.getProps().contains(HoodieTableConfig.ARCHIVELOG_FOLDER.key()));
}
@Test
public void testReadsWhenPropsFileDoesNotExist() throws IOException {
fs.delete(cfgPath, false);
assertThrows(HoodieIOException.class, () -> {
new HoodieTableConfig(fs, metaPath.toString(), null);
});
}
@Test
public void testReadsWithUpdateFailures() throws IOException {
HoodieTableConfig config = new HoodieTableConfig(fs, metaPath.toString(), null);
fs.delete(cfgPath, false);
try (FSDataOutputStream out = fs.create(backupCfgPath)) {
config.getProps().store(out, "");
}
assertFalse(fs.exists(cfgPath));
assertTrue(fs.exists(backupCfgPath));
config = new HoodieTableConfig(fs, metaPath.toString(), null);
assertEquals(4, config.getProps().size());
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testUpdateRecovery(boolean shouldPropsFileExist) throws IOException {
HoodieTableConfig config = new HoodieTableConfig(fs, metaPath.toString(), null);
if (!shouldPropsFileExist) {
fs.delete(cfgPath, false);
}
try (FSDataOutputStream out = fs.create(backupCfgPath)) {
config.getProps().store(out, "");
}
HoodieTableConfig.recoverIfNeeded(fs, cfgPath, backupCfgPath);
assertTrue(fs.exists(cfgPath));
assertFalse(fs.exists(backupCfgPath));
config = new HoodieTableConfig(fs, metaPath.toString(), null);
assertEquals(4, config.getProps().size());
}
}