This is an automated email from the ASF dual-hosted git repository.

dzamo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/drill.git


The following commit(s) were added to refs/heads/master by this push:
     new 7105ea3  DRILL-8040: Add HTTP status codes for API errors
7105ea3 is described below

commit 7105ea3b19947ebf53a667e56430ab8d0696559a
Author: James Turton <[email protected]>
AuthorDate: Tue Nov 9 15:38:51 2021 +0200

    DRILL-8040: Add HTTP status codes for API errors
---
 .../drill/exec/server/rest/QueryResources.java     |  25 +++--
 .../drill/exec/server/rest/StorageResources.java   | 103 +++++++++++++--------
 .../exec/server/rest/profile/ProfileResources.java |  26 ++++--
 .../main/resources/rest/static/js/serverMessage.js |   4 +-
 .../src/main/resources/rest/storage/list.ftl       |  16 +++-
 .../src/main/resources/rest/storage/update.ftl     |  13 ++-
 6 files changed, 118 insertions(+), 69 deletions(-)

diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
index 93d2d11..e49431f 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
@@ -98,19 +98,18 @@ public class QueryResources {
   @Produces(MediaType.APPLICATION_JSON)
   public StreamingOutput submitQueryJSON(QueryWrapper query) throws Exception {
 
-    // Prior to Drill 1.18, REST queries would batch the entire result set
-    // in memory, limiting query size. In Drill 1.19 and later, results are
-    // streamed from the executor, to the JSON writer and to the HTTP 
connection
-    // with no buffering.
-    //
-    // For compatibility with Drill 1.18 and before, Drill places the query
-    // schema *after* the data. Any tool that needs to know the schema will
-    // need to buffer the entire result set to wait for the schema. An obvious
-    // improvement is to provide an option to send the schema *before* the 
data.
-    // One drawback of doing so is that the schema will report that of the 
first
-    // batch: Drill allows schema to change across batches and thus the schema
-    // of the JSON-encoded data would change. This is more a bug with how Drill
-    // handles schemas than a JSON issue. (ODBC and JDBC have the same issues.)
+    /*
+    Prior to Drill 1.18, REST queries would batch the entire result set in 
memory,
+    limiting query size. In Drill 1.19 and later, results are streamed from the
+    executor, to the JSON writer and to the HTTP connection with no buffering.
+
+    Starting with Drill 1.19, the "metadata" property specifying the result 
column
+    data types is placed *before* the data itself. One drawback of doing so is 
that
+    the schema will report that of the first batch: Drill allows schema to 
change
+    across batches and thus the schema of the JSON-encoded data would change. 
This
+    is more a bug with how Drill handles schemas than a JSON issue. (ODBC and 
JDBC
+    have the same issues.)
+    */
     QueryRunner runner = new QueryRunner(work, webUserConnection);
     try {
       runner.start(query);
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
index 0a8e66a..a87cb73 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
@@ -101,15 +101,14 @@ public class StorageResources {
   public Response getConfigsFor(@PathParam("group") String pluginGroup, 
@PathParam("format") String format) {
     format = StringUtils.isNotEmpty(format) ? format.replace("/", "") : 
JSON_FORMAT;
     return isSupported(format)
-        ? Response.ok()
-            .entity(getConfigsFor(pluginGroup).toArray())
-            .header(HttpHeaders.CONTENT_DISPOSITION, 
String.format("attachment;filename=\"%s_storage_plugins.%s\"",
-                pluginGroup, format))
-            .build()
-        : Response.status(Response.Status.NOT_FOUND.getStatusCode())
-            .entity(String.format("Unknown \"%s\" file format for \"%s\" 
Storage Plugin configs group",
-                    format, pluginGroup))
-            .build();
+      ? Response.ok()
+        .entity(getConfigsFor(pluginGroup).toArray())
+        .header(HttpHeaders.CONTENT_DISPOSITION, 
String.format("attachment;filename=\"%s_storage_plugins.%s\"",
+            pluginGroup, format))
+        .build()
+      : Response.status(Response.Status.NOT_ACCEPTABLE)
+          .entity(message("Unknown \"%s\" file format for Storage Plugin 
config", format))
+          .build();
   }
 
   @GET
@@ -136,20 +135,27 @@ public class StorageResources {
   @GET
   @Path("/storage/{name}.json")
   @Produces(MediaType.APPLICATION_JSON)
-  public PluginConfigWrapper getPluginConfig(@PathParam("name") String name) {
+  public Response getPluginConfig(@PathParam("name") String name) {
     try {
-      return new PluginConfigWrapper(name, storage.getStoredConfig(name));
+      return Response.ok(new PluginConfigWrapper(name, 
storage.getStoredConfig(name)))
+        .build();
     } catch (Exception e) {
       logger.error("Failure while trying to access storage config: {}", name, 
e);
+
+      return Response.status(Response.Status.NOT_FOUND)
+        .entity(message("Failure while trying to access storage config: %s", 
e.getMessage()))
+        .build();
     }
-    return new PluginConfigWrapper(name, null);
   }
 
   @GET
   @Path("/storage/{name}")
   @Produces(MediaType.TEXT_HTML)
   public Viewable getPlugin(@PathParam("name") String name) {
-    StoragePluginModel model = new StoragePluginModel(getPluginConfig(name), 
request);
+    StoragePluginModel model = new StoragePluginModel(
+      (PluginConfigWrapper) getPluginConfig(name).getEntity(),
+      request
+    );
     return ViewableWithPermissions.create(authEnabled.get(), 
"/rest/storage/update.ftl", sc,
         model);
   }
@@ -158,15 +164,19 @@ public class StorageResources {
   @Path("/storage/{name}/enable/{val}")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
-  public JsonResult enablePlugin(@PathParam("name") String name, 
@PathParam("val") Boolean enable) {
+  public Response enablePlugin(@PathParam("name") String name, 
@PathParam("val") Boolean enable) {
     try {
       storage.setEnabled(name, enable);
-      return message("Success");
+      return Response.ok().entity(message("Success")).build();
     } catch (PluginNotFoundException e) {
-      return message("No plugin exists with the given name: " + name);
+      return Response.status(Response.Status.NOT_FOUND)
+        .entity(message("No plugin exists with the given name: " + name))
+        .build();
     } catch (PluginException e) {
-      logger.debug("Error in enabling storage name: {} flag: {}",  name, 
enable);
-      return message("Unable to enable/disable plugin:" + e.getMessage());
+      logger.info("Error when enabling storage name: {} flag: {}",  name, 
enable);
+      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+        .entity(message("Unable to enable/disable plugin: %s", e.getMessage()))
+        .build();
     }
   }
 
@@ -177,7 +187,7 @@ public class StorageResources {
   @Path("/storage/{name}/enable/{val}")
   @Produces(MediaType.APPLICATION_JSON)
   @Deprecated
-  public JsonResult enablePluginViaGet(@PathParam("name") String name, 
@PathParam("val") Boolean enable) {
+  public Response enablePluginViaGet(@PathParam("name") String name, 
@PathParam("val") Boolean enable) {
     return enablePlugin(name, enable);
   }
 
@@ -192,24 +202,28 @@ public class StorageResources {
   @Produces(MediaType.APPLICATION_JSON)
   public Response exportPlugin(@PathParam("name") String name, 
@PathParam("format") String format) {
     format = StringUtils.isNotEmpty(format) ? format.replace("/", "") : 
JSON_FORMAT;
-    return isSupported(format)
-        ? Response.ok(getPluginConfig(name))
-            .header(HttpHeaders.CONTENT_DISPOSITION, 
String.format("attachment;filename=\"%s.%s\"", name, format))
-            .build()
-        : Response.status(Response.Status.NOT_FOUND.getStatusCode())
-            .entity(String.format("Unknown \"%s\" file format for \"%s\" 
Storage Plugin config", format, name))
-            .build();
+    if (!isSupported(format)) {
+      return Response.status(Response.Status.NOT_ACCEPTABLE)
+        .entity(message("Unknown \"%s\" file format for Storage Plugin 
config", format))
+        .build();
+    }
+
+    return Response.ok(getPluginConfig(name))
+      .header(HttpHeaders.CONTENT_DISPOSITION, 
String.format("attachment;filename=\"%s.%s\"", name, format))
+      .build();
   }
 
   @DELETE
   @Path("/storage/{name}.json")
   @Produces(MediaType.APPLICATION_JSON)
-  public JsonResult deletePlugin(@PathParam("name") String name) {
+  public Response deletePlugin(@PathParam("name") String name) {
     try {
       storage.remove(name);
-      return message("Success");
+      return Response.ok().entity(message("Success")).build();
     } catch (PluginException e) {
-      return message(e.getMessage());
+      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+        .entity(message("Error while deleting plugin: %s",  e.getMessage()))
+        .build();
     }
   }
 
@@ -217,13 +231,15 @@ public class StorageResources {
   @Path("/storage/{name}.json")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
-  public JsonResult createOrUpdatePluginJSON(PluginConfigWrapper plugin) {
+  public Response createOrUpdatePluginJSON(PluginConfigWrapper plugin) {
     try {
       plugin.createOrUpdateInStorage(storage);
-      return message("Success");
+      return Response.ok().entity(message("Success")).build();
     } catch (PluginException e) {
       logger.error("Unable to create/ update plugin: " + plugin.getName(), e);
-      return message("Error while saving plugin: %s", e.getMessage());
+      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+        .entity(message("Error while saving plugin: %s ", e.getMessage()))
+        .build();
     }
   }
 
@@ -236,20 +252,27 @@ public class StorageResources {
   @Path("/storage/create_update")
   @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
   @Produces(MediaType.APPLICATION_JSON)
-  public JsonResult createOrUpdatePlugin(@FormParam("name") String name, 
@FormParam("config") String storagePluginConfig) {
+  public Response createOrUpdatePlugin(@FormParam("name") String name, 
@FormParam("config") String storagePluginConfig) {
     name = name.trim();
     if (name.isEmpty()) {
-      return message("Error (a storage name cannot be empty)");
+      return Response.status(Response.Status.BAD_REQUEST)
+        .entity(message("A storage config name may not be empty"))
+        .build();
     }
+
     try {
       storage.putJson(name, storagePluginConfig);
-      return message("Success");
+      return Response.ok().entity(message("Success")).build();
     } catch (PluginEncodingException e) {
       logger.warn("Error in JSON mapping: {}", storagePluginConfig, e);
-      return message("Invalid JSON: " + e.getMessage());
+      return Response.status(Response.Status.BAD_REQUEST)
+        .entity(message("Invalid JSON: %s", e.getMessage()))
+        .build();
     } catch (PluginException e) {
       logger.error("Error while saving plugin", e);
-      return message(e.getMessage());
+      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+        .entity(message("Error while saving plugin: ", e.getMessage()))
+        .build();
     }
   }
 
@@ -310,7 +333,7 @@ public class StorageResources {
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
   @Deprecated
-  public JsonResult createOrUpdatePlugin(PluginConfigWrapper plugin) {
+  public Response createOrUpdatePlugin(PluginConfigWrapper plugin) {
     return createOrUpdatePluginJSON(plugin);
   }
 
@@ -321,7 +344,7 @@ public class StorageResources {
   @Path("/storage/{name}/delete")
   @Produces(MediaType.APPLICATION_JSON)
   @Deprecated
-  public JsonResult deletePluginViaGet(@PathParam("name") String name) {
+  public Response deletePluginViaGet(@PathParam("name") String name) {
     return deletePlugin(name);
   }
 
@@ -341,7 +364,7 @@ public class StorageResources {
 
   /**
    * Model class for Storage Plugin page.
-   * It contains a storage plugin as well as the CSRK token for the page.
+   * It contains a storage plugin as well as the CSRF token for the page.
    */
   public static class StoragePluginModel {
     private final PluginConfigWrapper plugin;
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/profile/ProfileResources.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/profile/ProfileResources.java
index 288df87..127b2bb 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/profile/ProfileResources.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/profile/ProfileResources.java
@@ -36,6 +36,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 import javax.ws.rs.core.UriInfo;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -257,7 +258,7 @@ public class ProfileResources {
   @GET
   @Path("/profiles.json")
   @Produces(MediaType.APPLICATION_JSON)
-  public QProfiles getProfilesJSON(@Context UriInfo uriInfo) {
+  public Response getProfilesJSON(@Context UriInfo uriInfo) {
     try {
       final QueryProfileStoreContext profileStoreContext = 
work.getContext().getProfileStoreContext();
       final PersistentStore<QueryProfile> completed = 
profileStoreContext.getCompletedProfileStore();
@@ -319,7 +320,14 @@ public class ProfileResources {
 
       Collections.sort(finishedQueries, Collections.reverseOrder());
 
-      return new QProfiles(runningQueries, finishedQueries, errors);
+
+      QProfiles qProf = new QProfiles(runningQueries, finishedQueries, errors);
+
+      return errors.size() == 0
+        ? Response.ok().entity(qProf).build()
+        : Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+          .entity(qProf)
+          .build();
     } catch (Exception e) {
       throw UserException.resourceError(e)
       .message("Failed to get profiles from persistent or ephemeral store.")
@@ -331,7 +339,7 @@ public class ProfileResources {
   @Path("/profiles")
   @Produces(MediaType.TEXT_HTML)
   public Viewable getProfiles(@Context UriInfo uriInfo) {
-    QProfiles profiles = getProfilesJSON(uriInfo);
+    QProfiles profiles = (QProfiles) getProfilesJSON(uriInfo).getEntity();
     return ViewableWithPermissions.create(authEnabled.get(), 
"/rest/profile/list.ftl", sc, profiles);
   }
 
@@ -383,19 +391,23 @@ public class ProfileResources {
   @GET
   @Path("/profiles/{queryid}.json")
   @Produces(MediaType.APPLICATION_JSON)
-  public String getProfileJSON(@PathParam("queryid") String queryId) {
+  public Response getProfileJSON(@PathParam("queryid") String queryId) {
     try {
       String profileData = PROFILE_CACHE.getIfPresent(queryId);
       if (profileData == null) {
-        return new String(work.getContext().getProfileStoreContext()
+        profileData = new String(work.getContext().getProfileStoreContext()
           
.getProfileStoreConfig().getSerializer().serialize(getQueryProfile(queryId)));
       } else {
         PROFILE_CACHE.invalidate(queryId);
-        return profileData;
       }
+
+      return Response.ok().entity(profileData).build();
+
     } catch (Exception e) {
       logger.debug("Failed to serialize profile for: " + queryId);
-      return ("{ 'message' : 'error (unable to serialize profile)' }");
+      return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+        .entity("{ 'message' : 'error (unable to serialize profile)' }")
+        .build();
     }
   }
 
diff --git a/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js 
b/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js
index d426f25..dab35d8 100644
--- a/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js
+++ b/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js
@@ -22,6 +22,8 @@ function serverMessage(data) {
         setTimeout(function() { window.location.href = "/storage"; }, 800);
         return true;
     } else {
+           const errorMessage = data.errorMessage || data.result;
+
         messageEl.addClass("d-none");
         // Wait a fraction of a second before showing the message again. This
         // makes it clear if a second attempt gives the same error as
@@ -30,7 +32,7 @@ function serverMessage(data) {
             messageEl.removeClass("d-none")
                 .removeClass("alert-info")
                 .addClass("alert-danger")
-                .text("Please retry: " + data.result).alert();
+                .text("Please retry: " + errorMessage).alert();
         }, 200);
         return false;
     }
diff --git a/exec/java-exec/src/main/resources/rest/storage/list.ftl 
b/exec/java-exec/src/main/resources/rest/storage/list.ftl
index 5d4b6ae..370eaa1 100644
--- a/exec/java-exec/src/main/resources/rest/storage/list.ftl
+++ b/exec/java-exec/src/main/resources/rest/storage/list.ftl
@@ -131,7 +131,8 @@
             </div>
             <div class="radio">
               <label>
-                <input type="radio" name="format" id="hocon" value="conf">
+                <!-- Exporting to HOCON is not currently supported, see 
StorageResources.java. -->
+                <input type="radio" name="format" id="hocon" value="conf" 
disabled>
                 HOCON
               </label>
             </div>
@@ -214,8 +215,13 @@
           if (data.result === "Success") {
             location.reload();
           } else {
-              populateAndShowAlert('pluginEnablingFailure', {'_pluginName_': 
name,'_errorMessage_': data.result});
+            populateAndShowAlert('pluginEnablingFailure', {'_pluginName_': 
name,'_errorMessage_': data.result});
           }
+        }).fail(function(response) {
+          populateAndShowAlert(
+            'pluginEnablingFailure',
+            {'_pluginName_': name,'_errorMessage_': 
response.responseJSON.result}
+          );
         });
       }
     }
@@ -227,7 +233,8 @@
     function doCreate() {
       $("#createForm").ajaxForm({
         dataType: 'json',
-        success: serverMessage
+        success: serverMessage,
+        error: serverMessage
       });
     }
 
@@ -254,7 +261,6 @@
     // Modal windows management
     let exportInstance; // global variable
     $('#pluginsModal').on('show.bs.modal', function(event) {
-        console.log("alarm");
       const button = $(event.relatedTarget); // Button that triggered the modal
       const modal = $(this);
       exportInstance = button.attr("name");
@@ -297,4 +303,4 @@
   <#include "*/alertModals.ftl">
 </#macro>
 
-<@page_html/>
\ No newline at end of file
+<@page_html/>
diff --git a/exec/java-exec/src/main/resources/rest/storage/update.ftl 
b/exec/java-exec/src/main/resources/rest/storage/update.ftl
index 224eb52..285771f 100644
--- a/exec/java-exec/src/main/resources/rest/storage/update.ftl
+++ b/exec/java-exec/src/main/resources/rest/storage/update.ftl
@@ -125,6 +125,10 @@
           if (serverMessage(data)) {
               setTimeout(function() { location.reload(); }, 800);
           }
+        }).fail(function(response) {
+          if (serverMessage(response.responseJSON)) {
+              setTimeout(function() { location.reload(); }, 800);
+          }
         });
       }
     });
@@ -132,13 +136,16 @@
     function doUpdate() {
       $("#updateForm").ajaxForm({
         dataType: 'json',
-        success: serverMessage
+        success: serverMessage,
+        error: serverMessage
       });
     }
 
     function deleteFunction() {
       showConfirmationDialog('"${model.getPlugin().getName()}"' + ' plugin 
will be deleted. Proceed?', function() {
-        $.get("/storage/" + 
encodeURIComponent("${model.getPlugin().getName()}") + "/delete", 
serverMessage);
+        $.get("/storage/" + 
encodeURIComponent("${model.getPlugin().getName()}") + "/delete", 
serverMessage).fail(function() {
+                                       serverMessage({ errorMessage: "Error 
while trying to delete." })
+        });
       });
     }
 
@@ -164,4 +171,4 @@
   </script>
 </#macro>
 
-<@page_html/>
\ No newline at end of file
+<@page_html/>

Reply via email to