Browse Source

HUE-883 [oozie] Add Hive action

Do not create a new deplyment directory if the owner of the Job is the same as the submitter
Fix global properties XML generation
Romain Rigaux 13 years ago
parent
commit
80cacf573e

+ 18 - 1
apps/oozie/src/oozie/forms.py

@@ -22,7 +22,7 @@ from django.db.models import Q
 
 
 from desktop.lib.django_forms import MultiForm, SplitDateTimeWidget
 from desktop.lib.django_forms import MultiForm, SplitDateTimeWidget
 from oozie.models import Workflow, Node, Java, Mapreduce, Streaming, Coordinator,\
 from oozie.models import Workflow, Node, Java, Mapreduce, Streaming, Coordinator,\
-  Dataset, DataInput, DataOutput, Pig, Link
+  Dataset, DataInput, DataOutput, Pig, Link, Hive
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
@@ -129,6 +129,22 @@ class PigForm(forms.ModelForm):
     }
     }
 
 
 
 
+class HiveForm(forms.ModelForm):
+  class Meta:
+    model = Hive
+    exclude = NodeForm.Meta.ALWAYS_HIDE
+    widgets = {
+      'job_properties': forms.widgets.HiddenInput(),
+      'prepares': forms.widgets.HiddenInput(),
+      'params': forms.widgets.HiddenInput(),
+      'script_path': forms.TextInput(attrs={'class': 'pathChooser span5'}),
+      'files': forms.widgets.HiddenInput(),
+      'archives': forms.widgets.HiddenInput(),
+      'description': forms.TextInput(attrs={'class': 'span5'}),
+      'job_xml': forms.TextInput(attrs={'class': 'span5'}),
+    }
+
+
 class LinkForm(forms.ModelForm):
 class LinkForm(forms.ModelForm):
   comment = forms.CharField(label='if', max_length=1024, required=True, widget=forms.TextInput(attrs={'class': 'span8'}))
   comment = forms.CharField(label='if', max_length=1024, required=True, widget=forms.TextInput(attrs={'class': 'span8'}))
 
 
@@ -243,6 +259,7 @@ _node_type_TO_FORM_CLS = {
   Streaming.node_type: StreamingForm,
   Streaming.node_type: StreamingForm,
   Java.node_type: JavaForm,
   Java.node_type: JavaForm,
   Pig.node_type: PigForm,
   Pig.node_type: PigForm,
+  Hive.node_type: HiveForm,
 }
 }
 
 
 
 

+ 2 - 2
apps/oozie/src/oozie/management/commands/oozie_setup.py

@@ -65,7 +65,7 @@ def copy_dir(fs, local_dir, remote_dir, mode=0755):
   for f in os.listdir(local_dir):
   for f in os.listdir(local_dir):
     local_src = os.path.join(local_dir, f)
     local_src = os.path.join(local_dir, f)
     remote_dst = posixpath.join(remote_dir, f)
     remote_dst = posixpath.join(remote_dir, f)
-    print f, local_src, remote_dst
+
     if os.path.isdir(remote_dst):
     if os.path.isdir(remote_dst):
       copy_dir(fs, local_src, remote_dst, mode)
       copy_dir(fs, local_src, remote_dst, mode)
     else:
     else:
@@ -76,7 +76,7 @@ CHUNK_SIZE = 1024 * 1024
 
 
 def copy_file(fs, local_src, remote_dst):
 def copy_file(fs, local_src, remote_dst):
   if fs.exists(remote_dst):
   if fs.exists(remote_dst):
-    LOG.info(_('%(remote_dst)s already exists.  Skipping.') % {'remote_dst': remote_dst})
+    LOG.info(_('%(remote_dst)s already exists. Skipping.') % {'remote_dst': remote_dst})
     return
     return
   else:
   else:
     LOG.info(_('%(remote_dst)s does not exist. Trying to copy') % {'remote_dst': remote_dst})
     LOG.info(_('%(remote_dst)s does not exist. Trying to copy') % {'remote_dst': remote_dst})

+ 233 - 0
apps/oozie/src/oozie/migrations/0002_auto__add_hive.py

@@ -0,0 +1,233 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Adding model 'Hive'
+        db.create_table('oozie_hive', (
+            ('files', self.gf('django.db.models.fields.CharField')(default='[]', max_length=512)),
+            ('job_xml', self.gf('django.db.models.fields.CharField')(default='', max_length=512, blank=True)),
+            ('job_properties', self.gf('django.db.models.fields.TextField')(default='[{"name":"oozie.use.system.libpath","value":"true"}]')),
+            ('params', self.gf('django.db.models.fields.TextField')(default='[]')),
+            ('archives', self.gf('django.db.models.fields.CharField')(default='[]', max_length=512)),
+            ('node_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True, primary_key=True)),
+            ('prepares', self.gf('django.db.models.fields.TextField')(default='[]')),
+            ('script_path', self.gf('django.db.models.fields.CharField')(max_length=256)),
+        ))
+        db.send_create_signal('oozie', ['Hive'])
+
+
+    def backwards(self, orm):
+
+        # Deleting model 'Hive'
+        db.delete_table('oozie_hive')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'oozie.coordinator': {
+            'Meta': {'object_name': 'Coordinator', '_ormbases': ['oozie.Job']},
+            'concurrency': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'end': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 21, 15, 17, 4, 657844)'}),
+            'execution': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+            'frequency_number': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+            'frequency_unit': ('django.db.models.fields.CharField', [], {'default': "'days'", 'max_length': '20'}),
+            'job_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Job']", 'unique': 'True', 'primary_key': 'True'}),
+            'start': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 18, 15, 17, 4, 657813)'}),
+            'throttle': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'timeout': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'timezone': ('django.db.models.fields.CharField', [], {'default': "'America/Los_Angeles'", 'max_length': '24'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Workflow']", 'null': 'True'})
+        },
+        'oozie.datainput': {
+            'Meta': {'object_name': 'DataInput'},
+            'coordinator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Coordinator']"}),
+            'dataset': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Dataset']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'})
+        },
+        'oozie.dataoutput': {
+            'Meta': {'object_name': 'DataOutput'},
+            'coordinator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Coordinator']"}),
+            'dataset': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Dataset']", 'unique': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'})
+        },
+        'oozie.dataset': {
+            'Meta': {'object_name': 'Dataset'},
+            'coordinator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Coordinator']"}),
+            'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+            'done_flag': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}),
+            'frequency_number': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+            'frequency_unit': ('django.db.models.fields.CharField', [], {'default': "'days'", 'max_length': '20'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'start': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 18, 15, 17, 4, 658450)'}),
+            'timezone': ('django.db.models.fields.CharField', [], {'default': "'America/Los_Angeles'", 'max_length': '24'}),
+            'uri': ('django.db.models.fields.CharField', [], {'default': "'/data/${YEAR}${MONTH}${DAY}'", 'max_length': '1024'})
+        },
+        'oozie.end': {
+            'Meta': {'object_name': 'End'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.fork': {
+            'Meta': {'object_name': 'Fork'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.history': {
+            'Meta': {'object_name': 'History'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Job']"}),
+            'oozie_job_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'properties': ('django.db.models.fields.TextField', [], {}),
+            'submission_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'oozie.hive': {
+            'Meta': {'object_name': 'Hive'},
+            'archives': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'files': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': '\'[{"name":"oozie.use.system.libpath","value":"true"}]\''}),
+            'job_xml': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'script_path': ('django.db.models.fields.CharField', [], {'max_length': '256'})
+        },
+        'oozie.java': {
+            'Meta': {'object_name': 'Java'},
+            'archives': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'args': ('django.db.models.fields.CharField', [], {'max_length': '4096', 'blank': 'True'}),
+            'files': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'jar_path': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
+            'java_opts': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'job_xml': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
+            'main_class': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"})
+        },
+        'oozie.job': {
+            'Meta': {'object_name': 'Job'},
+            'deployment_dir': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_shared': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'parameters': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'schema_version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'oozie.join': {
+            'Meta': {'object_name': 'Join'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.kill': {
+            'Meta': {'object_name': 'Kill'},
+            'message': ('django.db.models.fields.CharField', [], {'default': "'Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}]'", 'max_length': '256'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.link': {
+            'Meta': {'object_name': 'Link'},
+            'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parent_node'", 'to': "orm['oozie.Node']"}),
+            'comment': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'child_node'", 'to': "orm['oozie.Node']"})
+        },
+        'oozie.mapreduce': {
+            'Meta': {'object_name': 'Mapreduce'},
+            'archives': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'files': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'jar_path': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'job_xml': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True'}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"})
+        },
+        'oozie.node': {
+            'Meta': {'object_name': 'Node'},
+            'children': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'parents'", 'symmetrical': 'False', 'through': "orm['oozie.Link']", 'to': "orm['oozie.Node']"}),
+            'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'node_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Workflow']"})
+        },
+        'oozie.pig': {
+            'Meta': {'object_name': 'Pig'},
+            'archives': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'files': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': '\'[{"name":"oozie.use.system.libpath","value":"true"}]\''}),
+            'job_xml': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'script_path': ('django.db.models.fields.CharField', [], {'max_length': '256'})
+        },
+        'oozie.start': {
+            'Meta': {'object_name': 'Start'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True'})
+        },
+        'oozie.streaming': {
+            'Meta': {'object_name': 'Streaming'},
+            'archives': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'files': ('django.db.models.fields.CharField', [], {'default': "'[]'", 'max_length': '512'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': '\'[{"name":"oozie.use.system.libpath","value":"true"}]\''}),
+            'mapper': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'reducer': ('django.db.models.fields.CharField', [], {'max_length': '512'})
+        },
+        'oozie.workflow': {
+            'Meta': {'object_name': 'Workflow', '_ormbases': ['oozie.Job']},
+            'end': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'end_workflow'", 'null': 'True', 'to': "orm['oozie.End']"}),
+            'is_single': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'job_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Job']", 'unique': 'True', 'primary_key': 'True'}),
+            'job_xml': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
+            'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
+        }
+    }
+
+    complete_apps = ['oozie']

+ 46 - 1
apps/oozie/src/oozie/models.py

@@ -598,6 +598,8 @@ class Node(models.Model):
       node = self.mapreduce
       node = self.mapreduce
     elif self.node_type == Pig.node_type:
     elif self.node_type == Pig.node_type:
       node = self.pig
       node = self.pig
+    elif self.node_type == Hive.node_type:
+      node = self.hive
     elif self.node_type == Streaming.node_type:
     elif self.node_type == Streaming.node_type:
       node = self.streaming
       node = self.streaming
     elif self.node_type == Java.node_type:
     elif self.node_type == Java.node_type:
@@ -893,7 +895,50 @@ class Pig(Action):
     return json.loads(self.prepares)
     return json.loads(self.prepares)
 
 
 
 
-Action.types = (Mapreduce.node_type, Streaming.node_type, Java.node_type, Pig.node_type)
+class Hive(Action):
+  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares')
+  node_type = 'hive'
+
+  script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script path'),
+                                 help_text=_t('Local path to the %(type)s script. e.g. my_script.sql') % {'type': node_type.title()})
+  params = models.TextField(default="[]", verbose_name=_t('Parameters'),
+                            help_text=_t('The %(type)s parameters of the script. e.g. "-param", "INPUT=${inputDir}"')  % {'type': node_type.title()})
+  files = models.CharField(max_length=PATH_MAX, default="[]", verbose_name=_t('Files'),
+      help_text=_t('List of names or paths of files to be added to the distributed cache. '
+                   'To force a symlink for a file on the task running directory, use a \'#\' '
+                   'followed by the symlink name. For example \'mycat.sh#cat\'.'))
+  archives = models.CharField(max_length=PATH_MAX, default="[]", verbose_name=_t('Archives'),
+      help_text=_t('List of names or paths of the archives to be added to the distributed cache. '
+                   'To force a symlink to the uncompressed archive on the task running directory, use a \'#\' '
+                   'followed by the symlink name. For example \'myarch.zip#myarch\'.'))
+  job_properties = models.TextField(default='[{"name":"oozie.use.system.libpath","value":"true"},{"name":"oozie.hive.defaults","value":"${hive.default.xml}"}]',
+                                    verbose_name=_t('Job properties'),
+                                    help_text=_t('For the job configuration (e.g. mapred.job.queue.name=production'))
+  prepares = models.TextField(default="[]", verbose_name=_t('Prepares'),
+                              help_text=_t('List of paths to delete or create before starting the application. '
+                                           'This should be used exclusively for directory cleanup'))
+  job_xml = models.CharField(max_length=PATH_MAX, default='', blank=True, verbose_name=_t('Job XML'),
+                             help_text=_t('Refer to a Hadoop JobConf job.xml file bundled in the workflow deployment directory. '
+                                          'Properties specified in the configuration element override properties specified in the '
+                                          'files specified by any job-xml elements.'))
+
+  def get_properties(self):
+    return json.loads(self.job_properties)
+
+  def get_files(self):
+    return json.loads(self.files)
+
+  def get_archives(self):
+    return json.loads(self.archives)
+
+  def get_params(self):
+    return json.loads(self.params)
+
+  def get_prepares(self):
+    return json.loads(self.prepares)
+
+
+Action.types = (Mapreduce.node_type, Streaming.node_type, Java.node_type, Pig.node_type, Hive.node_type)
 
 
 
 
 class ControlFlow(Node):
 class ControlFlow(Node):

+ 6 - 0
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -93,6 +93,12 @@ ${ layout.menubar(section='workflows') }
                   <i class="icon-plus"></i> ${ _('Pig') }
                   <i class="icon-plus"></i> ${ _('Pig') }
                 </a>
                 </a>
                 <p/>
                 <p/>
+                <p>
+                <a href="${ url('oozie:new_action', workflow=workflow.id, node_type='hive', parent_action_id=workflow.end.get_parents()[0].id) }"
+                  title="${ _('Click to add to the end') }" class="btn">
+                  <i class="icon-plus"></i> ${ _('Hive') }
+                </a>
+                <p/>
               </div>
               </div>
               <div class="tab-pane" id="import">
               <div class="tab-pane" id="import">
                 <p>
                 <p>

+ 10 - 8
apps/oozie/src/oozie/templates/editor/edit_workflow_action.mako

@@ -90,7 +90,7 @@ ${ layout.menubar(section='workflows') }
               </tbody>
               </tbody>
             </table>
             </table>
 
 
-            % if len(action_form['prepares'].errors):
+            % if action_form['prepares'].errors:
               <div class="alert alert-error">
               <div class="alert alert-error">
                 ${ unicode(action_form['prepares'].errors) | n }
                 ${ unicode(action_form['prepares'].errors) | n }
               </div>
               </div>
@@ -128,14 +128,16 @@ ${ layout.menubar(section='workflows') }
               </tbody>
               </tbody>
             </table>
             </table>
 
 
-            % if len(action_form['params'].errors):
+            % if action_form['params'].errors:
               <div class="alert alert-error">
               <div class="alert alert-error">
                 ${ unicode(action_form['params'].errors) | n }
                 ${ unicode(action_form['params'].errors) | n }
               </div>
               </div>
             % endif
             % endif
 
 
-            <button class="btn" data-bind="click: addParam">${ _('Add Param') }</button>
-            <button class="btn" data-bind="click: addArgument">${ _('Add Argument') }</button>
+              <button class="btn" data-bind="click: addParam">${ _('Add Param') }</button>
+            % if node_type in ('pig'):
+              <button class="btn" data-bind="click: addArgument">${ _('Add Argument') }</button>
+            % endif
           </div>
           </div>
         </div>
         </div>
       % endif
       % endif
@@ -161,7 +163,7 @@ ${ layout.menubar(section='workflows') }
               </tr>
               </tr>
             </tbody>
             </tbody>
           </table>
           </table>
-          % if len(action_form['job_properties'].errors):
+          % if action_form['job_properties'].errors:
             <div class="row">
             <div class="row">
               <div class="alert alert-error">
               <div class="alert alert-error">
                 ${ unicode(action_form['job_properties'].errors) | n }
                 ${ unicode(action_form['job_properties'].errors) | n }
@@ -189,7 +191,7 @@ ${ layout.menubar(section='workflows') }
                   </tr>
                   </tr>
                 </tbody>
                 </tbody>
               </table>
               </table>
-              % if len(action_form['files'].errors):
+              % if action_form['files'].errors:
                 <div class="alert alert-error">
                 <div class="alert alert-error">
                   ${ unicode(action_form['files'].errors) | n }
                   ${ unicode(action_form['files'].errors) | n }
                 </div>
                 </div>
@@ -216,7 +218,7 @@ ${ layout.menubar(section='workflows') }
               </tr>
               </tr>
             </tbody>
             </tbody>
           </table>
           </table>
-          % if len(action_form['archives'].errors):
+          % if action_form['archives'].errors:
             <div class="alert alert-error">
             <div class="alert alert-error">
               ${ unicode(action_form['archives'].errors) | n }
               ${ unicode(action_form['archives'].errors) | n }
             </div>
             </div>
@@ -348,7 +350,7 @@ ${ layout.menubar(section='workflows') }
         };
         };
 
 
         self.addArgument = function() {
         self.addArgument = function() {
-            self.params.push({ value: "", type: "arg" });
+            self.params.push({ value: "", type: "argument" });
         };
         };
 
 
         self.removeParam = function(val) {
         self.removeParam = function(val) {

+ 37 - 0
apps/oozie/src/oozie/templates/editor/gen/workflow-hive.xml.mako

@@ -0,0 +1,37 @@
+## 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.
+
+<%namespace name="common" file="workflow-common.xml.mako" />
+
+    <action name="${ node }">
+        <hive xmlns="uri:oozie:hive-action:0.2">
+            <job-tracker>${'${'}jobTracker}</job-tracker>
+            <name-node>${'${'}nameNode}</name-node>
+
+            ${ common.prepares(node.get_prepares()) }
+            ${ common.configuration(node.get_properties()) }
+
+            <script>${ node.script_path }</script>
+
+            % for param in node.get_params():
+              <${ param['type'] }>${ param['value'] }</${ param['type'] }>
+            % endfor
+
+            ${ common.distributed_cache(node.get_files(), node.get_archives()) }
+        </hive>
+        <ok to="${ node.get_child('ok') }"/>
+        <error to="${ node.get_child('error') }"/>
+    </action>

+ 2 - 0
apps/oozie/src/oozie/templates/editor/gen/workflow.xml.mako

@@ -22,6 +22,8 @@
   <global>
   <global>
     % if workflow.job_xml:
     % if workflow.job_xml:
       <job-xml>${ workflow.job_xml }</job-xml>
       <job-xml>${ workflow.job_xml }</job-xml>
+    % endif
+    % if workflow.get_properties():
       ${ common.configuration(workflow.get_properties()) }
       ${ common.configuration(workflow.get_properties()) }
     % endif
     % endif
   </global>
   </global>

+ 4 - 1
desktop/libs/liboozie/src/liboozie/submittion.py

@@ -105,7 +105,10 @@ class Submission(object):
     Return the job deployment directory in HDFS, creating it if necessary.
     Return the job deployment directory in HDFS, creating it if necessary.
     The actual deployment dir should be 0711 owned by the user
     The actual deployment dir should be 0711 owned by the user
     """
     """
-    path = Hdfs.join(REMOTE_DEPLOYMENT_DIR.get(), '_%s_-oozie-%s-%s' % (self.user.username, self.job.id, time.time()))
+    if self.user != self.job.owner:
+      path = Hdfs.join(REMOTE_DEPLOYMENT_DIR.get(), '_%s_-oozie-%s-%s' % (self.user.username, self.job.id, time.time()))
+    else:
+      path = self.job.deployment_dir
     self._create_dir(path)
     self._create_dir(path)
     return path
     return path