diff --git a/CHANGES.txt b/CHANGES.txt index df8dedf1ddb..b93fec1db5a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -739,6 +739,8 @@ Release 0.92.0 - Unreleased HBASE-3939 Some crossports of Hadoop IPC fixes HBASE-4756 Enable tab-completion in HBase shell (Ryan Thiessen) HBASE-4759 Migrate from JUnit 4.8.2 to JUnit 4.10 (nkeywal) + HBASE-4554 Allow set/unset coprocessor table attributes from shell + (Mingjie Lai) TASKS HBASE-3559 Move report of split to master OFF the heartbeat channel diff --git a/src/main/java/org/apache/hadoop/hbase/HConstants.java b/src/main/java/org/apache/hadoop/hbase/HConstants.java index a468100a941..d22f50a42bd 100644 --- a/src/main/java/org/apache/hadoop/hbase/HConstants.java +++ b/src/main/java/org/apache/hadoop/hbase/HConstants.java @@ -537,9 +537,9 @@ public final class HConstants { Bytes.toString(ROOT_TABLE_NAME), SPLIT_LOGDIR_NAME })); public static final Pattern CP_HTD_ATTR_KEY_PATTERN = Pattern.compile - ("coprocessor\\$([0-9]+)", Pattern.CASE_INSENSITIVE); + ("^coprocessor\\$([0-9]+)$", Pattern.CASE_INSENSITIVE); public static final Pattern CP_HTD_ATTR_VALUE_PATTERN = - Pattern.compile("([^\\|]*)\\|([^\\|]+)\\|[\\s]*([\\d]*)[\\s]*(\\|.*)?"); + Pattern.compile("(^[^\\|]*)\\|([^\\|]+)\\|[\\s]*([\\d]*)[\\s]*(\\|.*)?$"); public static final String CP_HTD_ATTR_VALUE_PARAM_KEY_PATTERN = "[^=,]+"; public static final String CP_HTD_ATTR_VALUE_PARAM_VALUE_PATTERN = "[^,]+"; diff --git a/src/main/ruby/hbase/admin.rb b/src/main/ruby/hbase/admin.rb index 61e04d81885..17cc891ab4c 100644 --- a/src/main/ruby/hbase/admin.rb +++ b/src/main/ruby/hbase/admin.rb @@ -364,6 +364,37 @@ module Hbase htd.setReadOnly(JBoolean.valueOf(arg[READONLY])) if arg[READONLY] htd.setMemStoreFlushSize(JLong.valueOf(arg[MEMSTORE_FLUSHSIZE])) if arg[MEMSTORE_FLUSHSIZE] htd.setDeferredLogFlush(JBoolean.valueOf(arg[DEFERRED_LOG_FLUSH])) if arg[DEFERRED_LOG_FLUSH] + + # set a coprocessor attribute + if arg.kind_of?(Hash) + arg.each do |key, value| + k = String.new(key) # prepare to strip + k.strip! + + if (k =~ /coprocessor/i) + # validate coprocessor specs + v = String.new(value) + v.strip! + if !(v =~ /^([^\|]*)\|([^\|]+)\|[\s]*([\d]*)[\s]*(\|.*)?$/) + raise ArgumentError, "Coprocessor value doesn't match spec: #{v}" + end + + # generate a coprocessor ordinal by checking max id of existing cps + maxId = 0 + htd.getValues().each do |k1, v1| + attrName = org.apache.hadoop.hbase.util.Bytes.toString(k1.get()) + # a cp key is coprocessor$(\d) + if (attrName =~ /coprocessor\$(\d+)/i) + ids = attrName.scan(/coprocessor\$(\d+)/i) + maxId = ids[0][0].to_i if ids[0][0].to_i > maxId + end + end + maxId += 1 + htd.setValue(k + "\$" + maxId.to_s, value) + end + end + end + @admin.modifyTable(table_name.to_java_bytes, htd) if wait == true puts "Updating all regions with the new schema..." @@ -372,6 +403,25 @@ module Hbase next end + # Unset table attributes + if method == "table_att_unset" + if arg.kind_of?(Hash) + if (!arg[NAME]) + next + end + if (htd.getValue(arg[NAME]) == nil) + raise ArgumentError, "Can not find attribute: #{arg[NAME]}" + end + htd.remove(arg[NAME].to_java_bytes) + @admin.modifyTable(table_name.to_java_bytes, htd) + if wait == true + puts "Updating all regions with the new schema..." + alter_status(table_name) + end + end + next + end + # Unknown method raise ArgumentError, "Unknown method: #{method}" end diff --git a/src/main/ruby/shell/commands/alter.rb b/src/main/ruby/shell/commands/alter.rb index ee3668db296..33fbcb3cdd6 100644 --- a/src/main/ruby/shell/commands/alter.rb +++ b/src/main/ruby/shell/commands/alter.rb @@ -48,6 +48,26 @@ For example, to change the max size of a family to 128MB, do: hbase> alter 't1', METHOD => 'table_att', MAX_FILESIZE => '134217728' +You can add a table coprocessor by setting a table coprocessor attribute: + + hbase> alter 't1', METHOD => 'table_att', + 'coprocessor'=>'hdfs:///foo.jar|com.foo.FooRegionObserver|1001|arg1=1,arg2=2' + +Since you can have multiple coprocessors configured for a table, a +sequence number will be automatically appended to the attribute name +to uniquely identify it. + +The coprocessor attribute must match the pattern below in order for +the framework to understand how to load the coprocessor classes: + + [coprocessor jar file location] | class name | [priority] | [arguments] + +You can also remove a table-scope attribute: + + hbase> alter 't1', METHOD => 'table_att_unset', NAME => 'MAX_FILESIZE' + + hbase> alter 't1', METHOD => 'table_att_unset', NAME => 'coprocessor$1' + There could be more than one alteration in one command: hbase> alter 't1', {NAME => 'f1'}, {NAME => 'f2', METHOD => 'delete'} diff --git a/src/test/ruby/hbase/admin_test.rb b/src/test/ruby/hbase/admin_test.rb index df88100eee9..0c2672b89f5 100644 --- a/src/test/ruby/hbase/admin_test.rb +++ b/src/test/ruby/hbase/admin_test.rb @@ -271,5 +271,44 @@ module Hbase admin.alter(@test_name, true, METHOD => 'table_att', 'MAX_FILESIZE' => 12345678) assert_match(/12345678/, admin.describe(@test_name)) end + + define_test "alter should be able to change coprocessor attributes" do + drop_test_table(@test_name) + create_test_table(@test_name) + + cp_key = "coprocessor" + class_name = "SimpleRegionObserver" + + cp_value = "hdfs:///foo.jar|" + class_name + "|12|arg1=1,arg2=2" + + # eval() is used to convert a string to regex + assert_no_match(eval("/" + class_name + "/"), admin.describe(@test_name)) + assert_no_match(eval("/" + cp_key + "/"), admin.describe(@test_name)) + admin.alter(@test_name, true, 'METHOD' => 'table_att', cp_key => cp_value) + assert_match(eval("/" + class_name + "/"), admin.describe(@test_name)) + assert_match(eval("/" + cp_key + "\\$(\\d+)/"), admin.describe(@test_name)) + end + + define_test "alter should be able to remove a table attribute" do + drop_test_table(@test_name) + create_test_table(@test_name) + + key1 = "coprocessor" + key2 = "MAX_FILESIZE" + admin.alter(@test_name, true, 'METHOD' => 'table_att', key1 => "|TestCP||") + admin.alter(@test_name, true, 'METHOD' => 'table_att', key2 => 12345678) + + # eval() is used to convert a string to regex + assert_match(eval("/" + key1 + "\\$(\\d+)/"), admin.describe(@test_name)) + assert_match(eval("/" + key2 + "/"), admin.describe(@test_name)) + + # get the cp key + cp_keys = admin.describe(@test_name).scan(/(coprocessor\$\d+)/i) + + admin.alter(@test_name, true, 'METHOD' => 'table_att_unset', 'NAME' => cp_keys[0][0]) + admin.alter(@test_name, true, 'METHOD' => 'table_att_unset', 'NAME' => key2) + assert_no_match(eval("/" + key1 + "\\$(\\d+)/"), admin.describe(@test_name)) + assert_no_match(eval("/" + key2 + "/"), admin.describe(@test_name)) + end end end