Browse Source

HUE-1043 [oozie] Rerun a bundle

Romain Rigaux 12 years ago
parent
commit
356f85b

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

@@ -16,6 +16,7 @@
 # limitations under the License.
 # limitations under the License.
 
 
 import logging
 import logging
+from datetime import datetime,  timedelta
 
 
 from django import forms
 from django import forms
 from django.db.models import Q
 from django.db.models import Q
@@ -452,6 +453,26 @@ class RerunCoordForm(forms.Form):
     self.fields['actions'].choices = [(action.actionNumber, action.title) for action in reversed(oozie_coordinator.get_working_actions())]
     self.fields['actions'].choices = [(action.actionNumber, action.title) for action in reversed(oozie_coordinator.get_working_actions())]
 
 
 
 
+class RerunBundleForm(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"))
+  coordinators = forms.MultipleChoiceField(required=True)
+  start = forms.SplitDateTimeField(input_time_formats=[TIME_FORMAT], required=False, initial=datetime.today(),
+                                   widget=SplitDateTimeWidget(attrs={'class': 'input-small', 'id': 'rerun_start'},
+                                                              date_format=DATE_FORMAT, time_format=TIME_FORMAT))
+  end = forms.SplitDateTimeField(input_time_formats=[TIME_FORMAT], required=False, initial=datetime.today() + timedelta(days=3),
+                                 widget=SplitDateTimeWidget(attrs={'class': 'input-small', 'id': 'rerun_end'},
+                                                            date_format=DATE_FORMAT, time_format=TIME_FORMAT))
+
+  def __init__(self, *args, **kwargs):
+    oozie_bundle = kwargs.pop('oozie_bundle')
+
+    super(RerunBundleForm, self).__init__(*args, **kwargs)
+
+    self.fields['coordinators'].choices = [(action.name, action.name) for action in reversed(oozie_bundle.actions)]
+    self.fields['coordinators'].initial = [action.name for action in reversed(oozie_bundle.actions)]
+
+
 class BundledCoordinatorForm(forms.ModelForm):
 class BundledCoordinatorForm(forms.ModelForm):
 
 
   def __init__(self, *args, **kwargs):
   def __init__(self, *args, **kwargs):

+ 13 - 3
apps/oozie/src/oozie/templates/dashboard/list_oozie_bundle.mako

@@ -62,10 +62,10 @@ ${ layout.menubar(section='dashboard') }
         <li>${ oozie_bundle.kickoffTime }</li>
         <li>${ oozie_bundle.kickoffTime }</li>
 
 
         <li class="nav-header">${ _('Created time') }</li>
         <li class="nav-header">${ _('Created time') }</li>
-        <li id="endTime">${ oozie_bundle.createdTime }</li>
+        <li>${ oozie_bundle.createdTime }</li>
 
 
         % if bundle:
         % if bundle:
-            <li class="nav-header">${ _('Bundles') }</li>
+            <li class="nav-header">${ _('Coordinators') }</li>
           % for bundled in bundle.coordinators.all():
           % for bundled in bundle.coordinators.all():
             <li rel="tooltip" title="${ bundled.coordinator.name }">
             <li rel="tooltip" title="${ bundled.coordinator.name }">
               <i class="icon-eye-open"></i> <span class="dataset">${ bundled.coordinator.name }</span>
               <i class="icon-eye-open"></i> <span class="dataset">${ bundled.coordinator.name }</span>
@@ -90,6 +90,16 @@ ${ layout.menubar(section='dashboard') }
               data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
               data-confirmation-message="${ _('Are you sure you\'d like to kill this job?') }">
                 ${_('Kill')}
                 ${_('Kill')}
             </button>
             </button>
+            <button class="btn btn-small
+               % if oozie_bundle.is_running() or oozie_bundle.status in ('KILLED', 'FAILED'):
+                 hide
+               % endif
+            "
+              id="rerun-btn"
+              data-rerun-url="${ url('oozie:rerun_oozie_bundle', job_id=oozie_bundle.id, app_path=oozie_bundle.bundleJobPath) }"
+            >
+              ${ _('Rerun') }
+            </button>
             <div id="rerun-coord-modal" class="modal hide"></div>
             <div id="rerun-coord-modal" class="modal hide"></div>
             <button title="${ _('Suspend the bundle after finishing the current running actions') }" id="suspend-btn"
             <button title="${ _('Suspend the bundle after finishing the current running actions') }" id="suspend-btn"
                data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_bundle.id, action='suspend') }"
                data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_bundle.id, action='suspend') }"
@@ -130,7 +140,7 @@ ${ layout.menubar(section='dashboard') }
         <table class="table table-striped table-condensed">
         <table class="table table-striped table-condensed">
           <thead>
           <thead>
           <tr>
           <tr>
-            <th>${ _('Day') }</th>
+            <th>${ _('Coordinator') }</th>
             <th>${ _('Last action') }</th>
             <th>${ _('Last action') }</th>
             <th>${ _('Next materialization') }</th>
             <th>${ _('Next materialization') }</th>
           </tr>
           </tr>

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

@@ -73,6 +73,9 @@ ${ layout.menubar(section='dashboard') }
             </div>
             </div>
           </li>
           </li>
 
 
+          <li class="nav-header">${ _('Last Modification time') }</li>
+          <li>${  utils.format_time(oozie_workflow.lastModTime) }</li>
+
           % if parameters and len(parameters) < 10:
           % if parameters and len(parameters) < 10:
               <li class="nav-header">${ _('Variables') }</li>
               <li class="nav-header">${ _('Variables') }</li>
               % for var, value in parameters.iteritems():
               % for var, value in parameters.iteritems():
@@ -257,6 +260,10 @@ ${ layout.menubar(section='dashboard') }
               <td>${ _('Application Path') }</td>
               <td>${ _('Application Path') }</td>
               <td>${  utils.hdfs_link(oozie_workflow.appPath) }</td>
               <td>${  utils.hdfs_link(oozie_workflow.appPath) }</td>
             </tr>
             </tr>
+            <tr>
+              <td>${ _('Run') }</td>
+              <td>${  oozie_workflow.run }</td>
+            </tr>
             </tbody>
             </tbody>
           </table>
           </table>
         </div>
         </div>
@@ -330,7 +337,8 @@ ${ layout.menubar(section='dashboard') }
       errorMessage: action.errorMessage,
       errorMessage: action.errorMessage,
       errorCode: action.errorCode,
       errorCode: action.errorCode,
       transition: action.transition,
       transition: action.transition,
-      data: action.data
+      data: action.data,
+      lastModTime: action.lastModTime
     }
     }
   }
   }
 
 

+ 113 - 0
apps/oozie/src/oozie/templates/dashboard/rerun_bundle_popup.mako

@@ -0,0 +1,113 @@
+## 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 coordinators to rerun') }</h3>
+          </div>
+          <div class="row-fluid">
+            ${ utils.render_field(rerun_form['coordinators'], show_label=False) }
+          </div>
+          <div class="row-fluid">
+            <div class="span6">
+              ${ utils.render_field_no_popover(rerun_form['start'], show_label=True) }
+            </div>
+            <div class="span6">
+              ${ utils.render_field_no_popover(rerun_form['end'], show_label=True) }
+            </div>
+          </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>
+        </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_coordinators").jHueSelector({
+      selectAllLabel: "${_('Select all')}",
+      searchPlaceholder: "${_('Search')}",
+      noChoicesFound: "${_('No successful actions found.')}",
+      width:524,
+      height:200
+    });
+  });
+</script>
+
+${ utils.decorate_datetime_fields() }

+ 26 - 27
apps/oozie/src/oozie/templates/dashboard/rerun_coord_popup.mako

@@ -45,35 +45,34 @@
               ${ utils.render_field_no_popover(rerun_form['nocleanup'], show_label=True) }
               ${ utils.render_field_no_popover(rerun_form['nocleanup'], show_label=True) }
             </div>
             </div>
           </div>
           </div>
-<div id="param-container">
-        ${ params_form.management_form | n,unicode }
+          <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') }
+            % 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>
                 </div>
-                <div class="span6">
-                  ${ utils.render_field(form['value'], show_label=False) }
-                </div>
-              </div>
-            </div>
-          % endfor
-        % endif
-      </div>
-    </fieldset>
+              % endfor
+            % endif
+          </div>
         </div>
         </div>
       </div>
       </div>
     </fieldset>
     </fieldset>

+ 53 - 2
apps/oozie/src/oozie/tests.py

@@ -2150,8 +2150,7 @@ class TestDashboard(OozieMockBase):
     # Display of buttons happens in js now
     # Display of buttons happens in js now
     response = self.c.get(reverse('oozie:list_oozie_bundle', args=[MockOozieApi.BUNDLE_IDS[0]]), {}, follow=True)
     response = self.c.get(reverse('oozie:list_oozie_bundle', args=[MockOozieApi.BUNDLE_IDS[0]]), {}, follow=True)
     assert_true(('%s/kill' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
     assert_true(('%s/kill' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
-    # Not supported yet
-    #assert_true(('rerun_oozie_bundle/%s' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
+    assert_true(('rerun_oozie_bundle/%s' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
     assert_true(('%s/suspend' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
     assert_true(('%s/suspend' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
     assert_true(('%s/resume' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
     assert_true(('%s/resume' % MockOozieApi.BUNDLE_IDS[0]) in response.content, response.content)
 
 
@@ -2197,6 +2196,58 @@ class TestDashboard(OozieMockBase):
       finish()
       finish()
 
 
 
 
+  def test_rerun_bundle(self):
+    response = self.c.get(reverse('oozie:rerun_oozie_coord', args=[MockOozieApi.WORKFLOW_IDS[0], '/path']))
+    assert_true('Select actions to rerun' in response.content, response.content)
+
+
+  def test_rerun_bundle_permissions(self):
+    post_data =  {
+        u'end_1': [u'01:55 PM'],
+        u'end_0': [u'03/02/2013'],
+        u'refresh': [u'on'],
+        u'nocleanup': [u'on'],
+        u'start_0': [u'02/27/2013'],
+        u'start_1': [u'01:55 PM'],
+        u'coordinators': [u'DailySleep'],
+
+        u'form-MAX_NUM_FORMS': [u''],
+        u'form-TOTAL_FORMS': [u'3'],
+        u'form-INITIAL_FORMS': [u'3'],
+        u'form-0-name': [u'oozie.use.system.libpath'],
+        u'form-0-value': [u'true'],
+        u'form-1-name': [u'hue-id-b'],
+        u'form-1-value': [u'22'],
+        u'form-2-name': [u'oozie.bundle.application.path'],
+        u'form-2-value': [u'hdfs://localhost:8020/path'],
+    }
+
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = self.c.post(reverse('oozie:rerun_oozie_bundle', args=[MockOozieApi.BUNDLE_IDS[0], '/path']), post_data)
+      assert_false('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+
+    # Login as someone else
+    client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
+    grant_access("not_me", "test", "oozie")
+
+    finish = SHARE_JOBS.set_for_testing(False)
+    try:
+      response = client_not_me.post(reverse('oozie:rerun_oozie_bundle', args=[MockOozieApi.BUNDLE_IDS[0], '/path']), post_data)
+      assert_true('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+
+    finish = SHARE_JOBS.set_for_testing(True)
+    try:
+      response = client_not_me.post(reverse('oozie:rerun_oozie_bundle', args=[MockOozieApi.BUNDLE_IDS[0], '/path']), post_data)
+      assert_true('Permission denied' in response.content, response.content)
+    finally:
+      finish()
+
+
   def test_list_workflows(self):
   def test_list_workflows(self):
     response = self.c.get(reverse('oozie:list_oozie_workflows')+"?format=json")
     response = self.c.get(reverse('oozie:list_oozie_workflows')+"?format=json")
     for wf_id in MockOozieApi.WORKFLOW_IDS:
     for wf_id in MockOozieApi.WORKFLOW_IDS:

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

@@ -85,6 +85,7 @@ urlpatterns += patterns(
 
 
   url(r'^rerun_oozie_job/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_job', name='rerun_oozie_job'),
   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'^rerun_oozie_coord/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_coordinator', name='rerun_oozie_coord'),
+  url(r'^rerun_oozie_bundle/(?P<job_id>[-\w]+)/(?P<app_path>.+?)$', 'rerun_oozie_bundle', name='rerun_oozie_bundle'),
   url(r'^manage_oozie_jobs/(?P<job_id>[-\w]+)/(?P<action>(start|suspend|resume|kill|rerun))$', 'manage_oozie_jobs', name='manage_oozie_jobs'),
   url(r'^manage_oozie_jobs/(?P<job_id>[-\w]+)/(?P<action>(start|suspend|resume|kill|rerun))$', 'manage_oozie_jobs', name='manage_oozie_jobs'),
 
 
   url(r'^list_oozie_info/$', 'list_oozie_info', name='list_oozie_info'),
   url(r'^list_oozie_info/$', 'list_oozie_info', name='list_oozie_info'),

+ 69 - 3
apps/oozie/src/oozie/views/dashboard.py

@@ -37,8 +37,9 @@ from liboozie.oozie_api import get_oozie
 from liboozie.submittion import Submission
 from liboozie.submittion import Submission
 
 
 from oozie.conf import OOZIE_JOBS_COUNT
 from oozie.conf import OOZIE_JOBS_COUNT
-from oozie.forms import RerunForm, ParameterForm, RerunCoordForm
-from oozie.models import History, Job, Workflow
+from oozie.forms import RerunForm, ParameterForm, RerunCoordForm,\
+  RerunBundleForm
+from oozie.models import History, Job, Workflow, utc_datetime_format
 from oozie.settings import DJANGO_APPS
 from oozie.settings import DJANGO_APPS
 
 
 from django.template.defaultfilters import escapejs
 from django.template.defaultfilters import escapejs
@@ -425,6 +426,69 @@ def _rerun_coordinator(request, oozie_id, args, params, properties):
                          detail=ex._headers.get('oozie-error-message', ex))
                          detail=ex._headers.get('oozie-error-message', ex))
 
 
 
 
+@show_oozie_error
+def rerun_oozie_bundle(request, job_id, app_path):
+  oozie_bundle = check_job_access_permission(request, job_id)
+  check_job_edition_permission(oozie_bundle, request.user)
+  ParametersFormSet = formset_factory(ParameterForm, extra=0)
+
+  if request.method == 'POST':
+    params_form = ParametersFormSet(request.POST)
+    rerun_form = RerunBundleForm(request.POST, oozie_bundle=oozie_bundle)
+
+    if sum([rerun_form.is_valid(), params_form.is_valid()]) == 2:
+      args = {}
+      args['deployment_dir'] = app_path
+
+      params = {
+        'coord-scope': ','.join(rerun_form.cleaned_data['coordinators']),
+        'refresh': rerun_form.cleaned_data['refresh'],
+        'nocleanup': rerun_form.cleaned_data['nocleanup'],
+      }
+
+      if rerun_form.cleaned_data['start'] and rerun_form.cleaned_data['end']:
+        date = {
+            'date-scope':
+                '%(start)s::%(end)s' % {
+                    'start': utc_datetime_format(rerun_form.cleaned_data['start']),
+                    'end': utc_datetime_format(rerun_form.cleaned_data['end'])
+                }
+        }
+        params.update(date)
+
+      properties = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
+
+      _rerun_bundle(request, job_id, args, params, properties)
+
+      request.info(_('Bundle re-running!'))
+      return redirect(reverse('oozie:list_oozie_bundle', kwargs={'job_id': job_id}))
+    else:
+      request.error(_('Invalid submission form: %s' % (rerun_form.errors,)))
+      return list_oozie_bundle(request, job_id)
+  else:
+    rerun_form = RerunBundleForm(oozie_bundle=oozie_bundle)
+    initial_params = ParameterForm.get_initial_params(oozie_bundle.conf_dict)
+    params_form = ParametersFormSet(initial=initial_params)
+
+  popup = render('dashboard/rerun_bundle_popup.mako', request, {
+                   'rerun_form': rerun_form,
+                   'params_form': params_form,
+                   'action': reverse('oozie:rerun_oozie_bundle', kwargs={'job_id': job_id, 'app_path': app_path}),
+                 }, force_template=True).content
+
+  return HttpResponse(json.dumps(popup), mimetype="application/json")
+
+
+def _rerun_bundle(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_bundle(params=params, **args)
+    return job_id
+  except RestException, ex:
+    raise PopupException(_("Error re-running bundle %s") % (oozie_id,),
+                         detail=ex._headers.get('oozie-error-message', ex))
+
+
 def massaged_workflow_actions_for_json(workflow_actions, oozie_coordinator, oozie_bundle):
 def massaged_workflow_actions_for_json(workflow_actions, oozie_coordinator, oozie_bundle):
   actions = []
   actions = []
   action_link_params = {}
   action_link_params = {}
@@ -451,7 +515,9 @@ def massaged_workflow_actions_for_json(workflow_actions, oozie_coordinator, oozi
       'errorCode': escapejs(action.errorCode),
       'errorCode': escapejs(action.errorCode),
       'errorMessage': escapejs(action.errorMessage),
       'errorMessage': escapejs(action.errorMessage),
       'transition': action.transition,
       'transition': action.transition,
-      'data': escapejs(action.data)
+      'data': escapejs(action.data),
+      'lastModTime': format_time(action.lastModTime),
+      'run': action.run,
     }
     }
     actions.append(massaged_action)
     actions.append(massaged_action)
 
 

+ 1 - 1
apps/oozie/static/help/index.html

@@ -476,6 +476,6 @@ vertical-align: center;
                         </td>
                         </td>
 		    </tr>
 		    </tr>
 	    </table>
 	    </table>
-	    
+	
     </body>
     </body>
 </html>
 </html>

+ 4 - 1
apps/oozie/static/js/bundles.utils.js

@@ -32,7 +32,10 @@ function getStatusClass(status, prefix){
   if (['SUCCEEDED', 'OK'].indexOf(status) > -1){
   if (['SUCCEEDED', 'OK'].indexOf(status) > -1){
     klass = prefix + "success";
     klass = prefix + "success";
   }
   }
-  else if (['RUNNING', 'READY', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED', 'SUSPENDEDWITHERROR', 'PAUSEDWITHERROR'].indexOf(status) > -1){
+  else if (['RUNNING', 'READY', 'PREP', 'WAITING', 'SUSPENDED', 'PREPSUSPENDED', 'PREPPAUSED', 'PAUSED',
+            'SUBMITTED',
+            'SUSPENDEDWITHERROR',
+            'PAUSEDWITHERROR'].indexOf(status) > -1){
     klass = prefix + "warning";
     klass = prefix + "warning";
   }
   }
   else {
   else {

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

@@ -431,7 +431,7 @@ var NodeModule = function($, IdGeneratorTable, NodeFields) {
       var self = this;
       var self = this;
       self.registry.remove(self.id());
       self.registry.remove(self.id());
     }
     }
-    
+
   });
   });
 
 
   return module;
   return module;

+ 1 - 1
desktop/libs/liboozie/src/liboozie/oozie_api.py

@@ -213,7 +213,7 @@ class OozieApi(object):
     job_control(jobid, action) -> None
     job_control(jobid, action) -> None
     Raise RestException on error.
     Raise RestException on error.
     """
     """
-    if action not in ('start', 'suspend', 'resume', 'kill', 'rerun', 'coord-rerun'):
+    if action not in ('start', 'suspend', 'resume', 'kill', 'rerun', 'coord-rerun', 'bundle-rerun'):
       msg = 'Invalid oozie job action: %s' % (action,)
       msg = 'Invalid oozie job action: %s' % (action,)
       LOG.error(msg)
       LOG.error(msg)
       raise ValueError(msg)
       raise ValueError(msg)

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

@@ -126,6 +126,21 @@ class Submission(object):
     return self.oozie_id
     return self.oozie_id
 
 
 
 
+  def rerun_bundle(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.bundle.application.path': deployment_dir})
+      get_oozie().job_control(self.oozie_id, action='bundle-rerun', properties=self.properties, parameters=params)
+      LOG.info("Rerun: %s" % (self,))
+    finally:
+      get_oozie().setuser(prev)
+
+    return self.oozie_id
+
+
   def deploy(self):
   def deploy(self):
     try:
     try:
       deployment_dir = self._create_deployment_dir()
       deployment_dir = self._create_deployment_dir()