Explorar el Código

[oozie] Sleep worflow and coordinator examples

Install a sleep example
Catch all Oozie errors and display them in a PopupException
Shared jobs can be submitted by another user
UI shows the possible actions for a workflow or coordinator according to permissions
Only workflow or coordinator variables with curly brackets are picked up
Each workflow or coordinator gets a unique deployment directory during its submission
Adding share option to coordinator
Fix History record creation
Remove 'show only mine' Oozie workflow and coordinator button
Romain Rigaux hace 13 años
padre
commit
c371fa8

BIN
apps/oozie/demo/lib/hadoop-examples.jar


+ 8 - 0
apps/oozie/demo/pig/aggregate.pig

@@ -0,0 +1,8 @@
+
+A = LOAD '$INPUT' AS (word:CHARARRAY, count:INT);
+
+B = FOREACH A GENERATE count, word;
+C = ORDER B BY count DESC;
+
+STORE C INTO '$OUTPUT';
+

+ 15 - 11
apps/oozie/src/oozie/conf.py

@@ -23,24 +23,28 @@ from desktop.lib.conf import Config, coerce_bool
 from desktop.lib import paths
 
 
-REMOTE_DATA_DIR = Config(
-  key="remote_data_dir",
-  default="/user/hue/oozie",
-  help=_("Location on HDFS where the oozie examples and templates are stored."))
-
-# Where examples are stored.
-LOCAL_DATA_DIR = Config(
+LOCAL_SAMPLE_DIR = Config(
   key="local_data_dir",
-  default=os.path.join(os.path.dirname(__file__), "..", "..", "data"),
-  help=_("Location on local FS where examples and template are stored"),
+  default=os.path.join(os.path.dirname(__file__), "..", "..", "examples"),
+  help=_("Location on local FS where the examples are stored"),
   private=True)
 
-SAMPLE_DATA_DIR = Config(
+LOCAL_SAMPLE_DATA_DIR = Config(
   key="sample_data_dir",
   default=paths.get_thirdparty_root("sample_data"),
-  help=_("Location on local FS where sample data is stored"),
+  help=_("Location on local FS where the data for the examples is stored"),
   private=True)
 
+REMOTE_SAMPLE_DIR = Config(
+  key="remote_data_dir",
+  default="/user/hue/oozie/examples",
+  help=_("Location on HDFS where the oozie examples and templates are stored."))
+
+REMOTE_DEPLOYMENT_DIR = Config(
+  key="remote_data_dir",
+  default="/user/hue/oozie/deployments",
+  help=_("Location on HDFS where the workflows/coordinator are deployed when submitted."))
+
 SHARE_JOBS = Config(
   key='share_jobs',
   default=True,

+ 183 - 0
apps/oozie/src/oozie/fixtures/initial_data.json

@@ -0,0 +1,183 @@
+[
+  {
+    "pk": 5,
+    "model": "oozie.job",
+    "fields": {
+      "is_shared": true,
+      "name": "SleepWorkflow",
+      "deployment_dir": "/user/hue/oozie/demo/sleep",
+      "schema_version": "",
+      "last_modified": "2012-08-20 13:13:34",
+      "owner": 1,
+      "description": "Run a Sleep MapReduce job"
+    }
+  },
+  {
+    "pk": 6,
+    "model": "oozie.job",
+    "fields": {
+      "is_shared": true,
+      "name": "DailySleep",
+      "deployment_dir": "/user/hue/oozie/demo/sleep",
+      "schema_version": "",
+      "last_modified": "2012-08-20 13:56:53",
+      "owner": 1,
+      "description": "Sleep each day for the date of the day milliseconds"
+    }
+  },
+  {
+    "pk": 5,
+    "model": "oozie.workflow",
+    "fields": {
+      "start": 18,
+      "is_single": false,
+      "end": 17
+    }
+  },
+  {
+    "pk": 23,
+    "model": "oozie.link",
+    "fields": {
+      "comment": "",
+      "name": "to",
+      "parent": 18,
+      "child": 19
+    }
+  },
+  {
+    "pk": 24,
+    "model": "oozie.link",
+    "fields": {
+      "comment": "",
+      "name": "related",
+      "parent": 18,
+      "child": 17
+    }
+  },
+  {
+    "pk": 25,
+    "model": "oozie.link",
+    "fields": {
+      "comment": "",
+      "name": "ok",
+      "parent": 19,
+      "child": 17
+    }
+  },
+  {
+    "pk": 26,
+    "model": "oozie.link",
+    "fields": {
+      "comment": "",
+      "name": "error",
+      "parent": 19,
+      "child": 16
+    }
+  },
+  {
+    "pk": 16,
+    "model": "oozie.node",
+    "fields": {
+      "node_type": "kill",
+      "workflow": 5,
+      "name": "kill",
+      "description": ""
+    }
+  },
+  {
+    "pk": 17,
+    "model": "oozie.node",
+    "fields": {
+      "node_type": "end",
+      "workflow": 5,
+      "name": "end",
+      "description": ""
+    }
+  },
+  {
+    "pk": 18,
+    "model": "oozie.node",
+    "fields": {
+      "node_type": "start",
+      "workflow": 5,
+      "name": "start",
+      "description": ""
+    }
+  },
+  {
+    "pk": 19,
+    "model": "oozie.node",
+    "fields": {
+      "node_type": "mapreduce",
+      "workflow": 5,
+      "name": "Sleep",
+      "description": "Sleep for some time"
+    }
+  },
+  {
+    "pk": 19,
+    "model": "oozie.mapreduce",
+    "fields": {
+      "files": "[]",
+      "job_properties": "[{\"name\":\"mapred.reduce.tasks\",\"value\":\"1\"},{\"name\":\"mapred.mapper.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.reducer.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.mapoutput.key.class\",\"value\":\"org.apache.hadoop.io.IntWritable\"},{\"name\":\"mapred.mapoutput.value.class\",\"value\":\"org.apache.hadoop.io.NullWritable\"},{\"name\":\"mapred.output.format.class\",\"value\":\"org.apache.hadoop.mapred.lib.NullOutputFormat\"},{\"name\":\"mapred.input.format.class\",\"value\":\"org.apache.hadoop.examples.SleepJob$SleepInputFormat\"},{\"name\":\"mapred.partitioner.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.speculative.execution\",\"value\":\"false\"},{\"name\":\"sleep.job.map.sleep.time\",\"value\":\"0\"},{\"name\":\"sleep.job.reduce.sleep.time\",\"value\":\"${REDUCER_SLEEP_TIME}\"}]",
+      "jar_path": "/user/hue/oozie/demo/lib/hadoop-examples.jar",
+      "archives": "[]",
+      "node_ptr": 19
+    }
+  },
+  {
+    "pk": 18,
+    "model": "oozie.start",
+    "fields": {
+      "node_ptr": 18
+    }
+  },
+  {
+    "pk": 17,
+    "model": "oozie.end",
+    "fields": {}
+  },
+  {
+    "pk": 16,
+    "model": "oozie.kill",
+    "fields": {
+      "message": "Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}]"
+    }
+  },
+  {
+    "pk": 6,
+    "model": "oozie.coordinator",
+    "fields": {
+      "end": "2012-08-04 00:00:00",
+      "frequency_number": 1,
+      "workflow": 5,
+      "frequency_unit": "days",
+      "start": "2012-08-01 00:00:00",
+      "timezone": "America/Los_Angeles"
+    }
+  },
+  {
+    "pk": 3,
+    "model": "oozie.dataset",
+    "fields": {
+      "name": "sleep_time",
+      "frequency_number": 1,
+      "coordinator": 6,
+      "frequency_unit": "days",
+      "uri": "${DAY}",
+      "start": "2012-08-01 00:00:00",
+      "timezone": "America/Los_Angeles",
+      "done_flag": "",
+      "description": ""
+    }
+  },
+  {
+    "pk": 1,
+    "model": "oozie.dataoutput",
+    "fields": {
+      "coordinator": 6,
+      "name": "REDUCER_SLEEP_TIME",
+      "dataset": 3
+    }
+  }
+]

+ 0 - 0
apps/oozie/src/oozie/management/__init__.py


+ 0 - 0
apps/oozie/src/oozie/management/commands/__init__.py


+ 86 - 0
apps/oozie/src/oozie/management/commands/oozie_demo_setup.py

@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# 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.
+
+import logging
+import os
+import posixpath
+import shutil
+
+from django.core.management.base import NoArgsCommand
+
+from hadoop import cluster
+
+from oozie.conf import LOCAL_SAMPLE_DATA_DIR, LOCAL_SAMPLE_DIR
+from oozie.models import Workflow
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Command(NoArgsCommand):
+  def handle_noargs(self, **options):
+    remote_fs = cluster.get_hdfs()
+    remote_dir = Workflow.objects.create_data_dir(remote_fs)
+
+    # Demo binaries
+    for demo in ('lib', 'pig'):
+      local_dir = posixpath.join(LOCAL_SAMPLE_DIR.get(), demo)
+      remote_data_dir = posixpath.join(remote_dir, demo)
+      self.stdout.write('Copying workflows %s to %s\n' % (local_dir, remote_data_dir))
+      copy_dir(local_dir, remote_fs, remote_data_dir)
+
+    # Demo data
+    local_dir = LOCAL_SAMPLE_DATA_DIR.get()
+    remote_data_dir = posixpath.join(remote_dir, 'data')
+    self.stdout.write('Copying data %s to %s\n' % (local_dir, remote_data_dir))
+    copy_dir(local_dir, remote_fs, remote_data_dir)
+
+  def has_been_setup(self):
+    return False
+
+# TODO refactor with Jobsub and move this to a utility in WebHdfs
+def copy_dir(local_dir, remote_fs, remote_dir):
+  remote_fs.mkdir(remote_dir)
+
+  for f in os.listdir(local_dir):
+    local_src = os.path.join(local_dir, f)
+    remote_dst = posixpath.join(remote_dir, f)
+    copy_file(local_src, remote_fs, remote_dst)
+
+
+CHUNK_SIZE = 65536
+
+def copy_file(local_src, remote_fs, remote_dst):
+  if remote_fs.exists(remote_dst):
+    LOG.info('%s already exists.  Skipping.' % remote_dst)
+    return
+  else:
+    LOG.info('%s does not exist. trying to copy' % remote_dst)
+
+  if os.path.isfile(local_src):
+    src = file(local_src)
+    try:
+      dst = remote_fs.open(remote_dst, 'w')
+      try:
+        shutil.copyfileobj(src, dst, CHUNK_SIZE)
+        LOG.info('Copied %s -> %s' % (local_src, remote_dst))
+      finally:
+        dst.close()
+    finally:
+      src.close()
+  else:
+    LOG.info('Skipping %s (not a file)' % local_src)

+ 6 - 6
apps/oozie/src/oozie/models.py

@@ -36,7 +36,7 @@ from desktop.lib import django_mako
 from hadoop.fs.hadoopfs import Hdfs
 from liboozie.submittion import Submission
 
-from oozie.conf import REMOTE_DATA_DIR
+from oozie.conf import REMOTE_SAMPLE_DIR
 from timezones import TIMEZONES
 
 
@@ -69,7 +69,7 @@ class Job(models.Model):
     super(Job, self).save()
 
     if not self.deployment_dir:
-      default_dir = Hdfs.join(REMOTE_DATA_DIR.get(), '_%s_-oozie-%s' % (self.owner.username, self.id))
+      default_dir = Hdfs.join(REMOTE_SAMPLE_DIR.get(), '_%s_-oozie-%s' % (self.owner.username, self.id))
       self.deployment_dir = default_dir
       super(Job, self).save()
 
@@ -137,14 +137,14 @@ class WorkflowManager(models.Manager):
     workflow.save()
 
     WorkflowManager.create_data_dir(fs)
-    Submission(workflow, fs, {})._create_deployment_dir()
+    Submission(workflow.owner, workflow, fs, {})._create_deployment_dir()
 
     return workflow
 
   @classmethod
   def create_data_dir(cls, fs):
     # If needed, create the remote home and data directories
-    remote_data_dir = REMOTE_DATA_DIR.get()
+    remote_data_dir = REMOTE_SAMPLE_DIR.get()
     user = fs.user
 
     try:
@@ -993,7 +993,7 @@ class DataOutput(models.Model):
 
 class HistoryManager(models.Manager):
   def create_from_submission(self, submission):
-    History.objects.create(submitter=submission.job.owner,
+    History.objects.create(submitter=submission.user,
                            oozie_job_id=submission.oozie_id,
                            job=submission.job,
                            properties=json.dumps(submission.properties))
@@ -1069,7 +1069,7 @@ def find_parameters(instance, fields=None):
     data = getattr(instance, field)
     if isinstance(data, basestring):
       for match in Template.pattern.finditer(data):
-        name = match.group('named') or match.group('braced')
+        name = match.group('braced')
         if name is not None:
           params.append(name)
   return params

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

@@ -18,6 +18,7 @@ DJANGO_APPS=['oozie']
 NICE_NAME = "Oozie Editor/Dashboard"
 REQUIRES_HADOOP = True
 
+# Unused
 PERMISSION_ACTIONS = (
   ("launch_editor", "Launch the Oozie Editor"),
   ("launch_dashboard", "Launch the Oozie Dashboard"),

+ 2 - 19
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinators.mako

@@ -34,13 +34,8 @@ ${layout.menubar(section='dashboard')}
       ${ _('Filter:') } <input id="filterInput" class="input-xlarge search-query" placeholder="Search for username, name, etc...">
 
       <span class="pull-right">
-        <span style="padding-right:10px;float:left">
+        <span style="padding-right:10px;float:left;margin-top:3px">
         ${ _('Show only') }
-         <a class="btn btn-submitter btn-info active">
-          <span class="btn-submitter">${ _('Mine') }</span>
-          <span class="btn-submitter hide">${ _('All') }</span>
-         </a>
-        ${ _('from the last') }
         </span>
         <span class="btn-group" style="float:left">
           <a class="btn btn-date btn-info">1</a>
@@ -200,12 +195,6 @@ ${layout.menubar(section='dashboard')}
       $("#filterInput").keyup();
     });
 
-    $("a.btn-submitter").click(function() {
-      $("a.btn-submitter").toggleClass('active');
-      $("span.btn-submitter").toggleClass('hide');
-      $("#filterInput").keyup();
-    });
-
     $.fn.dataTableExt.afnFiltering.push(
       function(oSettings, aData, iDataIndex) {
 
@@ -228,13 +217,7 @@ ${layout.menubar(section='dashboard')}
           dateFilter = Date.parse(aData[0]) >= minAge;
         }
 
-        submitterBtn = $('a.btn-submitter.active');
-        submitterFilter = true;
-        if (submitterBtn.length > 0) {
-          submitterFilter = aData[4] == '${ user }';
-        }
-
-        return statusFilter && dateFilter && submitterFilter;
+        return statusFilter && dateFilter;
       }
     );
 

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

@@ -141,7 +141,7 @@ ${ layout.menubar(section='dashboard') }
      % endif
 
     <div class="tab-pane ${ utils.if_false(hue_workflow, 'active') }" id="actions">
-      % if oozie_workflow.actions:
+      % if oozie_workflow.get_working_actions():
         <table class="table table-striped table-condensed selectable">
           <thead>
             <tr>

+ 2 - 20
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflows.mako

@@ -34,13 +34,8 @@ ${ layout.menubar(section='dashboard') }
       ${ _('Filter:') } <input id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
 
       <span class="pull-right">
-        <span style="padding-right:10px;float:left">
+        <span style="padding-right:10px;float:left;margin-top:3px">
         ${ _('Show only') }
-         <a class="btn btn-submitter btn-info active">
-          <span class="btn-submitter">${ _('Mine') }</span>
-          <span class="btn-submitter hide">${ _('All') }</span>
-         </a>
-        ${ _('from the last') }
         </span>
         <span class="btn-group" style="float:left">
           <a class="btn btn-date btn-info">1</a>
@@ -200,15 +195,8 @@ ${ layout.menubar(section='dashboard') }
       $("#filterInput").keyup();
     });
 
-    $("a.btn-submitter").click(function() {
-      $("a.btn-submitter").toggleClass('active');
-      $("span.btn-submitter").toggleClass('hide');
-      $("#filterInput").keyup();
-    });
-
     $.fn.dataTableExt.afnFiltering.push(
       function(oSettings, aData, iDataIndex) {
-
         urlHashes = ""
 
         statusBtn = $('a.btn-status.active');
@@ -228,13 +216,7 @@ ${ layout.menubar(section='dashboard') }
           dateFilter = Date.parse(aData[0]) >= minAge;
         }
 
-        submitterBtn = $('a.btn-submitter.active');
-        submitterFilter = true;
-        if (submitterBtn.length > 0) {
-          submitterFilter = aData[4] == '${ user }';
-        }
-
-        return statusFilter && dateFilter && submitterFilter;
+        return statusFilter && dateFilter;
       }
     );
 

+ 196 - 196
apps/oozie/src/oozie/templates/editor/edit_coordinator.mako

@@ -27,21 +27,21 @@ ${ layout.menubar(section='coordinators') }
 
 
 <div class="container-fluid">
-    <h1>${ _('Coordinator') } ${ coordinator.name }</h1>
-
-    <div class="well">
-      ${ _('Description') }: ${ coordinator.description or _("N/A") }
-      % if coordinator.workflow:
-      <br/>
-        ${ _('Workflow') }: <a href="${ coordinator.workflow.get_absolute_url() }">${ coordinator.workflow }</a>
-      % endif
-    </div>
+  <h1>${ _('Coordinator') } ${ coordinator.name }</h1>
+
+  <div class="well">
+    ${ _('Description') }: ${ coordinator.description or _("N/A") }
+    % if coordinator.workflow:
+    <br/>
+      ${ _('Workflow') }: <a href="${ coordinator.workflow.get_absolute_url() }">${ coordinator.workflow }</a>
+    % endif
+  </div>
 
-    <ul class="nav nav-tabs">
-        <li class="active"><a href="#editor" data-toggle="tab">${ _('Editor') }</a></li>
-        <li><a href="#datasets" data-toggle="tab">${ _('Datasets') }</a></li>
-        <li><a href="#history" data-toggle="tab">${ _('History') }</a></li>
-    </ul>
+  <ul class="nav nav-tabs">
+    <li class="active"><a href="#editor" data-toggle="tab">${ _('Editor') }</a></li>
+    <li><a href="#datasets" data-toggle="tab">${ _('Datasets') }</a></li>
+    <li><a href="#history" data-toggle="tab">${ _('History') }</a></li>
+  </ul>
 
 % if coordinator.id:
   <form class="form-horizontal" id="workflowForm" action="${ url('oozie:edit_coordinator', coordinator=coordinator.id) }" method="POST">
@@ -51,7 +51,7 @@ ${ layout.menubar(section='coordinators') }
 
     <div class="tab-content">
       <div class="tab-pane active" id="editor">
-      <div class="row-fluid">
+        <div class="row-fluid">
           <div class="span2">
           </div>
           <div class="span8">
@@ -60,232 +60,232 @@ ${ layout.menubar(section='coordinators') }
                ${ utils.render_field(coordinator_form['name']) }
                ${ utils.render_field(coordinator_form['description']) }
                ${ utils.render_field(coordinator_form['workflow']) }
+               ${ utils.render_field(coordinator_form['is_shared']) }
              </div>
 
             <hr/>
             <h2>${ _('Frequency') }</h2>
 
             <div class="fieldWrapper">
-                  <div class="row-fluid">
-                    <div class="span6">
-                    ${ utils.render_field(coordinator_form['frequency_number']) }
-                  </div>
-                  <div class="span6">
-                    ${ utils.render_field(coordinator_form['frequency_unit']) }
-                  </div>
+              <div class="row-fluid">
+                <div class="span6">
+                  ${ utils.render_field(coordinator_form['frequency_number']) }
                 </div>
+                <div class="span6">
+                  ${ utils.render_field(coordinator_form['frequency_unit']) }
+                </div>
+              </div>
             </div>
 
             <div class="fieldWrapper">
-                <div class="row-fluid">
-                  <div class="span6">
+              <div class="row-fluid">
+                 <div class="span6">
                 ${ utils.render_field(coordinator_form['start']) }
               </div>
-                <div class="span6">
-                  ${ utils.render_field(coordinator_form['end']) }
-                 </div>
+              <div class="span6">
+                 ${ utils.render_field(coordinator_form['end']) }
               </div>
-              ${ utils.render_field(coordinator_form['timezone']) }
             </div>
+              ${ utils.render_field(coordinator_form['timezone']) }
+          </div>
 
-              ${ dataset_formset.management_form }
-              ${ data_input_formset.management_form }
-              ${ data_output_formset.management_form }
+          ${ dataset_formset.management_form }
+          ${ data_input_formset.management_form }
+          ${ data_output_formset.management_form }
 
-            % if coordinator.id:
-              <hr/>
-              <h2>Data</h2>
-                <br/>
-              <p>
-                % if coordinator.workflow:
-                  ${ _('The inputs and outputs of the workflow need to be mapped to some data on the') }
-                  <a href="#" id="datasets-btn" class="btn">${ _('Datasets page') }</a>
-                % endif
-              </p>
-              </br>
-                <div class="row-fluid">
-                  <h3>${ _('Inputs') }</h3>
-
-                  % if data_input_formset.forms:
-                  <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
-                    <thead>
+          % if coordinator.id:
+            <hr/>
+            <h2>Data</h2>
+            <br/>
+            <p>
+              % if coordinator.workflow:
+                ${ _('The inputs and outputs of the workflow need to be mapped to some data on the') }
+                <a href="#" id="datasets-btn" class="btn">${ _('Datasets page') }</a>
+              % endif
+            </p>
+            </br>
+            <div class="row-fluid">
+              <h3>${ _('Inputs') }</h3>
+
+              % if data_input_formset.forms:
+                <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
+                  <thead>
+                    <tr>
+                      <th>${ _('Name') }</th>
+                      <th>${ _('Dataset') }</th>
+                      <th>${ _('Path') }</th>
+                      <th>${ _('Delete') }</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    % for form in data_input_formset.forms:
                       <tr>
-                        <th>${ _('Name') }</th>
-                        <th>${ _('Dataset') }</th>
-                        <th>${ _('Path') }</th>
-                        <th>${ _('Delete') }</th>
+                         ${ form['id'] }
+                         <td>${ form['name'] }</td>
+                         <td>${ form['dataset'] }</td>
+                         <td>${ form['dataset'].form.instance.dataset.uri }</td>
+                         <td>${ form['DELETE'] }</td>
                       </tr>
-                    </thead>
-                    <tbody>
-                      % for form in data_input_formset.forms:
-                        <tr>
-                           ${ form['id'] }
-                           <td>${ form['name'] }</td>
-                           <td>${ form['dataset'] }</td>
-                           <td>${ form['dataset'].form.instance.dataset.uri }</td>
-                           <td>${ form['DELETE'] }</td>
-                        </tr>
-                      % endfor
-                    </tbody>
-                  </table>
-                  % else:
-                    <br/>
-                    <div class="alert alert-error">
-                      ${ _('No inputs') }
-                    </div>
-                  % endif
-
-                 <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('Add') }</a>
+                    % endfor
+                  </tbody>
+                </table>
+              % else:
+                <br/>
+                <div class="alert alert-error">
+                  ${ _('No inputs') }
                 </div>
+              % endif
 
-              <br/>
+             <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('Add') }</a>
+            </div>
 
-                <div class="row-fluid">
-                  <h3>${ _('Outputs') }</h3>
+            <br/>
 
-                  % if data_output_formset.forms:
-                  <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
-                    <thead>
-                      <tr>
-                        <th>${ _('Name') }</th>
-                        <th>${ _('Dataset') }</th>
-                        <th>${ _('Path') }</th>
-                        <th>${ _('Delete') }</th>
-                      </tr>
-                    </thead>
-                    <tbody>
-                      % for form in data_output_formset.forms:
-                        <tr>
-                           ${ form['id'] }
-                           <td>${ form['name'] }</td>
-                           <td>${ form['dataset'] }</td>
-                           <td>${ form['dataset'].form.instance.dataset.uri }</td>
-                           <td>${ form['DELETE'] }</td>
-                        </tr>
-                      % endfor
-                    </tbody>
-                  </table>
-                  % else:
-                    <br/>
-                    <div class="alert alert-error">
-                      ${ _('No outputs') }
-                    </div>
-                  % endif
-
-                  <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('Add') }</a>
-                </div>
-            % endif
-         </div>
-      </div>
-      </div>
+            <div class="row-fluid">
+              <h3>${ _('Outputs') }</h3>
 
-      <div class="tab-pane" id="datasets">
-        <div class="row-fluid">
-            <div class="span1">
-              <table>
-                <thead>
-                  <tr>
-                    <th>${ _('Add a new dataset') }</th>
-                  </tr>
-                </thead>
-                <tbody>
-                  <tr>
-                    <td><br/><a class="btn" data-toggle="modal" href="#add-dataset-modal">${ _('Create') }</a></td>
-                  </tr>
-                </tbody>
-              </table>
-
-
-            </div>
-            <div class="span10">
-              % if coordinator.id:
-                <div>
+              % if data_output_formset.forms:
               <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
                 <thead>
                   <tr>
-                    <th>${ _('Pick dataset as input/output') }</th>
                     <th>${ _('Name') }</th>
-                    <th>${ _('Description') }</th>
-                    <th>${ _('Frequency') }</th>
-                    <th>${ _('Start') }</th>
-                    <th>${ _('Uri') }</th>
-                    <th>${ _('Timezone') }</th>
-                    <th>${ _('Done flag') }</th>
+                    <th>${ _('Dataset') }</th>
+                    <th>${ _('Path') }</th>
                     <th>${ _('Delete') }</th>
                   </tr>
                 </thead>
                 <tbody>
-                 % for form in dataset_formset.forms:
-                  % for hidden in form.hidden_fields():
-                    ${ hidden }
+                  % for form in data_output_formset.forms:
+                    <tr>
+                       ${ form['id'] }
+                       <td>${ form['name'] }</td>
+                       <td>${ form['dataset'] }</td>
+                       <td>${ form['dataset'].form.instance.dataset.uri }</td>
+                       <td>${ form['DELETE'] }</td>
+                    </tr>
                   % endfor
-                  <tr>
-                    <td>
-                      <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('input') }</a>
-                      <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('output') }</a>
-                      </td>
-                    <td>${ form.instance.name }</td>
-                    <td>${ form.instance.description }</td>
-                    <td>${ form.instance.text_frequency }</td>
-                    <td>${ form.instance.start }</td>
-                    <td>${ form.instance.uri }</td>
-                    <td>${ form.instance.timezone }</td>
-                    <td>${ form.instance.done_flag }</td>
-                    <td>${ form['DELETE'] }</td>
-                  </tr>
-
-                   <div class="hide">
-                      % for field in form.visible_fields():
-                              ${ field.errors }
-                              ${ field.label }: ${ field }
-                      % endfor
-                      </div>
-
-                 % endfor
                 </tbody>
               </table>
-             </div>
-                % if not dataset_formset.forms:
-                  <div class="alert alert-error">
-                    ${ _('No datasets') }
-                  </div>
-                % endif
+              % else:
+                <br/>
+                <div class="alert alert-error">
+                  ${ _('No outputs') }
+                </div>
               % endif
+
+              <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('Add') }</a>
             </div>
-          </div>
+          % endif
+        </div>
       </div>
+    </div>
 
-      <div class="tab-pane" id="history">
-        <table class="table">
-          <thead>
-            <tr>
-              <th>${ _('Date') }</th>
-              <th>${ _('Id') }</th>
-            </tr>
-          </thead>
-          <tbody>
-            % if not history:
-              ${ _('N/A') }
-            % endif
-            % for record in history:
+    <div class="tab-pane" id="datasets">
+      <div class="row-fluid">
+          <div class="span1">
+            <table>
+              <thead>
                 <tr>
-                  <td><a href="${ url('oozie:list_history_record', record_id=record.id) }" data-row-selector="true"></a>${ record.submission_date }</td>
-                  <td>${ record.oozie_job_id }</td>
+                  <th>${ _('Add a new dataset') }</th>
                 </tr>
-            % endfor
-          </tbody>
-        </table>
-      </div>
-
-      <br/>
+              </thead>
+              <tbody>
+                <tr>
+                  <td><br/><a class="btn" data-toggle="modal" href="#add-dataset-modal">${ _('Create') }</a></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
 
+          <div class="span10">
+            % if coordinator.id:
+              <div>
+                <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
+                  <thead>
+                    <tr>
+                      <th>${ _('Pick dataset as input/output') }</th>
+                      <th>${ _('Name') }</th>
+                      <th>${ _('Description') }</th>
+                      <th>${ _('Frequency') }</th>
+                      <th>${ _('Start') }</th>
+                      <th>${ _('Uri') }</th>
+                      <th>${ _('Timezone') }</th>
+                      <th>${ _('Done flag') }</th>
+                      <th>${ _('Delete') }</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                   % for form in dataset_formset.forms:
+                    % for hidden in form.hidden_fields():
+                      ${ hidden }
+                    % endfor
+                    <tr>
+                      <td>
+                        <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('input') }</a>
+                        <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('output') }</a>
+                        </td>
+                      <td>${ form.instance.name }</td>
+                      <td>${ form.instance.description }</td>
+                      <td>${ form.instance.text_frequency }</td>
+                      <td>${ form.instance.start }</td>
+                      <td>${ form.instance.uri }</td>
+                      <td>${ form.instance.timezone }</td>
+                      <td>${ form.instance.done_flag }</td>
+                      <td>${ form['DELETE'] }</td>
+                    </tr>
+
+                     <div class="hide">
+                        % for field in form.visible_fields():
+                                ${ field.errors }
+                                ${ field.label }: ${ field }
+                        % endfor
+                        </div>
+
+                   % endfor
+                  </tbody>
+                </table>
+              </div>
+              % if not dataset_formset.forms:
+                <div class="alert alert-error">
+                  ${ _('No datasets') }
+                </div>
+              % endif
+            % endif
+          </div>
+       </div>
     </div>
 
-    <div class="form-actions center">
-      <a href="${ url('oozie:list_coordinator') }" class="btn">${ _('Back') }</a>
-      <input class="btn btn-primary" type="submit" value="${ _('Save') }"></input>
+    <div class="tab-pane" id="history">
+      <table class="table">
+        <thead>
+          <tr>
+            <th>${ _('Date') }</th>
+            <th>${ _('Id') }</th>
+          </tr>
+        </thead>
+        <tbody>
+          % if not history:
+            ${ _('N/A') }
+          % endif
+          % for record in history:
+              <tr>
+                <td><a href="${ url('oozie:list_history_record', record_id=record.id) }" data-row-selector="true"></a>${ record.submission_date }</td>
+                <td>${ record.oozie_job_id }</td>
+              </tr>
+          % endfor
+        </tbody>
+      </table>
     </div>
 
+    <br/>
+
+  </div>
+
+  <div class="form-actions center">
+    <a href="${ url('oozie:list_coordinator') }" class="btn">${ _('Back') }</a>
+    <input class="btn btn-primary" type="submit" value="${ _('Save') }"></input>
+  </div>
+
   </form>
 
 % if coordinator.id:

+ 1 - 1
apps/oozie/src/oozie/templates/editor/gen/workflow-graph-status.xml.mako

@@ -42,7 +42,7 @@
           % if not is_fork:
             ${ form.instance.description }<br/>
             % if form.instance.__unicode__() in actions:
-              ${actions[form.instance.__unicode__()].errorMessage }
+              ${ actions[form.instance.__unicode__()].errorMessage or '' }
             % endif
           % endif
         </div>

+ 8 - 23
apps/oozie/src/oozie/templates/editor/list_coordinators.mako

@@ -17,6 +17,8 @@
 <%!
   from desktop.views import commonheader, commonfooter
   from django.utils.translation import ugettext as _
+
+  from oozie.views import can_access_job, can_edit_job
 %>
 
 <%namespace name="layout" file="../navigation-bar.mako" />
@@ -69,18 +71,20 @@ ${ layout.menubar(section='coordinators') }
         <tr class="action-row">
           <td class=".btn-large action-column" data-row-selector-exclude="true" style="background-color: white;">
             <input type="radio" name="action" data-row-selector-exclude="true"
-              % if currentuser.username == coordinator.owner.username:
+              % if can_edit_job(currentuser, coordinator):
                   data-param-url="${ url('oozie:workflow_parameters', workflow=coordinator.id) }"
                   data-delete-url="${ url('oozie:delete_coordinator', coordinator=coordinator.id) }"
               % endif
+              % if can_access_job(currentuser, coordinator):
                   data-clone-url="${ url('oozie:clone_coordinator', coordinator=coordinator.id) }"
                   data-bundle-url="${ url('oozie:create_coordinator') }"
                   data-submit-url="${ url('oozie:submit_coordinator', coordinator=coordinator.id) }"
+              % endif
               >
             </input>
-            %if currentuser.username == coordinator.owner.username:
+            % if can_access_job(currentuser, coordinator):
               <a href="${ url('oozie:edit_coordinator', coordinator=coordinator.id) }" data-row-selector="true"/>
-            %endif%
+            % endif
           </td>
           <td>${ coordinator.name }</td>
           <td>${ coordinator.description }</td>
@@ -91,7 +95,7 @@ ${ layout.menubar(section='coordinators') }
           </td>
           <td>${ coordinator.text_frequency }</td>
           <td>
-            <span class="label label-info">${ _('production') }</span>
+            <span class="label label-info">${ coordinator.status }</span>
           </td>
           <td nowrap="nowrap">${ utils.format_date(coordinator.last_modified) }</td>
           <td>${ coordinator.owner.username }</td>
@@ -134,21 +138,6 @@ ${ layout.menubar(section='coordinators') }
   </form>
 </div>
 
-<div id="installSamples" class="modal hide fade">
-  <form id="installSamplesForm" action="${url('oozie:setup')}" method="POST">
-    <div class="modal-header">
-      <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3>${ _('Install sample coordinators?') }</h3>
-    </div>
-    <div class="modal-body">
-      ${ _('It will take a few seconds to install.') }
-    </div>
-    <div class="modal-footer">
-      <input type="submit" class="btn primary" value="${ _('Yes') }"/>
-      <a href="#" class="btn secondary" data-dismiss="modal">${ _('No') }</a>
-    </div>
-  </form>
-</div>
 
 <style>
   td .btn-large{ cursor: crosshair;  }
@@ -284,10 +273,6 @@ ${ layout.menubar(section='coordinators') }
         oTable.fnFilter($(this).val());
     });
 
-    $("#installSamplesLink").click(function(){
-        $("#installSamples").modal("show");
-    });
-
     $("a[data-row-selector='true']").jHueRowSelector();
   });
 </script>

+ 10 - 9
apps/oozie/src/oozie/templates/editor/list_workflows.mako

@@ -17,6 +17,8 @@
 <%!
   from desktop.views import commonheader, commonfooter
   from django.utils.translation import ugettext as _
+
+  from oozie.views import can_access_job, can_edit_job
 %>
 
 <%namespace name="layout" file="../navigation-bar.mako" />
@@ -32,6 +34,7 @@ ${ layout.menubar(section='workflows') }
   <div class="well hueWell">
     <div class="btn-group pull-right">
       <a href="${ url('oozie:create_workflow') }" class="btn">${ _('Create') }</a>
+      <a href="#installSamples" data-toggle="modal" class="btn">${ _('Install examples') }</a>
     </div>
 
     <div class="row-fluid">
@@ -70,15 +73,17 @@ ${ layout.menubar(section='workflows') }
         <tr class="action-row">
           <td class=".btn-large action-column" data-row-selector-exclude="true" style="background-color: white;">
             <input type="radio" name="action" data-row-selector-exclude="true"
-              % if currentuser.username == workflow.owner.username:
+              % if can_access_job(currentuser, workflow):
                   data-param-url="${ url('oozie:workflow_parameters', workflow=workflow.id) }"
                   data-submit-url="${ url('oozie:submit_workflow', workflow=workflow.id) }"
+                  data-schedule-url="${ url('oozie:create_coordinator', workflow=workflow.id) }"
+              % endif
+              % if can_edit_job(currentuser, workflow):
                   data-delete-url="${ url('oozie:delete_workflow', workflow=workflow.id) }"
                   data-clone-url="${ url('oozie:clone_workflow', workflow=workflow.id) }"
-                  data-schedule-url="${ url('oozie:create_coordinator', workflow=workflow.id) }"
               % endif
             />
-            % if currentuser.username == workflow.owner.username:
+            % if can_access_job(currentuser, workflow):
               <a href="${ url('oozie:edit_workflow', workflow=workflow.id) }" data-row-selector="true"></a>
             % endif
           </td>
@@ -134,10 +139,10 @@ ${ layout.menubar(section='workflows') }
 </div>
 
 <div id="installSamples" class="modal hide fade">
-  <form id="installSamplesForm" action="${url('oozie:setup')}" method="POST">
+  <form id="installSamplesForm" action="${url('oozie:install_examples')}" method="POST">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3>${ _('Install sample job workflows?') }</h3>
+      <h3>${ _('Install samples?') }</h3>
     </div>
     <div class="modal-body">
       ${ _('It will take a few seconds to install.') }
@@ -278,10 +283,6 @@ ${ layout.menubar(section='workflows') }
       oTable.fnFilter($(this).val());
     });
 
-    $("#installSamplesLink").click(function(){
-      $("#installSamples").modal("show");
-    });
-
     $("a[data-row-selector='true']").jHueRowSelector();
   });
 </script>

+ 1 - 1
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -127,7 +127,7 @@
 <%def name="get_status(status)">
    % if status in ('SUCCEEDED', 'OK'):
      label-success
-   % elif status in ('RUNNING', 'PREP'):
+   % elif status in ('RUNNING', 'PREP', 'WAITING'):
       label-warning
    % elif status == 'READY':
       label-success

+ 13 - 5
apps/oozie/src/oozie/tests.py

@@ -32,7 +32,7 @@ from liboozie.types import WorkflowList, Workflow as OozieWorkflow, Coordinator
   CoordinatorList, WorkflowAction
 
 from oozie.models import Workflow, Node, Job, Coordinator, Fork
-from oozie.conf import SHARE_JOBS
+from oozie.conf import SHARE_JOBS, REMOTE_DEPLOYMENT_DIR
 
 LOG = logging.getLogger(__name__)
 
@@ -55,10 +55,10 @@ class MockOozieApi:
 
   def __init__(self, *args, **kwargs):
     pass
-  
+
   def setuser(self, user):
     pass
-  
+
   def submit_job(self, properties):
     return 'ONE-OOZIE-ID'
 
@@ -104,11 +104,11 @@ class TestEditor:
 
   def test_find_paramters(self):
     jobs = [Job(name="$a"),
-            Job(name="foo $b $$"),
+            Job(name="foo ${b} $$"),
             Job(name="${foo}", description="xxx ${foo}")]
 
     result = [job.find_parameters(['name', 'description']) for job in jobs]
-    assert_equal(set(["a", "b", "foo"]), reduce(lambda x, y: x | set(y), result, set()))
+    assert_equal(set(["b", "foo"]), reduce(lambda x, y: x | set(y), result, set()))
 
 
   def test_create_workflow(self):
@@ -353,17 +353,21 @@ class TestEditor:
 
     # Submit
     finish = SHARE_JOBS.set_for_testing(False)
+    finish_deployement = REMOTE_DEPLOYMENT_DIR.set_for_testing('/tmp')
     try:
       response = client_not_me.post(reverse('oozie:submit_workflow', args=[self.wf.id]))
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
+      finish_deployement()
     finish = SHARE_JOBS.set_for_testing(True)
+    finish_deployement = REMOTE_DEPLOYMENT_DIR.set_for_testing('/tmp')
     try:
       response = client_not_me.post(reverse('oozie:submit_workflow', args=[self.wf.id]))
       assert_false('Permission denied' in response.content, response.content)
     finally:
       finish()
+      finish_deployement()
 
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)
@@ -466,17 +470,21 @@ class TestEditor:
 
     # Submit
     finish = SHARE_JOBS.set_for_testing(False)
+    finish_deployement = REMOTE_DEPLOYMENT_DIR.set_for_testing('/tmp')
     try:
       response = client_not_me.post(reverse('oozie:submit_coordinator', args=[coord.id]))
       assert_true('Permission denied' in response.content, response.content)
     finally:
       finish()
+      finish_deployement()
     finish = SHARE_JOBS.set_for_testing(True)
+    finish_deployement = REMOTE_DEPLOYMENT_DIR.set_for_testing('/tmp')
     try:
       response = client_not_me.post(reverse('oozie:submit_coordinator', args=[coord.id]))
       assert_false('Permission denied' in response.content, response.content)
     finally:
       finish()
+      finish_deployement()
 
     # Delete
     finish = SHARE_JOBS.set_for_testing(False)

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

@@ -51,7 +51,7 @@ urlpatterns = patterns(
   url(r'^workflow_parameters/(?P<workflow>\d+)$', 'get_workflow_parameters', name='workflow_parameters'),
   url(r'^list_history$', 'list_history', name='list_history'),
   url(r'^list_history/(?P<record_id>[-\w]+)$', 'list_history_record', name='list_history_record'),
-  url(r'^setup/$', 'setup', name='setup'),
+  url(r'^install_examples/$', 'install_examples', name='install_examples'),
 )
 
 urlpatterns += patterns(

+ 12 - 14
apps/oozie/src/oozie/views/dashboard.py

@@ -31,7 +31,7 @@ from desktop.lib.rest.http_client import RestException
 from liboozie.oozie_api import get_oozie
 
 from oozie.models import History
-from oozie.views.editor import can_access_job
+from oozie.views.editor import can_access_job_or_exception
 
 
 LOG = logging.getLogger(__name__)
@@ -44,17 +44,6 @@ A Workflow/Coordinator can be accessed/submitted/modified only by its owner or a
 
 Permissions checking happens by calling check_access_and_get_oozie_job().
 """
-
-
-def show_oozie_error(view_func):
-  def decorate(request, *args, **kwargs):
-    try:
-      return view_func(request, *args, **kwargs)
-    except RestException, ex:
-      raise PopupException(_('Sorry, an error with Oozie happened.'), detail=ex._headers.get('oozie-error-message', ex))
-  return wraps(view_func)(decorate)
-
-
 def manage_oozie_jobs(request, job_id, action):
   if request.method != 'POST':
     raise PopupException(_('Please use a POST request to manage an Oozie job.'))
@@ -73,6 +62,15 @@ def manage_oozie_jobs(request, job_id, action):
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
+def show_oozie_error(view_func):
+  def decorate(request, *args, **kwargs):
+    try:
+      return view_func(request, *args, **kwargs)
+    except RestException, ex:
+      raise PopupException(_('Sorry, an error with Oozie happened.'), detail=ex._headers.get('oozie-error-message', ex))
+  return wraps(view_func)(decorate)
+
+
 @show_oozie_error
 def list_oozie_workflows(request):
   kwargs = {'cnt': 50,}
@@ -113,8 +111,8 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None):
   hue_coord = history and history.get_coordinator() or History.get_coordinator_from_config(oozie_workflow.conf_dict)
   hue_workflow = (hue_coord and hue_coord.workflow) or (history and history.get_workflow()) or History.get_workflow_from_config(oozie_workflow.conf_dict)
 
-  if hue_coord: can_access_job(request, hue_coord.workflow.id)
-  if hue_workflow: can_access_job(request, hue_workflow.id)
+  if hue_coord: can_access_job_or_exception(request, hue_coord.workflow.id)
+  if hue_workflow: can_access_job_or_exception(request, hue_workflow.id)
 
   # Add parameters from coordinator to workflow if possible
   parameters = {}

+ 60 - 61
apps/oozie/src/oozie/views/editor.py

@@ -37,6 +37,7 @@ from hadoop.fs.exceptions import WebHdfsException
 from liboozie.submittion import Submission
 
 from oozie.conf import SHARE_JOBS
+from oozie.management.commands import oozie_setup
 from oozie.models import Workflow, Node, Link, History, Coordinator,\
   Dataset, DataInput, DataOutput, Job, _STD_PROPERTIES_JSON
 from oozie.forms import NodeForm, WorkflowForm, CoordinatorForm, DatasetForm,\
@@ -57,38 +58,63 @@ A Workflow/Coordinator can be modified only by its owner or a superuser.
 
 Permissions checking happens by adding the decorators.
 """
+def can_access_job(user, job):
+  return user.is_superuser or job.owner == user or (SHARE_JOBS.get() and job.is_shared)
 
-def can_access_job(request, job_id):
-  """
-  Logic for testing if a user can access a certain Workflow / Coordinator.
-  """
+
+def can_access_job_or_exception(request, job_id):
   if job_id is None:
     return
   try:
     job = Job.objects.select_related().get(pk=job_id).get_full_node()
-    if request.user.is_superuser or job.owner == request.user.username or (SHARE_JOBS.get() and job.is_shared):
+    if can_access_job(request.user, job):
       return job
     else:
       message = _("Permission denied. %(username)s don't have the permissions to access job %(id)s") % \
           {'username': request.user.username, 'id': job.id}
       access_warn(request, message)
+      request.error(message)
       raise PopupException(message)
 
   except Job.DoesNotExist:
     raise PopupException(_('job %(id)s not exist') % {'id': job_id})
 
 
-def can_modify_job(request, job):
+def check_job_access_permission(view_func):
+  """
+  Decorator ensuring that the user has access to the workflow or coordinator.
+
+  Arg: 'workflow' or 'coordinator' id.
+  Return: the workflow of coordinator or raise an exception
+
+  Notice: its gets an id in input and returns the full object in output (not an id).
+  """
+  def decorate(request, *args, **kwargs):
+    if 'workflow' in kwargs:
+      job_type = 'workflow'
+    else:
+      job_type = 'coordinator'
+
+    job = kwargs.get(job_type)
+    if job is not None:
+      job = can_access_job_or_exception(request, job)
+    kwargs[job_type] = job
+
+    return view_func(request, *args, **kwargs)
+  return wraps(view_func)(decorate)
+
+
+def can_edit_job(user, job):
   """Only owners or admins can modify a job."""
-  return request.user.is_superuser or job.owner.id == request.user.id
+  return user.is_superuser or job.owner.id == user.id
 
 
-def check_job_modification(request, job):
-  if not can_modify_job(request, job):
-    raise PopupException(_('Not allowed to modified this job'))
+def can_edit_job_or_exception(request, job):
+  if not can_edit_job(request.user, job):
+    raise PopupException('Not allowed to modified this job')
 
 
-def check_job_modification_permission(authorize_get=False):
+def check_job_edition_permission(authorize_get=False):
   """
   Decorator ensuring that the user has the permissions to modify a workflow or coordinator.
 
@@ -103,37 +129,13 @@ def check_job_modification_permission(authorize_get=False):
 
       job = kwargs.get(job_type)
       if job is not None and not (authorize_get and request.method == 'GET'):
-        check_job_modification(request, job)
+        can_edit_job_or_exception(request, job)
 
       return view_func(request, *args, **kwargs)
     return wraps(view_func)(decorate)
   return inner
 
 
-def check_job_access_permission(view_func):
-  """
-  Decorator ensuring that the user has access to the workflow or coordinator.
-
-  Arg: 'workflow' or 'coordinator' id.
-  Return: the workflow of coordinator or raise an exception
-
-  Notice: its gets an id in input and returns the full object in output (not an id).
-  """
-  def decorate(request, *args, **kwargs):
-    if 'workflow' in kwargs:
-      job_type = 'workflow'
-    else:
-      job_type = 'coordinator'
-
-    job = kwargs.get(job_type)
-    if job is not None:
-      job = can_access_job(request, job)
-    kwargs[job_type] = job
-
-    return view_func(request, *args, **kwargs)
-  return wraps(view_func)(decorate)
-
-
 def check_action_access_permission(view_func):
   """
   Decorator ensuring that the user has access to the workflow action.
@@ -146,14 +148,14 @@ def check_action_access_permission(view_func):
   def decorate(request, *args, **kwargs):
     action_id = kwargs.get('action')
     action = Node.objects.get(id=action_id).get_full_node()
-    can_access_job(request, action.workflow.id)
+    can_access_job_or_exception(request, action.workflow.id)
     kwargs['action'] = action
 
     return view_func(request, *args, **kwargs)
   return wraps(view_func)(decorate)
 
 
-def check_action_modification_permission(view_func):
+def check_action_edition_permission(view_func):
   """
   Decorator ensuring that the user has the permissions to modify a workflow action.
 
@@ -161,7 +163,7 @@ def check_action_modification_permission(view_func):
   """
   def decorate(request, *args, **kwargs):
     action = kwargs.get('action')
-    check_job_modification(request, action.workflow)
+    can_edit_job_or_exception(request, action.workflow)
 
     return view_func(request, *args, **kwargs)
   return wraps(view_func)(decorate)
@@ -215,7 +217,7 @@ def edit_workflow(request, workflow):
   WorkflowFormSet = inlineformset_factory(Workflow, Node, form=NodeForm, max_num=0, can_order=False, can_delete=False)
   history = History.objects.filter(submitter=request.user, job=workflow)
 
-  if request.method == 'POST' and can_modify_job(request, workflow):
+  if request.method == 'POST' and can_edit_job_or_exception(request, workflow):
     try:
       workflow_form = WorkflowForm(request.POST, instance=workflow)
       actions_formset = WorkflowFormSet(request.POST, request.FILES, instance=workflow)
@@ -245,7 +247,7 @@ def edit_workflow(request, workflow):
 
 
 @check_job_access_permission
-@check_job_modification_permission()
+@check_job_edition_permission()
 def delete_workflow(request, workflow):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
@@ -253,7 +255,7 @@ def delete_workflow(request, workflow):
   workflow.coordinator_set.update(workflow=None) # In Django 1.3 could do ON DELETE set NULL
   workflow.save()
   workflow.delete()
-  Submission(workflow, request.fs, {}).remove_deployment_dir()
+  Submission(request.user, workflow, request.fs, {}).remove_deployment_dir()
   request.info(_('Workflow deleted!'))
 
   return redirect(reverse('oozie:list_workflows'))
@@ -278,7 +280,7 @@ def submit_workflow(request, workflow):
 
   try:
     mapping = dict(request.POST.iteritems())
-    submission = Submission(workflow, request.fs, mapping)
+    submission = Submission(request.user, workflow, request.fs, mapping)
     job_id = submission.run()
   except RestException, ex:
     raise PopupException(_("Error submitting workflow %s") % (workflow,),
@@ -340,7 +342,7 @@ def new_action(request, workflow, node_type, parent_action_id):
 def edit_action(request, action):
   ActionForm = design_form_by_type(action.node_type)
 
-  if request.method == 'POST' and can_modify_job(request, action.workflow):
+  if request.method == 'POST' and can_edit_job_or_exception(request, action.workflow):
     action_form = ActionForm(request.POST, instance=action)
     if action_form.is_valid():
       action = action_form.save()
@@ -362,7 +364,7 @@ def edit_action(request, action):
 
 
 @check_action_access_permission
-@check_action_modification_permission
+@check_action_edition_permission
 def edit_workflow_fork(request, action):
   fork = action
 
@@ -399,7 +401,7 @@ def edit_workflow_fork(request, action):
 
 
 @check_action_access_permission
-@check_action_modification_permission
+@check_action_edition_permission
 def delete_action(request, action):
   if request.method == 'POST':
     action.workflow.delete_action(action)
@@ -419,7 +421,7 @@ def clone_action(request, action):
 
 
 @check_action_access_permission
-@check_action_modification_permission
+@check_action_edition_permission
 def move_up_action(request, action):
   if request.method == 'POST':
     action.workflow.move_action_up(action)
@@ -429,7 +431,7 @@ def move_up_action(request, action):
 
 
 @check_action_access_permission
-@check_action_modification_permission
+@check_action_edition_permission
 def move_down_action(request, action):
   if request.method == 'POST':
     action.workflow.move_action_down(action)
@@ -461,20 +463,20 @@ def create_coordinator(request, workflow=None):
 
 
 @check_job_access_permission
-@check_job_modification_permission()
+@check_job_edition_permission()
 def delete_coordinator(request, coordinator):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
   coordinator.delete()
-  Submission(coordinator, request.fs, {}).remove_deployment_dir()
+  Submission(request.user, coordinator, request.fs, {}).remove_deployment_dir()
   request.info(_('Coordinator deleted!'))
 
-  return redirect(reverse('oozie:list_workflows'))
+  return redirect(reverse('oozie:list_coordinator'))
 
 
 @check_job_access_permission
-@check_job_modification_permission(True)
+@check_job_edition_permission(True)
 def edit_coordinator(request, coordinator):
   history = History.objects.filter(submitter=request.user, job=coordinator)
 
@@ -522,7 +524,7 @@ def edit_coordinator(request, coordinator):
 
 
 @check_job_access_permission
-@check_job_modification_permission()
+@check_job_edition_permission()
 def create_coordinator_dataset(request, coordinator):
   """Returns {'status' 0/1, data:html or url}"""
 
@@ -553,7 +555,7 @@ def create_coordinator_dataset(request, coordinator):
 
 
 @check_job_access_permission
-@check_job_modification_permission()
+@check_job_edition_permission()
 def create_coordinator_data(request, coordinator, data_type):
   """Returns {'status' 0/1, data:html or url}"""
 
@@ -608,7 +610,7 @@ def submit_coordinator(request, coordinator):
 
   try:
     if not coordinator.workflow.is_deployed(request.fs):
-      submission = Submission(coordinator.workflow, request.fs, request.POST)
+      submission = Submission(request.user, coordinator.workflow, request.fs, request.POST)
       wf_dir = submission.deploy()
       coordinator.workflow.deployment_dir = wf_dir
       coordinator.workflow.save()
@@ -617,7 +619,7 @@ def submit_coordinator(request, coordinator):
     properties = {'wf_application_path': coordinator.workflow.deployment_dir}
     properties.update(dict(request.POST.iteritems()))
 
-    submission = Submission(coordinator, request.fs, properties=properties)
+    submission = Submission(request.user, coordinator, request.fs, properties=properties)
     job_id = submission.run()
   except RestException, ex:
     raise PopupException(_("Error submitting coordinator %s") % (coordinator,),
@@ -661,14 +663,11 @@ def list_history_record(request, record_id):
   })
 
 
-def setup(request):
-  """Installs oozie examples."""
+def install_examples(request):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
   try:
-    # Warning: below will modify fs.user
-    #oozie_setup.Command().handle_noargs()
-    pass
+    oozie_setup.Command().handle_noargs()
   except WebHdfsException, e:
     raise PopupException(_('The examples could not be installed.'), detail=e)
   return redirect(reverse('oozie:list_workflows'))

+ 14 - 10
desktop/libs/liboozie/src/liboozie/submittion.py

@@ -17,20 +17,24 @@
 
 import errno
 import logging
+import time
 
 from desktop.lib.django_util import PopupException
 from hadoop import cluster
+from hadoop.fs.hadoopfs import Hdfs
 from liboozie.oozie_api import get_oozie
 
+from oozie.conf import REMOTE_DEPLOYMENT_DIR
+
 
 LOG = logging.getLogger(__name__)
 
 
 class Submission(object):
   """Represents one unique Oozie submission"""
-  def __init__(self, job, fs, properties=None):
+  def __init__(self, user, job, fs, properties=None):
     self.job = job
-    self.username = job.owner.username
+    self.user = user
     self.fs = fs
     self.oozie_id = None
 
@@ -40,7 +44,7 @@ class Submission(object):
       self.properties = {}
 
   def __unicode__(self):
-    res = "Submission for job '%s' (id %s, owner %s)" % (self.job.name, self.job.id, self.username)
+    res = "Submission for job '%s' (id %s, owner %s)" % (self.job.name, self.job.id, self.user)
     if self.oozie_id:
       res += " -- " + self.oozie_id
     return res
@@ -58,7 +62,7 @@ class Submission(object):
     deployment_dir = self.deploy()
 
     try:
-      prev = get_oozie().setuser(self.username )
+      prev = get_oozie().setuser(self.user.username)
       self._update_properties(jobtracker, deployment_dir)
       self.oozie_id = get_oozie().submit_job(self.properties)
       LOG.info("Submitted: %s" % (self,))
@@ -80,7 +84,7 @@ class Submission(object):
                            detail=str(ex))
 
     oozie_xml = self.job.to_xml()
-    self._do_as(self.username , self._copy_files, deployment_dir, oozie_xml)
+    self._do_as(self.user.username , self._copy_files, deployment_dir, oozie_xml)
 
     return deployment_dir
 
@@ -98,7 +102,7 @@ class Submission(object):
     """
     Return the job deployment directory in HDFS, creating it if necessary.
     """
-    path = self.job.deployment_dir
+    path = Hdfs.join(REMOTE_DEPLOYMENT_DIR.get(), '_%s_-oozie-%s-%s' % (self.user.username, self.job.id, time.time()))
 
     try:
       statbuf = self.fs.stats(path)
@@ -114,7 +118,7 @@ class Submission(object):
         raise IOError(ex.errno, msg)
     # The actual deployment dir should be 0711 owned by the user
     if not self.fs.exists(path):
-      self._do_as(self.username , self.fs.mkdir, path, 0711)
+      self._do_as(self.user.username , self.fs.mkdir, path, 0711)
     return path
 
   def _copy_files(self, deployment_dir, oozie_xml):
@@ -157,9 +161,9 @@ class Submission(object):
     """Delete the workflow deployment directory."""
     try:
       path = self.job.deployment_dir
-      if self._do_as(self.username , self.fs.exists, path):
-        self._do_as(self.username , self.fs.rmtree, path)
+      if self._do_as(self.user.username , self.fs.exists, path):
+        self._do_as(self.user.username , self.fs.rmtree, path)
     except Exception, ex:
       LOG.warn("Failed to clean up workflow deployment directory for "
                "%s (owner %s). Caused by: %s",
-               self.job.name, self.job.owner.username, ex)
+               self.job.name, self.user, ex)