/*
* Copyright © 2014-2015 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.util.hbase;
import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.data2.util.TableId;
import co.cask.cdap.proto.Id;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import java.io.IOException;
import java.util.Map;
/**
* Reads and writes current {@link CConfiguration} to a table in HBase. This make the configuration available
* to processes running on the HBase servers (such as coprocessors). The entire configuration is stored in
* a single row, keyed by the configuration type (to allow future expansion), with the configuration
* key as the column name, and the configuration value as the value.
*/
public class ConfigurationTable {
/**
* Defines the types of configurations to save in the table. Each type is used as a row key.
*/
public enum Type {
DEFAULT
}
// since this will be used in the coprocessor context, we use commons logging
private static final Log LOG = LogFactory.getLog(ConfigurationTable.class);
private static final String TABLE_NAME = "configuration";
private static final byte[] FAMILY = Bytes.toBytes("f");
private final Configuration hbaseConf;
public ConfigurationTable(Configuration hbaseConf) {
this.hbaseConf = hbaseConf;
}
/**
* Writes the {@link CConfiguration} instance as a new row to the HBase table. The {@link Type} given is used as
* the row key (allowing multiple configurations to be stored). After the new configuration is written, this will
* delete any configurations written with an earlier timestamp (to prevent removed values from being visible).
* @param cConf The CConfiguration instance to store
* @throws IOException If an error occurs while writing the configuration
*/
public void write(Type type, CConfiguration cConf) throws IOException {
TableId tableId = TableId.from(Id.Namespace.SYSTEM, TABLE_NAME);
// must create the table if it doesn't exist
HBaseAdmin admin = new HBaseAdmin(hbaseConf);
HTable table = null;
try {
HBaseTableUtil tableUtil = new HBaseTableUtilFactory(cConf).get();
HTableDescriptorBuilder htd = tableUtil.buildHTableDescriptor(tableId);
htd.addFamily(new HColumnDescriptor(FAMILY));
tableUtil.createTableIfNotExists(admin, tableId, htd.build());
long now = System.currentTimeMillis();
long previous = now - 1;
byte[] typeBytes = Bytes.toBytes(type.name());
LOG.info("Writing new config row with key " + type);
// populate the configuration data
table = tableUtil.createHTable(hbaseConf, tableId);
table.setAutoFlush(false);
Put p = new Put(typeBytes);
for (Map.Entry<String, String> e : cConf) {
p.add(FAMILY, Bytes.toBytes(e.getKey()), now, Bytes.toBytes(e.getValue()));
}
table.put(p);
LOG.info("Deleting any configuration from " + previous + " or before");
Delete d = new Delete(typeBytes);
d.deleteFamily(FAMILY, previous);
table.delete(d);
} finally {
try {
admin.close();
} catch (IOException ioe) {
LOG.error("Error closing HBaseAdmin: ", ioe);
}
if (table != null) {
try {
table.close();
} catch (IOException ioe) {
LOG.error("Error closing HBaseAdmin: " + ioe.getMessage(), ioe);
}
}
}
}
/**
* Reads the given configuration type from the HBase table, looking for the HBase table name under the
* given "sysConfigTablePrefix".
* @param type Type of configuration to read in
* @param sysConfigTablePrefix table prefix of the configuration table. (The full table name of the configuration
* table minus the table qualifier). Example: 'cdap.system:'
* @return The {@link CConfiguration} instance populated with the stored values, or {@code null} if no row
* was found for the given type.
* @throws IOException If an error occurs while attempting to read the table or the table does not exist.
*/
public CConfiguration read(Type type, String sysConfigTablePrefix) throws IOException {
String tableName = sysConfigTablePrefix + TABLE_NAME;
HTable table = null;
CConfiguration conf = null;
try {
// tableUtil is not used to create the HTable because this code is used from coprocessors which are already HBase
// version specific. Because of that, the sysConfigTablePrefix parameter passed in is already version-specific.
table = new HTable(hbaseConf, tableName);
Get get = new Get(Bytes.toBytes(type.name()));
get.addFamily(FAMILY);
Result result = table.get(get);
int propertyCnt = 0;
if (result != null && !result.isEmpty()) {
conf = CConfiguration.create();
conf.clear();
Map<byte[], byte[]> kvs = result.getFamilyMap(FAMILY);
for (Map.Entry<byte[], byte[]> e : kvs.entrySet()) {
conf.set(Bytes.toString(e.getKey()), Bytes.toString(e.getValue()));
propertyCnt++;
}
}
LOG.info("Read " + propertyCnt + " properties from configuration table = " +
Bytes.toString(table.getTableName()) + ", row = " + type.name());
} catch (TableNotFoundException e) {
// it's expected that this may occur when tables are created before MasterServiceMain has started
LOG.warn("Configuration table " + tableName + " does not yet exist.");
} finally {
if (table != null) {
try {
table.close();
} catch (IOException ioe) {
LOG.error("Error closing HTable for " + tableName, ioe);
}
}
}
return conf;
}
}