ソースを参照

[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 13 年 前
コミット
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)