Browse Source

HUE-818 [oozie] Resubmit a coordinator from a certain day

A popup shows a list of actions ready to be rerun.
Add the status coloring of each action when clicking on the rerun button
Fix progress bar when rerunning some actions
Fix coloring of action ready to be rerun
Add some tests but no real rerun of a coordinator
Support suspend and resume for a coordinator
Support user parameters in rerun form
Readonly users can't resubmit jobs
Romain Rigaux 13 years ago
parent
commit
1c8b114898

+ 14 - 0
apps/oozie/src/oozie/forms.py

@@ -29,6 +29,7 @@ from oozie.models import Workflow, Node, Java, Mapreduce, Streaming, Coordinator
   Dataset, DataInput, DataOutput, Pig, Link, Hive, Sqoop, Ssh, Shell, DistCp, Fs,\
   Email, SubWorkflow, Generic
 
+
 LOG = logging.getLogger(__name__)
 
 
@@ -437,6 +438,19 @@ class RerunForm(forms.Form):
     self.fields['skip_nodes'].initial = initial_skip_nodes
 
 
+class RerunCoordForm(forms.Form):
+  refresh = forms.BooleanField(initial=True, required=False, help_text=_t('Used to indicate if user wants to cleanup output events for given rerun actions'))
+  nocleanup = forms.BooleanField(initial=True, required=False, help_text=_t("Used to indicate if user wants to refresh an action's input and output events"))
+  actions = forms.MultipleChoiceField(required=True)
+
+  def __init__(self, *args, **kwargs):
+    oozie_coordinator = kwargs.pop('oozie_coordinator')
+
+    super(RerunCoordForm, self).__init__(*args, **kwargs)
+
+    self.fields['actions'].choices = [(action.actionNumber, action.title) for action in reversed(oozie_coordinator.get_working_actions())]
+
+
 def design_form_by_type(node_type, user, workflow):
   klass_form = _node_type_TO_FORM_CLS[node_type]
 

+ 2 - 2
apps/oozie/src/oozie/import_workflow.py

@@ -423,7 +423,7 @@ def _preprocess_nodes(workflow, transformed_root, workflow_definition_root, node
   preprocess nodes
   Resolve start name and subworkflow dependencies.
   Looks at path and interrogates all workflows until the proper deployment path is found.
-  If the proper deployment path is never found, then 
+  If the proper deployment path is never found, then
   """
   for full_node in nodes:
     if full_node.node_type is 'start':
@@ -452,7 +452,7 @@ def _resolve_subworkflow_from_deployment_dir(fs, workflow, app_path):
   """
   Resolves subworkflow in a subworkflow node
   Looks at path and interrogates all workflows until the proper deployment path is found.
-  If the proper deployment path is never found, then 
+  If the proper deployment path is never found, then
   """
   if not fs:
     raise RuntimeError(_("No hadoop file system to operate on."))

+ 94 - 43
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako

@@ -41,7 +41,7 @@ ${ layout.menubar(section='dashboard') }
             % if coordinator is not None:
               <a href="${ coordinator.get_absolute_url() }">${ oozie_coordinator.appName }</a>
             % else:
-            ${ oozie_coordinator.appName }
+              ${ oozie_coordinator.appName }
             % endif
         </li>
 
@@ -80,25 +80,51 @@ ${ layout.menubar(section='dashboard') }
         % if has_job_edition_permission(oozie_coordinator, user):
           <li class="nav-header">${ _('Manage') }</li>
           <li>
-          <form action="${ url('oozie:resubmit_coordinator', oozie_coord_id=oozie_coordinator.id) }" method="post">
-              <button type="button" title="${_('Kill %(coordinator)s') % dict(coordinator=oozie_coordinator.id)}"
-                 id="kill-btn"
-                 class="btn btn-small btn-danger confirmationModal"
-                 alt="${ _('Are you sure you want to kill coordinator %s?') % oozie_coordinator.id }"
-                 href="javascript:void(0)"
-                 data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='kill') }"
-                 data-message="${ _('The coordinator was killed!') }"
-                 data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
-              ${_('Kill')}
-              </button>
-              <button id="resubmit-btn" type="submit" class="btn btn-small
-              % if oozie_coordinator.is_running():
-                hide
-              % endif
-              ">
-              ${ _('Resubmit') }
-              </button>
-          </form>
+            <button title="${_('Kill %(coordinator)s') % dict(coordinator=oozie_coordinator.id)}"
+              id="kill-btn"
+              class="btn btn-small confirmationModal
+               % if not oozie_coordinator.is_running():
+                 hide
+               % endif
+              "
+              alt="${ _('Are you sure you want to kill coordinator %s?') % oozie_coordinator.id }"
+              href="javascript:void(0)"
+              data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='kill') }"
+              data-message="${ _('The coordinator was killed!') }"
+              data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
+                ${_('Kill')}
+            </button>
+            <button class="btn btn-small
+               % if oozie_coordinator.is_running() or oozie_coordinator.status in ('KILLED', 'FAILED'):
+                 hide
+               % endif
+            "
+              id="rerun-btn"
+              data-rerun-url="${ url('oozie:rerun_oozie_coord', job_id=oozie_coordinator.id, app_path=oozie_coordinator.coordJobPath) }"
+            >
+              ${ _('Rerun') }
+            </button>
+            <div id="rerun-coord-modal" class="modal hide"></div>
+            <button title="${ _('Suspend the coordinator after finishing the current running actions') }" id="suspend-btn"
+               data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='suspend') }"
+               data-confirmation-message="${ _('Are you sure you\'d like to suspend this job?') }"
+               class="btn btn-small confirmationModal
+               % if not oozie_coordinator.is_running():
+                 hide
+               % endif
+               " rel="tooltip" data-placement="right">
+              ${ _('Suspend') }
+            </button>
+            <button title="${ _('Resume the coordinator') }" id="resume-btn"
+               data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='resume') }"
+               data-confirmation-message="${ _('Are you sure you\'d like to resume this job?') }"
+               class="btn btn-small confirmationModal
+               % if oozie_coordinator.is_running():
+                 hide
+               % endif
+               ">
+              ${ _('Resume') }
+            </button>
           </li>
         % endif
       </ul>
@@ -123,7 +149,6 @@ ${ layout.menubar(section='dashboard') }
           </tr>
           </thead>
           <tbody data-bind="template: {name: 'calendarTemplate', foreach: actions}">
-
           </tbody>
           <tfoot>
             <tr data-bind="visible: isLoading()">
@@ -143,21 +168,18 @@ ${ layout.menubar(section='dashboard') }
       </div>
 
       <script id="calendarTemplate" type="text/html">
-
         <tr>
           <td>
-            <a data-bind="visible:externalId !='', attr: { href: externalIdUrl}" data-row-selector="true"></a>
-            <span data-bind="text: nominalTime, attr: {'class': statusClass}"></span>
+            <a data-bind="attr: {href: url}" data-row-selector="true">
+              <span data-bind="text: title, attr: {'class': statusClass, 'id': 'date-' + $index()}"></span>
+            </a>
           </td>
           <td><span data-bind="text: errorMessage"></span> <span data-bind="visible:missingDependencies !='', text: '${ _('Missing')}' + missingDependencies"></span></td>
-
         </tr>
-
       </script>
 
 
       <div class="tab-pane" id="actions">
-
         <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
           <thead>
           <tr>
@@ -179,7 +201,6 @@ ${ layout.menubar(section='dashboard') }
           </thead>
 
           <tbody data-bind="template: {name: 'actionTemplate', foreach: actions}">
-
           </tbody>
 
           <tfoot>
@@ -200,7 +221,6 @@ ${ layout.menubar(section='dashboard') }
       </div>
 
       <script id="actionTemplate" type="text/html">
-
         <tr>
           <td data-bind="text: number"></td>
           <td data-bind="text: nominalTime"></td>
@@ -210,17 +230,13 @@ ${ layout.menubar(section='dashboard') }
           <td data-bind="text: missingDependencies"></td>
           <td data-bind="text: createdTime"></td>
           <td data-bind="text: lastModifiedTime"></td>
-
           <td>
-            <a data-bind="visible:externalId !='', attr: { href: url}, text: id" data-row-selector"true"></a>
+            <a data-bind="visible:externalId !='', attr: {href: url}, text: id" data-row-selector"true"></a>
           </td>
-
           <td>
-            <a data-bind="visible:externalId !='', attr: { href: externalIdUrl}, text: externalId"></a>
+            <a data-bind="visible:externalId !='', attr: {href: externalIdUrl}, text: externalId"></a>
           </td>
-
         </tr>
-
       </script>
 
       <div class="tab-pane" id="configuration">
@@ -294,12 +310,9 @@ ${ layout.menubar(section='dashboard') }
     if (['SUCCEEDED', 'OK'].indexOf(status) > -1){
       klass = prefix + "success";
     }
-    else if (['RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED'].indexOf(status) > -1){
+    else if (['READY', 'RUNNING', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED', 'SUBMITTED'].indexOf(status) > -1){
       klass = prefix + "warning";
     }
-    else if (status == 'READY'){
-      klass = prefix + "success";
-    }
     else {
       klass = prefix + "important";
       if (prefix == "bar-"){
@@ -319,6 +332,7 @@ ${ layout.menubar(section='dashboard') }
       statusClass: "label " + getStatusClass(action.status),
       externalId: action.externalId,
       externalIdUrl: action.externalIdUrl,
+      title: action.title,
       nominalTime: action.nominalTime,
       createdTime: action.createdTime,
       lastModifiedTime: action.lastModifiedTime,
@@ -333,7 +347,6 @@ ${ layout.menubar(section='dashboard') }
     self.actions = ko.observableArray(ko.utils.arrayMap(actions), function (action) {
       return new Action(action);
     });
-
   };
 
   var viewModel = new RunningCoordinatorActionsModel([]);
@@ -393,12 +406,37 @@ ${ layout.menubar(section='dashboard') }
       return false;
     });
 
+    $("#suspend-btn").bind('confirmation', function() {
+      var _this = this;
+      $.post($(this).data("url"),
+        { 'notification': $(this).data("message") },
+        function(response) {
+          if (response['status'] != 0) {
+            $.jHueNotify.error("${ _('Error: ') }" + response['data']);
+          } else {
+            window.location.reload();
+          }
+        }
+      );
+      return false;
+    });
+
+    $('#rerun-btn').click(function() {
+      var _action = $(this).data("rerun-url");
+
+      $.get(_action, function(response) {
+          $('#rerun-coord-modal').html(response);
+          $('#rerun-coord-modal').modal('show');
+        }
+      );
+     });
+
     resizeLogs();
     refreshView();
     var logsAtEnd = true;
 
     function refreshView() {
-      $.getJSON(window.location.href + "?format=json", function (data) {
+      $.getJSON("${ oozie_coordinator.get_absolute_url() }" + "?format=json", function (data) {
         viewModel.isLoading(false);
         if (data.actions){
           viewModel.actions(ko.utils.arrayMap(data.actions, function (action) {
@@ -408,9 +446,22 @@ ${ layout.menubar(section='dashboard') }
 
         $("#status span").attr("class", "label").addClass(getStatusClass(data.status)).text(data.status);
 
-        if (data.id && data.status != "RUNNING" && data.status != "SUSPENDED"){
+        if (data.id && data.status != "RUNNING" && data.status != "SUSPENDED" && data.status != "KILLED" && data.status != "FAILED"){
           $("#kill-btn").hide();
-          $("#resubmit-btn").show();
+          $("#rerun-btn").show();
+        }
+
+        if (data.id && (data.status == "RUNNING" || data.status == "RUNNINGWITHERROR")){
+          $("#suspend-btn").show();
+        } else {
+          $("#suspend-btn").hide();
+        }
+
+        if (data.id && (data.status == "SUSPENDED" || data.status == "SUSPENDEDWITHERROR" || data.status == "SUSPENDEDWITHERROR"
+            || data.status == "PREPSUSPENDED")){
+          $("#resume-btn").show();
+        } else {
+          $("#resume-btn").hide();
         }
 
         $("#progress .bar").text(data.progress+"%").css("width", data.progress+"%").attr("class", "bar "+getStatusClass(data.status, "bar-"));

+ 1 - 1
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -90,7 +90,7 @@ ${ layout.menubar(section='dashboard') }
               <li>
                 <button title="${_('Kill %(workflow)s') % dict(workflow=oozie_workflow.id)}"
                    id="kill-btn"
-                   class="btn btn-small btn-danger confirmationModal
+                   class="btn btn-small confirmationModal
                    % if not oozie_workflow.is_running():
                      hide
                    % endif

+ 109 - 0
apps/oozie/src/oozie/templates/dashboard/rerun_coord_popup.mako

@@ -0,0 +1,109 @@
+## Licensed to Cloudera, Inc. under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  Cloudera, Inc. licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##     http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+<%!
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="utils" file="../utils.inc.mako" />
+
+
+<form action="${ action }" method="POST">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h2>${ _('Rerun this job?') }</h2>
+  </div>
+
+  <div class="modal-body">
+    <fieldset>
+      <div id="config-container">
+        <div class="fieldWrapper">
+          <div class="row-fluid">
+            <h3>${ _('Select actions to rerun') }</h3>
+          </div>
+          <div class="row-fluid">
+            ${ utils.render_field(rerun_form['actions'], show_label=False) }
+          </div>
+          <div class="row-fluid">
+            <div class="span6">
+              ${ utils.render_field_no_popover(rerun_form['refresh'], show_label=True) }
+            </div>
+            <div class="span6">
+              ${ utils.render_field_no_popover(rerun_form['nocleanup'], show_label=True) }
+            </div>
+          </div>
+<div id="param-container">
+        ${ params_form.management_form | n,unicode }
+
+        % if params_form.forms:
+          % if len(params_form.forms) > 1:
+            <h3>${ _('Variables') }</h3>
+          % endif
+          % for form in params_form.forms:
+            % for hidden in form.hidden_fields():
+              ${ hidden | n,unicode }
+            % endfor
+            <div class="fieldWrapper">
+              <div class="row-fluid
+                % if form['name'].form.initial.get('name') == 'oozie.use.system.libpath':
+                  hide
+                % endif
+                ">
+                <div class="span6">
+                  ${ form['name'].form.initial.get('name') }
+                </div>
+                <div class="span6">
+                  ${ utils.render_field(form['value'], show_label=False) }
+                </div>
+              </div>
+            </div>
+          % endfor
+        % endif
+      </div>
+    </fieldset>
+        </div>
+      </div>
+    </fieldset>
+  </div>
+
+  <div class="modal-footer">
+    <a href="#" class="btn secondary" data-dismiss="modal">${ _('Cancel') }</a>
+    <input id="submit-btn" type="submit" class="btn btn-primary" value="${ _('Submit') }"/>
+  </div>
+</form>
+
+<script type="text/javascript" charset="utf-8">
+  $(document).ready(function(){
+    var ViewModel = function() {
+      var self = this;
+    };
+
+    window.viewModel = new ViewModel();
+
+    $("#id_actions").jHueSelector({
+      selectAllLabel: "${_('Select all')}",
+      searchPlaceholder: "${_('Search')}",
+      noChoicesFound: "${_('No successful actions found.')}",
+      width:524,
+      height:200
+    });
+
+    // Update status color of each date
+    $(".jHueSelectorBody ul li label").each(function(index) {
+      $(this).addClass($("#date-" + index).attr('class'));
+    });
+  });
+</script>

+ 1 - 1
apps/oozie/src/oozie/templates/editor/create_coordinator.mako

@@ -39,7 +39,7 @@ ${ layout.menubar(section='coordinators') }
   .sidebar-nav {
     padding: 9px 0;
   }
-  
+
 </style>
 
 <script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>

File diff suppressed because it is too large
+ 0 - 0
apps/oozie/src/oozie/tests.py


+ 1 - 1
apps/oozie/src/oozie/urls.py

@@ -42,7 +42,6 @@ urlpatterns = patterns(
   url(r'^edit_coordinator_dataset/(?P<dataset>\d+)$', 'edit_coordinator_dataset', name='edit_coordinator_dataset'),
   url(r'^create_coordinator_data/(?P<coordinator>[-\w]+)/(?P<data_type>(input|output))$', 'create_coordinator_data', name='create_coordinator_data'),
   url(r'^submit_coordinator/(?P<coordinator>\d+)$', 'submit_coordinator', name='submit_coordinator'),
-  url(r'^resubmit_coordinator/(?P<oozie_coord_id>[-\w]+)$', 'resubmit_coordinator', name='resubmit_coordinator'),
 
   url(r'^list_history$', 'list_history', name='list_history'),
   url(r'^list_history/(?P<record_id>[-\w]+)$', 'list_history_record', name='list_history_record'),
@@ -73,5 +72,6 @@ urlpatterns += patterns(
   url(r'^list_oozie_coordinator/(?P<job_id>[-\w]+)$', 'list_oozie_coordinator', name='list_oozie_coordinator'),
   url(r'^list_oozie_workflow_action/(?P<action>[-\w@]+)$', 'list_oozie_workflow_action', name='list_oozie_workflow_action'),
   url(r'^rerun_oozie_job/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_job', name='rerun_oozie_job'),
+  url(r'^rerun_oozie_coord/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_coordinator', name='rerun_oozie_coord'),
   url(r'^manage_oozie_jobs/(?P<job_id>[-\w]+)/(?P<action>(start|suspend|resume|kill|rerun))$', 'manage_oozie_jobs', name='manage_oozie_jobs'),
 )

+ 61 - 4
apps/oozie/src/oozie/views/dashboard.py

@@ -35,7 +35,7 @@ from desktop.lib.rest.http_client import RestException
 from desktop.log.access import access_warn
 from liboozie.oozie_api import get_oozie
 from liboozie.submittion import Submission
-from oozie.forms import RerunForm, ParameterForm
+from oozie.forms import RerunForm, ParameterForm, RerunCoordForm
 
 
 from oozie.conf import OOZIE_JOBS_COUNT
@@ -220,6 +220,7 @@ def list_oozie_workflow_action(request, action):
 def rerun_oozie_job(request, job_id, app_path):
   ParametersFormSet = formset_factory(ParameterForm, extra=0)
   oozie_workflow = check_job_access_permission(request, job_id)
+  check_job_edition_permission(oozie_workflow, request.user)
 
   if request.method == 'POST':
     rerun_form = RerunForm(request.POST, oozie_workflow=oozie_workflow)
@@ -238,7 +239,7 @@ def rerun_oozie_job(request, job_id, app_path):
 
       _rerun_workflow(request, job_id, args, mapping)
 
-      request.info(_('Workflow re-running.'))
+      request.info(_('Workflow re-running!'))
       return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
     else:
       request.error(_('Invalid submission form: %s %s' % (rerun_form.errors, params_form.errors)))
@@ -262,7 +263,61 @@ def _rerun_workflow(request, oozie_id, run_args, mapping):
     job_id = submission.rerun(**run_args)
     return job_id
   except RestException, ex:
-    raise PopupException(_("Error rerunning workflow %s") % (oozie_id,),
+    raise PopupException(_("Error re-running workflow %s") % (oozie_id,),
+                         detail=ex._headers.get('oozie-error-message', ex))
+
+
+@show_oozie_error
+def rerun_oozie_coordinator(request, job_id, app_path):
+  oozie_coordinator = check_job_access_permission(request, job_id)
+  check_job_edition_permission(oozie_coordinator, request.user)
+  ParametersFormSet = formset_factory(ParameterForm, extra=0)
+
+  if request.method == 'POST':
+    params_form = ParametersFormSet(request.POST)
+    rerun_form = RerunCoordForm(request.POST, oozie_coordinator=oozie_coordinator)
+
+    if sum([rerun_form.is_valid(), params_form.is_valid()]) == 2:
+      args = {}
+      args['deployment_dir'] = app_path
+
+      params = {
+        'type': 'action',
+        'scope': ','.join(oozie_coordinator.aggreate(rerun_form.cleaned_data['actions'])),
+        'refresh': rerun_form.cleaned_data['refresh'],
+        'nocleanup': rerun_form.cleaned_data['nocleanup'],
+      }
+
+      properties = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
+
+      _rerun_coordinator(request, job_id, args, params, properties)
+
+      request.info(_('Coordinator re-running!'))
+      return redirect(reverse('oozie:list_oozie_coordinator', kwargs={'job_id': job_id}))
+    else:
+      request.error(_('Invalid submission form: %s' % (rerun_form.errors,)))
+      return list_oozie_coordinator(request, job_id)
+  else:
+    rerun_form = RerunCoordForm(oozie_coordinator=oozie_coordinator)
+    initial_params = ParameterForm.get_initial_params(oozie_coordinator.conf_dict)
+    params_form = ParametersFormSet(initial=initial_params)
+
+  popup = render('dashboard/rerun_coord_popup.mako', request, {
+                   'rerun_form': rerun_form,
+                   'params_form': params_form,
+                   'action': reverse('oozie:rerun_oozie_coord', kwargs={'job_id': job_id, 'app_path': app_path}),
+                 }, force_template=True).content
+
+  return HttpResponse(json.dumps(popup), mimetype="application/json")
+
+
+def _rerun_coordinator(request, oozie_id, args, params, properties):
+  try:
+    submission = Submission(user=request.user, fs=request.fs, oozie_id=oozie_id, properties=properties)
+    job_id = submission.rerun_coord(params=params, **args)
+    return job_id
+  except RestException, ex:
+    raise PopupException(_("Error re-running coordinator %s") % (oozie_id,),
                          detail=ex._headers.get('oozie-error-message', ex))
 
 
@@ -303,12 +358,14 @@ def massaged_coordinator_actions_for_json(coordinator):
       'externalId': action.externalId or '-',
       'externalIdUrl': action.externalId and reverse('oozie:list_oozie_workflow_action', kwargs={'action': action.externalId}) or '',
       'nominalTime': format_time(action.nominalTime),
+      'title': action.title,
       'createdTime': format_time(action.createdTime),
       'lastModifiedTime': format_time(action.lastModifiedTime),
       'errorMessage': escapejs(action.errorMessage),
       'missingDependencies': escapejs(action.missingDependencies)
     }
-    actions.append(massaged_action)
+
+    actions.insert(0, massaged_action)
 
   return actions
 

+ 0 - 15
apps/oozie/src/oozie/views/editor.py

@@ -492,21 +492,6 @@ def _submit_coordinator(request, coordinator, mapping):
                          detail=ex._headers.get('oozie-error-message', ex))
 
 
-def resubmit_coordinator(request, oozie_coord_id):
-  if request.method != 'POST':
-    raise PopupException(_('A POST request is required.'))
-
-  history = History.objects.get(oozie_job_id=oozie_coord_id)
-  Job.objects.is_accessible_or_exception(request, history.job.id)
-
-  coordinator = history.get_coordinator().get_full_node()
-  properties = history.properties_dict
-  job_id = _submit_coordinator(request, coordinator, properties)
-
-  request.info(_('Coordinator re-submitted'))
-  return redirect(reverse('oozie:list_oozie_coordinator', kwargs={'job_id': job_id}))
-
-
 def list_history(request):
   """
   List the job submission history.

+ 12 - 5
desktop/libs/liboozie/src/liboozie/oozie_api.py

@@ -202,19 +202,22 @@ class OozieApi(object):
     resp = self._root.get('job/%s' % (action_id,), params)
     return Klass(resp)
 
-  def job_control(self, jobid, action):
+  def job_control(self, jobid, action, properties=None, parameters=None):
     """
     job_control(jobid, action) -> None
     Raise RestException on error.
     """
-    if action not in ('start', 'suspend', 'resume', 'kill'):
+    if action not in ('start', 'suspend', 'resume', 'kill', 'rerun', 'coord-rerun'):
       msg = 'Invalid oozie job action: %s' % (action,)
       LOG.error(msg)
       raise ValueError(msg)
+    properties = self._get_oozie_properties(properties)
     params = self._get_params()
     params['action'] = action
+    if parameters is not None:
+      params.update(parameters)
 
-    return self._root.put('job/%s' % jobid, params)
+    return self._root.put('job/%s' % jobid, params,  data=config_gen(properties), contenttype=_XML_CONTENT_TYPE)
 
 
   def submit_workflow(self, application_path, properties=None):
@@ -254,9 +257,13 @@ class OozieApi(object):
     resp = self._root.post('jobs', params, data=config_gen(properties), contenttype=_XML_CONTENT_TYPE)
     return resp['id']
 
-  def rerun(self, jobid, properties=None):
+  def rerun(self, jobid, properties=None, params=None):
     properties = self._get_oozie_properties(properties)
-    params = self._get_params()
+    if params is None:
+      params = self._get_params()
+    else:
+      self._get_params().update(params)
+
     params['action'] = 'rerun'
 
     return self._root.put('job/%s' % jobid, params, data=config_gen(properties), contenttype=_XML_CONTENT_TYPE)

+ 16 - 0
desktop/libs/liboozie/src/liboozie/submittion.py

@@ -110,6 +110,22 @@ class Submission(object):
     return self.oozie_id
 
 
+  def rerun_coord(self, deployment_dir, params):
+    jobtracker = cluster.get_cluster_addr_for_job_submission()
+
+    try:
+      prev = get_oozie().setuser(self.user.username)
+      self._update_properties(jobtracker, deployment_dir)
+      self.properties.update({'oozie.coord.application.path': deployment_dir})
+
+      get_oozie().job_control(self.oozie_id, action='coord-rerun', properties=self.properties, parameters=params)
+      LOG.info("Rerun: %s" % (self,))
+    finally:
+      get_oozie().setuser(prev)
+
+    return self.oozie_id
+
+
   def deploy(self):
     try:
       deployment_dir = self._create_deployment_dir()

+ 6 - 1
desktop/libs/liboozie/src/liboozie/tests.py

@@ -20,7 +20,7 @@ import logging
 from nose.tools import assert_equal
 from oozie.tests import MockOozieApi
 
-from liboozie.types import WorkflowAction
+from liboozie.types import WorkflowAction, Coordinator
 
 
 LOG = logging.getLogger(__name__)
@@ -31,3 +31,8 @@ def test_valid_external_id():
   assert_equal(None, WorkflowAction(MockOozieApi.JSON_WORKFLOW_LIST[1]).externalId)
   assert_equal(None, WorkflowAction(MockOozieApi.JSON_WORKFLOW_LIST[2]).externalId)
   assert_equal(None, WorkflowAction(MockOozieApi.JSON_WORKFLOW_LIST[3]).externalId)
+
+
+def aggregate_coordinator_instances():
+  dates = ['1', '2', '3', '6', '7', '8', '10', '12', '15', '16', '20', '23', '30', '40']
+  assert_equal(['1-3', '6-8', '10-10', '12-12', '15-16', '20-20', '23-23', '30-30', '40-40'], Coordinator.aggreate(dates))

+ 29 - 3
desktop/libs/liboozie/src/liboozie/types.py

@@ -32,7 +32,7 @@ from desktop.lib.exceptions_renderable import PopupException
 from desktop.log.access import access_warn
 
 import hadoop.confparse
-from liboozie.utils import parse_timestamp
+from liboozie.utils import parse_timestamp, format_time
 
 from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
@@ -152,6 +152,7 @@ class CoordinatorAction(Action):
     else:
       self.conf_dict = {}
 
+    self.title = ' %s-%s'% (self.actionNumber, format_time(self.nominalTime))
 
 class WorkflowAction(Action):
   _ATTRS = [
@@ -353,9 +354,34 @@ class Coordinator(Job):
     end = mktime(self.endTime)
 
     if end != start:
-      return min(int((1 - (end - next) / (end - start)) * 100), 100)
+      progress = min(int((1 - (end - next) / (end - start)) * 100), 100)
     else:
-      return 100
+      progress = 100
+
+    # Manage case of a rerun
+    action_count = float(len(self.actions))
+    if action_count != 0 and progress == 100:
+      progress = int(sum([action.is_finished() for action in self.actions]) / action_count * 100)
+
+    return progress
+
+  @classmethod
+  def aggreate(cls, actions):
+    if not actions:
+      return []
+
+    result = []
+    first = prev = actions[0]
+
+    for a in actions[1:]:
+      if int(a) != int(prev) + 1:
+        result.append('-'.join((first, prev)))
+        first = a
+      prev = a
+
+    result.append('-'.join((first, prev)))
+
+    return result
 
 
 class Workflow(Job):

Some files were not shown because too many files changed in this diff