Sfoglia il codice sorgente

HUE-1858 [oozie] "Could not save workflow" error message requires substantially more information

Also fix 3 tier graph with decisions and forks.
Improve general error handling.
Abraham Elmahrek 11 anni fa
parent
commit
051e117167

+ 40 - 23
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -588,11 +588,21 @@ ${ controls.decision_form(node_form, link_form, default_link_form, 'decision', T
  * Initialize the workflow, registry, modal, and import objects.
  */
  // Custom handlers for saving, loading, error checking, etc.
+function interpret_server_error(data, premessage) {
+  var message = premessage;
+  if (data) {
+    if (data.message) {
+      message += ": " + data.message;
+    }
+  }
+  return message;
+}
+
 function import_jobsub_load_success(data) {
   if (data.status == 0) {
     import_jobsub_action.initialize(data.data);
   } else {
-    $(document).trigger("error", "${ _('Received invalid response from server: ') } " + JSON.stringify(data));
+    $(document).trigger("error", interpret_server_error(data, "${ _('Received invalid response from server') } "));
   }
 }
 
@@ -600,29 +610,30 @@ function import_workflow_load_success(data) {
   if (data.status == 0) {
     import_workflow_action.initialize(data.data);
   } else {
-    $(document).trigger("error", "${ _('Received invalid response from server: ') } " + JSON.stringify(data));
+    $(document).trigger("error", interpret_server_error(data, "${ _('Received invalid response from server') } "));
   }
 }
 
 function workflow_save_success(data) {
-  $(document).trigger("info", "${ _('Workflow saved') }");
-  workflow.reload(data.data);
-  workflow.is_dirty( false );
-  workflow.loading(false);
-  $("#btn-save-wf").button('reset');
+  if (data.status != 0) {
+    $(document).trigger("error", interpret_server_error(data, "${ _('Could not save workflow') }"));
+  } else {
+    $(document).trigger("info", "${ _('Workflow saved') }");
+    workflow.reload(data.data);
+    workflow.is_dirty( false );
+    workflow.loading(false);
+    $("#btn-save-wf").button('reset');
+  }
 }
 
-function workflow_save_error(data) {
-  try {
-    if (data.status !== 400) {
-      throw Exception();
-    }
-    var response = $.parseJSON(data.responseText);
-    ko.mapping.fromJS(response.data.errors, workflow.errors);
-  } catch(err) {}
-  $(document).trigger("error", "${ _('Could not save workflow') }");
-  workflow.loading(false);
-  $("#btn-save-wf").button('reset');
+function workflow_save_error(jqXHR) {
+  if (jqXHR.status !== 400) {
+    $(document).trigger("error", interpret_server_error(jqXHR.responseJSON, "${ _('Could not save workflow') }"));
+  } else {
+    ko.mapping.fromJS(jqXHR.responseJSON.details.errors, workflow.errors);
+    workflow.loading(false);
+    $("#btn-save-wf").button('reset');
+  }
 }
 
 function workflow_read_only_handler() {
@@ -641,11 +652,17 @@ function workflow_load_success(data) {
     ko.applyBindings(kill_view_model, $('#editKill')[0]);
 
   } else {
-    $(document).trigger("error", "${ _('Received invalid response from server: ') }" + JSON.stringify(data));
+    $(document).trigger("error", interpret_server_error(data, "${ _('Error loading workflow') }"));
   }
   workflow.loading(false);
 }
 
+function workflow_load_error(jqXHR) {
+  var data = jqXHR.responseJSON;
+  $(document).trigger("error", interpret_server_error(jqXHR.responseJSON, "${ _('Error loading workflow') }"));
+  workflow.loading(false);
+}
+
 function save_workflow() {
   workflow.loading(true);
   if (kill_view_model.enabled()) {
@@ -698,7 +715,7 @@ import_workflow_action.fetchWorkflows({ success: import_workflow_load_success })
     }
   });
   workflow.loading(true);
-  workflow.load({ success: workflow_load_success });
+  workflow.load({ success: workflow_load_success, error: workflow_load_error });
 }
 
 /**
@@ -864,7 +881,7 @@ $('#importJobsub').on('click', '.action-row', function(e) {
           routie('editWorkflow');
           $(document).trigger("info", "${ _('Action imported at the top of the workflow.') } ");
         } else {
-          $(document).trigger("error", "${ _('Received invalid response from server: ') } " + JSON.stringify(data));
+          $(document).trigger("error", interpret_server_error(data, "${ _('Received invalid response from server') }"));
         }
       }
     });
@@ -886,7 +903,7 @@ $('#importOozie').on('click', '.action-row', function(e) {
           import_view_model.oozie().initialize({nodes: data.data.actions});
           routie('importAction/oozie');
         } else {
-          $(document).trigger("error", "${ _('Received invalid response from server: ') } " + JSON.stringify(data));
+          $(document).trigger("error", interpret_server_error(data, "${ _('Received invalid response from server') }"));
         }
       }
     });
@@ -903,7 +920,7 @@ $('#importOozieAction').on('click', '.action-row', function(e) {
 
     workflow.el.trigger('workflow:rebuild');
     routie('editWorkflow');
-    $(document).trigger("error", "${ _('Action imported at the top of the workflow.') } ");
+    $(document).trigger("info", "${ _('Action imported at the top of the workflow.') }");
   }
 });
 

+ 57 - 0
apps/oozie/src/oozie/tests.py

@@ -555,6 +555,25 @@ class TestAPI(OozieMockBase):
 
     assert_equal(0, test_response_json_object['status'])
 
+  def test_workflow_fail(self):
+    import oozie.views.api
+    old_method = oozie.views.api._workflow
+    def exception_method(*args, **kwargs):
+      logging.error( 'here' )
+      raise Exception("arg")
+    oozie.views.api._workflow = exception_method
+
+    try:
+      response = self.c.get(reverse('oozie:workflow', kwargs={'workflow': self.wf.pk}))
+      test_response_json = response.content
+      test_response_json_object = json.loads(test_response_json)
+
+      assert_equal(1, test_response_json_object['status'], test_response_json_object)
+      assert_equal('arg', test_response_json_object['message'], test_response_json_object)
+      assert_equal({}, test_response_json_object['details'], test_response_json_object)
+    finally:
+      oozie.views.api._workflow = old_method
+
   def test_workflow_validate_node(self):
     data = {"files":"[\"hive-site.xml\"]","job_xml":"hive-site.xml","description":"Show databases","workflow":17,"child_links":[{"comment":"","name":"ok","id":106,"parent":76,"child":74},{"comment":"","name":"error","id":107,"parent":76,"child":73}],"job_properties":"[{\"name\":\"oozie.hive.defaults\",\"value\":\"hive-site.xml\"}]","node_type":"hive","params":"[{\"value\":\"INPUT=/user/hue/oozie/workspaces/data\",\"type\":\"param\"}]","archives":"[]","node_ptr":76,"prepares":"[]","script_path":"hive.sql","id":76,"name":"Hive"}
     response = self.c.post(reverse('oozie:workflow_validate_node', kwargs={'workflow': self.wf.pk, 'node_type': 'hive'}), data={'node': json.dumps(data)}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
@@ -645,6 +664,25 @@ class TestAPI(OozieMockBase):
     assert_equal(0, response_json_dict['status'])
     assert_equal(0, len(response_json_dict['data']['workflows']))
 
+  def test_workflows_fail(self):
+    # Insert an exception
+    import oozie.utils
+    old_method = oozie.utils.model_to_dict
+    def exception_method(*args, **kwargs):
+      raise Exception("arg")
+    oozie.utils.model_to_dict = exception_method
+
+    try:
+      response = self.c.post(reverse('oozie:workflows') + "?managed=true", HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+      test_response_json = response.content
+      test_response_json_object = json.loads(test_response_json)
+
+      assert_equal(1, test_response_json_object['status'], test_response_json_object)
+      assert_equal("Must be GET request.", test_response_json_object['message'], test_response_json_object)
+      assert_equal({}, test_response_json_object['details'], test_response_json_object)
+    finally:
+      oozie.utils.model_to_dict = old_method
+
   def test_workflow_actions(self):
     response = self.c.get(reverse('oozie:workflow_actions', kwargs={'workflow': self.wf.pk}), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
     response_json_dict = json.loads(response.content)
@@ -657,6 +695,25 @@ class TestAPI(OozieMockBase):
     assert_equal(0, response_json_dict['status'])
     assert_equal(3, len(response_json_dict['data']['actions']))
 
+  def test_workflow_actions_fail(self):
+    # Insert an exception
+    import oozie.utils
+    old_method = oozie.utils.model_to_dict
+    def exception_method(*args, **kwargs):
+      raise Exception("arg")
+    oozie.utils.model_to_dict = exception_method
+
+    try:
+      response = self.c.post(reverse('oozie:workflow_actions', kwargs={'workflow': self.wf.pk}), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+      test_response_json = response.content
+      test_response_json_object = json.loads(test_response_json)
+
+      assert_equal(1, test_response_json_object['status'], test_response_json_object)
+      assert_equal("Must be GET request.", test_response_json_object['message'], test_response_json_object)
+      assert_equal({}, test_response_json_object['details'], test_response_json_object)
+    finally:
+      oozie.utils.model_to_dict = old_method
+
   def test_autocomplete(self):
     response = self.c.get(reverse('oozie:autocomplete_properties'))
     test_response_json = response.content

+ 37 - 1
apps/oozie/src/oozie/views/api.py

@@ -17,11 +17,13 @@
 
 import json
 import logging
+import sys
 
-from django.http import HttpResponse
+from django.http import HttpResponse, Http404
 from django.utils.translation import ugettext as _
 
 from desktop.lib.exceptions import StructuredException
+from desktop.lib.i18n import force_unicode
 from desktop.models import Document
 
 from oozie.forms import WorkflowForm, NodeForm, design_form_by_type
@@ -35,6 +37,35 @@ from oozie.utils import model_to_dict, format_dict_field_values, format_field_va
 LOG = logging.getLogger(__name__)
 
 
+def error_handler(view_fn):
+  def decorator(request, *args, **kwargs):
+    try:
+      return view_fn(request, *args, **kwargs)
+    except Http404, e:
+      raise e
+    except StructuredException, e:
+      error_code = e.error_code
+      message = e.message
+      details = e.data or {}
+    except Exception, e:
+      error_code = 500
+      details = {}
+      (type, value, tb) = sys.exc_info()
+      if not hasattr(e, 'message') or not e.message:
+        message = str(e)
+      else:
+        message = force_unicode(e.message, strings_only=True, errors='replace')
+
+    response = {
+      'status': 1,
+      'message': message,
+      'details': details
+    }
+
+    return HttpResponse(json.dumps(response), mimetype="application/json", status=error_code)
+  return decorator
+
+
 def get_or_create_node(workflow, node_data, save=True):
   node = None
   id = str(node_data['id'])
@@ -265,6 +296,7 @@ def _workflow(request, workflow):
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
+@error_handler
 @check_job_access_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 @check_job_edition_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 def workflow_validate_node(request, workflow, node_type):
@@ -281,6 +313,7 @@ def workflow_validate_node(request, workflow, node_type):
 
 
 # Workflow and child links are SPECIAL.
+@error_handler
 @check_job_access_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 @check_job_edition_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 def workflow_save(request, workflow):
@@ -334,6 +367,7 @@ def workflow_save(request, workflow):
   return _workflow(request, workflow=workflow)
 
 
+@error_handler
 @check_job_access_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 def workflow(request, workflow):
   if request.method != 'GET':
@@ -342,6 +376,7 @@ def workflow(request, workflow):
   return _workflow(request, workflow)
 
 
+@error_handler
 @check_job_access_permission(exception_class=(lambda x: StructuredException(code="UNAUTHORIZED_REQUEST_ERROR", message=x, data=None, error_code=401)))
 def workflow_actions(request, workflow):
   if request.method != 'GET':
@@ -357,6 +392,7 @@ def workflow_actions(request, workflow):
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
+@error_handler
 def workflows(request):
   if request.method not in ['GET']:
     raise StructuredException(code="METHOD_NOT_ALLOWED_ERROR", message=_('Must be GET request.'), error_code=405)

+ 4 - 1
apps/oozie/static/js/workflow.js

@@ -131,7 +131,10 @@ $.extend(ForkNode.prototype, Node.prototype, {
     }
 
     var links = self.links().filter(function(element, index, arr) {
-      return self.registry.get(element.child()).node_type() != 'join';
+      var node = self.registry.get(element.child());
+      // If no node is found, it means that it's a temporary node that may be added to the graph.
+      // This will not be a join. It will most likely be a decision node.
+      return !node || node.node_type() != 'join';
     });
 
     if (links.length < 2) {