[java] KUDU-3267 Improve logging on writes to non-existent partitions

This patch helps to log a more meaningful error message when a
non-existent range partition is written to by the Kudu Java client.
Due to different behavior of the client in different flush modes,
the existing messages returned by the client are different when
different flush modes are used. Effort has been made to bring
consistency to the error messages returned in all the flush modes
as well.

For example in the default AUTO_FLUSH_BACKGROUND mode
Existing log message snippet:
Not found: ([0x000000018005BDAA4BF52400, 0x000000028005BCC2F4C85400))
New log message snippet (in any FLUSH mode):
Not Found: accessed range partition ([0x80000064, 0x800000C8)) does
not exist in table: TestKuduSession

Thanks to Grant Henke for the help in writing this patch.

Change-Id: Ia24582de6b060e908f5ecbc46e2638b95cd567b3
Reviewed-on: http://gerrit.cloudera.org:8080/17518
Tested-by: Alexey Serbin <aserbin@cloudera.com>
Reviewed-by: Alexey Serbin <aserbin@cloudera.com>
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduSession.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduSession.java
index 3b0f5c0..0739733 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduSession.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduSession.java
@@ -370,7 +370,8 @@
           if (failure instanceof NonCoveredRangeException) {
             // TODO: this should be something different than NotFound so that
             // applications can distinguish from updates on missing rows.
-            error = new RowError(Status.NotFound(failure.getMessage()), operation);
+            error = new RowError(Status.NotFound(String.format(
+                    "%s: %s", failure.getMessage(), operation.getTable().getName())), operation);
           } else {
             LOG.warn("unexpected tablet lookup failure for operation {}", operation, failure);
             error = new RowError(Status.RuntimeError(failure.getMessage()), operation);
@@ -808,9 +809,14 @@
     @Override
     public Object call(Exception e) throws Exception {
       if (e instanceof KuduException) {
-        Status status = ((KuduException) e).getStatus();
-        RowError rowError = new RowError(status, operation);
-        return new OperationResponse(0, null, 0, operation, rowError);
+        Status status;
+        if (e instanceof NonCoveredRangeException) {
+          status = Status.NotFound(String.format(
+                  "%s: %s", e.getMessage(), operation.getTable().getName()));
+        } else {
+          status = ((KuduException) e).getStatus();
+        }
+        return new OperationResponse(0, null, 0, operation, new RowError(status, operation));
       }
       return e;
     }
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/NonCoveredRangeException.java b/java/kudu-client/src/main/java/org/apache/kudu/client/NonCoveredRangeException.java
index 6f3efe2..8d7640b 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/NonCoveredRangeException.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/NonCoveredRangeException.java
@@ -28,11 +28,22 @@
   private final byte[] nonCoveredRangeEnd;
 
   public NonCoveredRangeException(byte[] nonCoveredRangeStart, byte[] nonCoveredRangeEnd) {
-    super(Status.NotFound("non-covered range"));
+    super(Status.NotFound(getMessage(nonCoveredRangeStart, nonCoveredRangeEnd)));
     this.nonCoveredRangeStart = nonCoveredRangeStart;
     this.nonCoveredRangeEnd = nonCoveredRangeEnd;
   }
 
+  private static String getMessage(byte[] rangeStart, byte[] rangeEnd) {
+    return String.format("accessed range partition ([%s, %s)) does not exist in table",
+            rangeStart.length == 0 ? "<start>" : Bytes.hex(rangeStart),
+            rangeEnd.length == 0 ? "<end>" : Bytes.hex(rangeEnd));
+  }
+
+  @Override
+  public String getMessage() {
+    return getMessage(nonCoveredRangeStart, nonCoveredRangeEnd);
+  }
+
   byte[] getNonCoveredRangeStart() {
     return nonCoveredRangeStart;
   }
@@ -40,11 +51,4 @@
   byte[] getNonCoveredRangeEnd() {
     return nonCoveredRangeEnd;
   }
-
-  @Override
-  public String getMessage() {
-    return String.format("([%s, %s))",
-        nonCoveredRangeStart.length == 0 ? "<start>" : Bytes.hex(nonCoveredRangeStart),
-        nonCoveredRangeEnd.length == 0 ? "<end>" : Bytes.hex(nonCoveredRangeEnd));
-  }
 }
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPartitioner.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPartitioner.java
index 29bd3ed..6c5c063 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPartitioner.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPartitioner.java
@@ -158,7 +158,7 @@
       part.partitionRow(over);
       fail("partitionRow did not throw a NonCoveredRangeException");
     } catch (NonCoveredRangeException ex) {
-      // Expected
+      assertTrue(ex.getMessage().contains("does not exist in table"));
     }
   }
 
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
index 52e2571..062fc67 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
@@ -540,6 +540,42 @@
   }
 
   @Test(timeout = 10000)
+  public void testNonCoveredRangeException() throws Exception {
+    CreateTableOptions createOptions = getBasicTableOptionsWithNonCoveredRange();
+    createOptions.setNumReplicas(1);
+    client.createTable(tableName, basicSchema, createOptions);
+    KuduTable table = client.openTable(tableName);
+    Insert insert = createInsert(table, 150);
+
+    //AUTO_FLUSH_SYNC case
+    KuduSession session = client.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC);
+    OperationResponse apply = session.apply(insert);
+    assertTrue(apply.hasRowError());
+    System.err.println(apply.getRowError().getErrorStatus().getMessage());
+    assertTrue(apply.getRowError().getErrorStatus().getMessage().contains(
+            "does not exist in table: TestKuduSession"));
+    //AUTO_FLUSH_BACKGROUND case
+    session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND);
+    assertEquals(null, session.apply(insert));
+    List<OperationResponse> autoFlushResult = session.flush();
+    assertEquals(1, autoFlushResult.size());
+    OperationResponse responseAuto = autoFlushResult.get(0);
+    assertTrue(responseAuto.hasRowError());
+    assertTrue(responseAuto.getRowError().getErrorStatus().getMessage().contains(
+            "does not exist in table: TestKuduSession"));
+    //MANUAL_FLUSH case
+    session.setFlushMode(SessionConfiguration.FlushMode.MANUAL_FLUSH);
+    assertEquals(null, session.apply(insert));
+    List<OperationResponse> manualFlushResult = session.flush();
+    assertEquals(1, manualFlushResult.size());
+    OperationResponse responseManual = manualFlushResult.get(0);
+    assertTrue(responseManual.hasRowError());
+    assertTrue(responseManual.getRowError().getErrorStatus().getMessage().contains(
+            "does not exist in table: TestKuduSession"));
+  }
+
+  @Test(timeout = 10000)
   public void testInsertAutoFlushSyncNonCoveredRange() throws Exception {
     CreateTableOptions createOptions = getBasicTableOptionsWithNonCoveredRange();
     createOptions.setNumReplicas(1);