Author: garyh
Date: Tue Aug  2 06:18:07 2011
New Revision: 1153040

URL: http://svn.apache.org/viewvc?rev=1153040&view=rev
Log:
HBASE-3810  Relax parsing and add convenience methods for HTableDescriptor 
coprocessor config

Modified:
    hbase/trunk/CHANGES.txt
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java
    
hbase/trunk/src/test/java/org/apache/hadoop/hbase/coprocessor/TestClassLoading.java

Modified: hbase/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1153040&r1=1153039&r2=1153040&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Tue Aug  2 06:18:07 2011
@@ -349,6 +349,8 @@ Release 0.91.0 - Unreleased
    HBASE-4143  HTable.doPut(List) should check the writebuffer length every so 
often
                (Doug Meil via Ted Yu)
    HBASE-3065  Retry all 'retryable' zk operations; e.g. connection loss 
(Liyin Tang)
+   HBASE-3810  Registering a coprocessor in HTableDescriptor should be easier
+               (Mingjie Lai via garyh)
 
   TASKS
    HBASE-3559  Move report of split to master OFF the heartbeat channel

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java?rev=1153040&r1=1153039&r2=1153040&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java Tue 
Aug  2 06:18:07 2011
@@ -29,10 +29,13 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.regex.Matcher;
 
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.coprocessor.Coprocessor;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.io.hfile.Compression;
+import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
 import org.apache.hadoop.hbase.regionserver.StoreFile;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.io.WritableComparable;
@@ -668,6 +671,110 @@ public class HTableDescriptor implements
   }
 
   /**
+   * Add a table coprocessor to this table. The coprocessor
+   * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
+   * or Endpoint.
+   * It won't check if the class can be loaded or not.
+   * Whether a coprocessor is loadable or not will be determined when
+   * a region is opened.
+   * @param className Full class name.
+   * @throws IOException
+   */
+  public void addCoprocessor(String className) throws IOException {
+    addCoprocessor(className, null, Coprocessor.PRIORITY_USER, null);
+  }
+
+  /**
+   * Add a table coprocessor to this table. The coprocessor
+   * type must be {@link org.apache.hadoop.hbase.coprocessor.RegionObserver}
+   * or Endpoint.
+   * It won't check if the class can be loaded or not.
+   * Whether a coprocessor is loadable or not will be determined when
+   * a region is opened.
+   * @param jarFilePath Path of the jar file. If it's null, the class will be
+   * loaded from default classloader.
+   * @param className Full class name.
+   * @param priority Priority
+   * @param kvs Arbitrary key-value parameter pairs passed into the 
coprocessor.
+   * @throws IOException
+   */
+  public void addCoprocessor(String className, Path jarFilePath,
+                             int priority, final Map<String, String> kvs)
+  throws IOException {
+    if (hasCoprocessor(className)) {
+      throw new IOException("Coprocessor " + className + " already exists.");
+    }
+    // validate parameter kvs
+    //String kvString = "";
+    StringBuilder kvString = new StringBuilder();
+    if (kvs != null) {
+      for (Map.Entry<String, String> e: kvs.entrySet()) {
+        if (!e.getKey().matches(RegionCoprocessorHost.PARAMETER_KEY_PATTERN)) {
+          throw new IOException("Illegal parameter key = " + e.getKey());
+        }
+        if 
(!e.getValue().matches(RegionCoprocessorHost.PARAMETER_VALUE_PATTERN)) {
+          throw new IOException("Illegal parameter (" + e.getKey() +
+              ") value = " + e.getValue());
+        }
+        if (kvString.length() != 0) {
+          kvString.append(',');
+        }
+        kvString.append(e.getKey());
+        kvString.append('=');
+        kvString.append(e.getValue());
+      }
+    }
+
+    // generate a coprocessor key
+    int maxCoprocessorNumber = 0;
+    Matcher keyMatcher;
+    for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
+        this.values.entrySet()) {
+      keyMatcher =
+          RegionCoprocessorHost.CP_KEY_PATTERN.matcher(
+              Bytes.toString(e.getKey().get()));
+      if (!keyMatcher.matches()) {
+        continue;
+      }
+      maxCoprocessorNumber = Math.max(Integer.parseInt(keyMatcher.group(1)),
+          maxCoprocessorNumber);
+    }
+    maxCoprocessorNumber++;
+
+    String key = "coprocessor$" + Integer.toString(maxCoprocessorNumber);
+    String value = ((jarFilePath == null)? "" : jarFilePath.toString()) +
+        "|" + className + "|" + Integer.toString(priority) + "|" +
+        kvString.toString();
+    setValue(key, value);
+  }
+
+  public boolean hasCoprocessor(String className) {
+    Matcher keyMatcher;
+    Matcher valueMatcher;
+    for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
+        this.values.entrySet()) {
+      keyMatcher =
+          RegionCoprocessorHost.CP_KEY_PATTERN.matcher(
+              Bytes.toString(e.getKey().get()));
+      if (!keyMatcher.matches()) {
+        continue;
+      }
+      valueMatcher =
+        RegionCoprocessorHost.CP_VALUE_PATTERN.matcher(
+            Bytes.toString(e.getValue().get()));
+      if (!valueMatcher.matches()) {
+        continue;
+      }
+      // get className and compare
+      String clazz = valueMatcher.group(2).trim(); // classname is the 2nd 
field
+      if (clazz.equals(className.trim())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
    * @param rootdir qualified path of HBase root directory
    * @param tableName name of table
    * @return path for table

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java?rev=1153040&r1=1153039&r2=1153040&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java
 (original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java
 Tue Aug  2 06:18:07 2011
@@ -92,9 +92,15 @@ public class RegionCoprocessorHost
     }
   }
 
-  static final Pattern attrSpecMatch1 = 
Pattern.compile("(.+)\\|(.+)\\|(.+)\\|(.+)");
-  static final Pattern attrSpecMatch2 = Pattern.compile("(.+)\\|(.+)\\|(.+)");
-  static final Pattern cfgSpecMatch = Pattern.compile("([^=]+)=([^,]+),?");
+  public static final Pattern CP_KEY_PATTERN = Pattern.compile
+      ("coprocessor\\$([0-9]+)", Pattern.CASE_INSENSITIVE);
+  public static final Pattern CP_VALUE_PATTERN =
+      Pattern.compile("([^\\|]*)\\|([^\\|]+)\\|[\\s]*([\\d]*)[\\s]*(\\|.*)?");
+
+  public static final String PARAMETER_KEY_PATTERN = "[^=,]+";
+  public static final String PARAMETER_VALUE_PATTERN = "[^,]+";
+  public static final Pattern CFG_SPEC_MATCH = Pattern.compile(
+      "(" + PARAMETER_KEY_PATTERN + ")=(" + PARAMETER_VALUE_PATTERN  + "),?");
 
   /** The region server services */
   RegionServerServices rsServices;
@@ -117,28 +123,29 @@ public class RegionCoprocessorHost
     loadSystemCoprocessors(conf, REGION_COPROCESSOR_CONF_KEY);
 
     // load Coprocessor From HDFS
-    loadTableCoprocessors();
+    loadTableCoprocessors(conf);
   }
 
-  void loadTableCoprocessors() {
+  void loadTableCoprocessors(final Configuration conf) {
     // scan the table attributes for coprocessor load specifications
     // initialize the coprocessors
     List<RegionEnvironment> configured = new ArrayList<RegionEnvironment>();
     for (Map.Entry<ImmutableBytesWritable,ImmutableBytesWritable> e:
         region.getTableDesc().getValues().entrySet()) {
-      String key = Bytes.toString(e.getKey().get());
-      String spec = Bytes.toString(e.getValue().get());
-      if (key.startsWith("COPROCESSOR")) {
+      String key = Bytes.toString(e.getKey().get()).trim();
+      String spec = Bytes.toString(e.getValue().get()).trim();
+      if (CP_KEY_PATTERN.matcher(key).matches()) {
         // found one
         try {
-          Matcher matcher = attrSpecMatch1.matcher(spec);
-          if (!matcher.matches()) {
-            matcher = attrSpecMatch2.matcher(spec);
-          }
+          Matcher matcher = CP_VALUE_PATTERN.matcher(spec);
           if (matcher.matches()) {
-            Path path = new Path(matcher.group(1));
-            String className = matcher.group(2);
-            int priority = Integer.valueOf(matcher.group(3));
+            // jar file path can be empty if the cp class can be loaded
+            // from class loader.
+            Path path = matcher.group(1).trim().isEmpty() ?
+                null : new Path(matcher.group(1).trim());
+            String className = matcher.group(2).trim();
+            int priority = matcher.group(3).trim().isEmpty() ?
+                Coprocessor.PRIORITY_USER : Integer.valueOf(matcher.group(3));
             String cfgSpec = null;
             try {
               cfgSpec = matcher.group(4);
@@ -146,8 +153,9 @@ public class RegionCoprocessorHost
               // ignore
             }
             if (cfgSpec != null) {
-              Configuration newConf = HBaseConfiguration.create();
-              Matcher m = cfgSpecMatch.matcher(cfgSpec);
+              cfgSpec = cfgSpec.substring(cfgSpec.indexOf('|') + 1);
+              Configuration newConf = HBaseConfiguration.create(conf);
+              Matcher m = CFG_SPEC_MATCH.matcher(cfgSpec);
               while (m.find()) {
                 newConf.set(m.group(1), m.group(2));
               }

Modified: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/coprocessor/TestClassLoading.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/coprocessor/TestClassLoading.java?rev=1153040&r1=1153039&r2=1153040&view=diff
==============================================================================
--- 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/coprocessor/TestClassLoading.java
 (original)
+++ 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/coprocessor/TestClassLoading.java
 Tue Aug  2 06:18:07 2011
@@ -41,6 +41,7 @@ import java.util.jar.*;
 import org.junit.*;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 /**
  * Test coprocessors class loading.
@@ -57,6 +58,8 @@ public class TestClassLoading {
   static final String cpName1 = "TestCP1";
   static final String cpName2 = "TestCP2";
   static final String cpName3 = "TestCP3";
+  static final String cpName4 = "TestCP4";
+  static final String cpName5 = "TestCP5";
 
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
@@ -187,6 +190,10 @@ public class TestClassLoading {
     htd.setValue("COPROCESSOR$2", jarFileOnHDFS2.toString() + "|" + cpName2 +
       "|" + Coprocessor.PRIORITY_USER + "|k1=v1,k2=v2,k3=v3");
     HBaseAdmin admin = new HBaseAdmin(this.conf);
+    if (admin.tableExists(tableName)) {
+      admin.disableTable(tableName);
+      admin.deleteTable(tableName);
+    }
     admin.createTable(htd);
 
     // verify that the coprocessors were loaded
@@ -240,4 +247,94 @@ public class TestClassLoading {
     }
     assertTrue("Class " + cpName3 + " was missing on a region", found);
   }
+
+  @Test
+  // HBase-3810: Registering a Coprocessor at HTableDescriptor should be
+  // less strict
+  public void testHBase3810() throws Exception {
+    // allowed value pattern: [path] | class name | [priority] | [key values]
+
+    File jarFile1 = buildCoprocessorJar(cpName1);
+    File jarFile2 = buildCoprocessorJar(cpName2);
+    File jarFile4 = buildCoprocessorJar(cpName4);
+    File jarFile5 = buildCoprocessorJar(cpName5);
+
+    String cpKey1 = "COPROCESSOR$1";
+    String cpKey2 = " Coprocessor$2 ";
+    String cpKey3 = " coprocessor$03 ";
+
+    String cpValue1 = jarFile1.toString() + "|" + cpName1 + "|" +
+        Coprocessor.PRIORITY_USER;
+    String cpValue2 = jarFile2.toString() + " | " + cpName2 + " | ";
+    // load from default class loader
+    String cpValue3 =
+        " | org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver | | k=v ";
+
+    // create a table that references the jar
+    HTableDescriptor htd = new HTableDescriptor(tableName);
+    htd.addFamily(new HColumnDescriptor("test"));
+
+    // add 3 coprocessors by setting htd attributes directly.
+    htd.setValue(cpKey1, cpValue1);
+    htd.setValue(cpKey2, cpValue2);
+    htd.setValue(cpKey3, cpValue3);
+
+    // add 2 coprocessor by using new htd.addCoprocessor() api
+    htd.addCoprocessor(cpName4, new Path(jarFile4.getPath()),
+        Coprocessor.PRIORITY_USER, null);
+    Map<String, String> kvs = new HashMap<String, String>();
+    kvs.put("k1", "v1");
+    kvs.put("k2", "v2");
+    kvs.put("k3", "v3");
+    htd.addCoprocessor(cpName5, new Path(jarFile5.getPath()),
+        Coprocessor.PRIORITY_USER, kvs);
+
+    HBaseAdmin admin = new HBaseAdmin(this.conf);
+    if (admin.tableExists(tableName)) {
+      admin.disableTable(tableName);
+      admin.deleteTable(tableName);
+    }
+    admin.createTable(htd);
+
+    // verify that the coprocessor was loaded
+    boolean found_2 = false, found_1 = false, found_3 = false,
+        found_4 = false, found_5 = false;
+    boolean found5_k1 = false, found5_k2 = false, found5_k3 = false,
+        found5_k4 = false;
+
+    MiniHBaseCluster hbase = TEST_UTIL.getHBaseCluster();
+    for (HRegion region: 
hbase.getRegionServer(0).getOnlineRegionsLocalContext()) {
+      if (region.getRegionNameAsString().startsWith(tableName)) {
+        found_1 = found_1 ||
+            (region.getCoprocessorHost().findCoprocessor(cpName1) != null);
+        found_2 = found_2 ||
+            (region.getCoprocessorHost().findCoprocessor(cpName2) != null);
+        found_3 = found_3 ||
+            
(region.getCoprocessorHost().findCoprocessor("SimpleRegionObserver")
+                != null);
+        found_4 = found_4 ||
+            (region.getCoprocessorHost().findCoprocessor(cpName4) != null);
+
+        CoprocessorEnvironment env =
+            region.getCoprocessorHost().findCoprocessorEnvironment(cpName5);
+        if (env != null) {
+          found_5 = true;
+          Configuration conf = env.getConfiguration();
+          found5_k1 = conf.get("k1") != null;
+          found5_k2 = conf.get("k2") != null;
+          found5_k3 = conf.get("k3") != null;
+        }
+      }
+    }
+    assertTrue("Class " + cpName1 + " was missing on a region", found_1);
+    assertTrue("Class " + cpName2 + " was missing on a region", found_2);
+    assertTrue("Class SimpleRegionObserver was missing on a region", found_3);
+    assertTrue("Class " + cpName4 + " was missing on a region", found_4);
+    assertTrue("Class " + cpName5 + " was missing on a region", found_5);
+
+    assertTrue("Configuration key 'k1' was missing on a region", found5_k1);
+    assertTrue("Configuration key 'k2' was missing on a region", found5_k2);
+    assertTrue("Configuration key 'k3' was missing on a region", found5_k3);
+    assertFalse("Configuration key 'k4' wasn't configured", found5_k4);
+  }
 }


Reply via email to