Fixed P4RuntimeClient not parsing detailed write errors

Before the only error shown was UNKNOWN, now we can report errors
specif to each write request.

Change-Id: I5eeed890597b7273720414498b3abce8e9d62fa8
diff --git a/protocols/p4runtime/ctl/BUILD b/protocols/p4runtime/ctl/BUILD
index 9418b80..d6c0c7a 100644
--- a/protocols/p4runtime/ctl/BUILD
+++ b/protocols/p4runtime/ctl/BUILD
@@ -6,6 +6,7 @@
     "@com_google_protobuf//:protobuf_java",
     "//protocols/grpc:grpc-core-repkg",
     "@io_grpc_grpc_java//netty",
+    "@io_grpc_grpc_java//protobuf-lite",
     "@io_grpc_grpc_java//stub",
     "@com_google_api_grpc_proto_google_common_protos//jar",
 ]
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 867bfa3..0bd45d3 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -24,8 +24,10 @@
 import com.google.protobuf.InvalidProtocolBufferException;
 import io.grpc.Context;
 import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
 import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
 import io.grpc.stub.ClientCallStreamObserver;
 import io.grpc.stub.StreamObserver;
 import org.apache.commons.lang3.tuple.ImmutablePair;
@@ -118,6 +120,11 @@
     // Timeout in seconds to obtain the request lock.
     private static final int LOCK_TIMEOUT = 60;
 
+    private static final Metadata.Key<com.google.rpc.Status> STATUS_DETAILS_KEY =
+            Metadata.Key.of("grpc-status-details-bin",
+                            ProtoLiteUtils.metadataMarshaller(
+                                    com.google.rpc.Status.getDefaultInstance()));
+
     private static final Map<WriteOperationType, Update.Type> UPDATE_TYPES = ImmutableMap.of(
             WriteOperationType.UNSPECIFIED, Update.Type.UNSPECIFIED,
             WriteOperationType.INSERT, Update.Type.INSERT,
@@ -1022,37 +1029,32 @@
 
         checkGrpcException(ex);
 
-        List<P4RuntimeOuterClass.Error> errors = null;
-        String description = null;
-        try {
-            errors = extractWriteErrorDetails(ex);
-        } catch (InvalidProtocolBufferException e) {
-            description = ex.getStatus().getDescription();
-        }
+        final List<P4RuntimeOuterClass.Error> errors = extractWriteErrorDetails(ex);
 
-        log.warn("Unable to {} {} {}(s) on {}: {}{} (detailed errors might be logged below)",
+        if (errors.isEmpty()) {
+            final String description = ex.getStatus().getDescription();
+            log.warn("Unable to {} {} {}(s) on {}: {}",
                  opType.name(), writeEntities.size(), entryType, deviceId,
                  ex.getStatus().getCode().name(),
-                 description == null ? "" : " - " + description);
-
-        if (errors == null || errors.isEmpty()) {
+                     description == null ? "" : " - " + description);
             return;
         }
 
         // FIXME: we are assuming entities is an ordered collection, e.g. a list,
         // and that errors are reported in the same order as the corresponding
         // written entity. Write RPC methods should be refactored to accept an
-        // order list of entities, instead of a collection.
+        // ordered list of entities, instead of a collection.
         if (errors.size() == writeEntities.size()) {
             Iterator<E> entityIterator = writeEntities.iterator();
             errors.stream()
                     .map(e -> ImmutablePair.of(e, entityIterator.next()))
                     .filter(p -> p.left.getCanonicalCode() != Status.OK.getCode().value())
-                    .forEach(p -> log.warn("Unable to {} {}: {} [{}]",
-                                           opType.name(), entryType, parseP4Error(p.getLeft()),
+                    .forEach(p -> log.warn("Unable to {} {} on {}: {} [{}]",
+                                           opType.name(), entryType, deviceId,
+                                           parseP4Error(p.getLeft()),
                                            p.getRight().toString()));
         } else {
-            log.error("Unable to reconcile error details to updates " +
+            log.warn("Unable to reconcile error details to updates " +
                               "(sent {} updates, but device returned {} errors)",
                       entryType, writeEntities.size(), errors.size());
             errors.stream()
@@ -1063,13 +1065,14 @@
     }
 
     private List<P4RuntimeOuterClass.Error> extractWriteErrorDetails(
-            StatusRuntimeException ex) throws InvalidProtocolBufferException {
-        String statusString = ex.getStatus().getDescription();
-        if (statusString == null) {
+            StatusRuntimeException ex) {
+        if (!ex.getTrailers().containsKey(STATUS_DETAILS_KEY)) {
             return Collections.emptyList();
         }
-        com.google.rpc.Status status = com.google.rpc.Status
-                .parseFrom(statusString.getBytes());
+        com.google.rpc.Status status = ex.getTrailers().get(STATUS_DETAILS_KEY);
+        if (status == null) {
+            return Collections.emptyList();
+        }
         return status.getDetailsList().stream()
                 .map(any -> {
                     try {
@@ -1086,12 +1089,12 @@
     }
 
     private String parseP4Error(P4RuntimeOuterClass.Error err) {
-        return format("%s %s (%s code %d)%s",
-                      Status.fromCodeValue(err.getCanonicalCode()),
+        return format("%s %s%s (%s:%d)",
+                      Status.fromCodeValue(err.getCanonicalCode()).getCode(),
                       err.getMessage(),
+                      err.hasDetails() ? ", " + err.getDetails().toString() : "",
                       err.getSpace(),
-                      err.getCode(),
-                      err.hasDetails() ? "\n" + err.getDetails().toString() : "");
+                      err.getCode());
     }
 
     private void checkGrpcException(StatusRuntimeException ex) {