Browse Source

HUE-1452 [oozie] Credentials by action for Oozie workflows

Credentials enabled only when Oozie is using security.
Romain Rigaux 12 years ago
parent
commit
95a0e1423f
46 changed files with 488 additions and 265 deletions
  1. 6 1
      apps/beeswax/src/beeswax/hive_site.py
  2. 8 8
      apps/oozie/src/oozie/migrations/0021_auto__chg_field_java_args__add_field_job_is_trashed.py
  3. 8 8
      apps/oozie/src/oozie/migrations/0022_auto__chg_field_mapreduce_node_ptr__chg_field_start_node_ptr.py
  4. 60 49
      apps/oozie/src/oozie/models.py
  5. 2 2
      apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako
  6. 2 2
      apps/oozie/src/oozie/templates/dashboard/list_oozie_sla.mako
  7. 2 2
      apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako
  8. 25 6
      apps/oozie/src/oozie/templates/editor/action_utils.mako
  9. 1 1
      apps/oozie/src/oozie/templates/editor/create_coordinator.mako
  10. 1 1
      apps/oozie/src/oozie/templates/editor/create_workflow.mako
  11. 11 8
      apps/oozie/src/oozie/templates/editor/dataset_utils.mako
  12. 1 1
      apps/oozie/src/oozie/templates/editor/edit_bundle.mako
  13. 3 3
      apps/oozie/src/oozie/templates/editor/edit_coordinator.mako
  14. 8 33
      apps/oozie/src/oozie/templates/editor/edit_workflow.mako
  15. 3 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-common.xml.mako
  16. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-distcp.xml.mako
  17. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-email.xml.mako
  18. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-fs.xml.mako
  19. 3 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-generic.xml.mako
  20. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-hive.xml.mako
  21. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-java.xml.mako
  22. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-mapreduce.xml.mako
  23. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-pig.xml.mako
  24. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-shell.xml.mako
  25. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-sqoop.xml.mako
  26. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-ssh.xml.mako
  27. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-streaming.xml.mako
  28. 1 1
      apps/oozie/src/oozie/templates/editor/gen/workflow-subworkflow.xml.mako
  29. 12 9
      apps/oozie/src/oozie/templates/editor/gen/workflow.xml.mako
  30. 1 1
      apps/oozie/src/oozie/templates/editor/import_coordinator.mako
  31. 1 1
      apps/oozie/src/oozie/templates/editor/import_workflow.mako
  32. 1 1
      apps/oozie/src/oozie/templates/editor/list_trashed_coordinators.mako
  33. 34 12
      apps/oozie/src/oozie/tests.py
  34. 1 1
      apps/oozie/src/oozie/urls.py
  35. 1 4
      apps/oozie/src/oozie/utils.py
  36. 0 1
      apps/oozie/src/oozie/views/api.py
  37. 10 10
      apps/oozie/src/oozie/views/dashboard.py
  38. 12 5
      apps/oozie/src/oozie/views/editor.py
  39. 1 41
      apps/oozie/static/js/workflow.js
  40. 29 24
      apps/oozie/static/js/workflow.models.js
  41. 40 2
      apps/oozie/static/js/workflow.node.js
  42. 93 0
      desktop/libs/liboozie/src/liboozie/credentials.py
  43. 88 0
      desktop/libs/liboozie/src/liboozie/credentials_test.py
  44. 2 2
      desktop/libs/liboozie/src/liboozie/oozie_api.py
  45. 6 11
      desktop/libs/liboozie/src/liboozie/submittion.py
  46. 0 2
      desktop/libs/liboozie/src/liboozie/tests.py

+ 6 - 1
apps/beeswax/src/beeswax/hive_site.py

@@ -26,9 +26,9 @@ import re
 import socket
 import socket
 
 
 from desktop.lib import security_util
 from desktop.lib import security_util
+from hadoop import confparse
 
 
 import beeswax.conf
 import beeswax.conf
-from hadoop import confparse
 
 
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
@@ -45,6 +45,7 @@ _CNF_HIVESERVER2_KERBEROS_PRINCIPAL = 'hive.server2.authentication.kerberos.prin
 _CNF_HIVESERVER2_AUTHENTICATION = 'hive.server2.authentication'
 _CNF_HIVESERVER2_AUTHENTICATION = 'hive.server2.authentication'
 _CNF_HIVESERVER2_IMPERSONATION = 'hive.server2.enable.doAs'
 _CNF_HIVESERVER2_IMPERSONATION = 'hive.server2.enable.doAs'
 
 
+
 # Host is whatever up to the colon. Allow and ignore a trailing slash.
 # Host is whatever up to the colon. Allow and ignore a trailing slash.
 _THRIFT_URI_RE = re.compile("^thrift://([^:]+):(\d+)[/]?$")
 _THRIFT_URI_RE = re.compile("^thrift://([^:]+):(\d+)[/]?$")
 
 
@@ -118,6 +119,10 @@ def get_hiveserver2_authentication():
 def hiveserver2_impersonation_enabled():
 def hiveserver2_impersonation_enabled():
   return get_conf().get(_CNF_HIVESERVER2_IMPERSONATION, 'FALSE').upper() == 'TRUE'
   return get_conf().get(_CNF_HIVESERVER2_IMPERSONATION, 'FALSE').upper() == 'TRUE'
 
 
+def hiveserver2_jdbc_url():
+  return 'jdbc:hive2://%s:%s' % (beeswax.conf.HIVE_SERVER_HOST.get(), beeswax.conf.HIVE_SERVER_PORT.get())
+
+
 def _parse_hive_site():
 def _parse_hive_site():
   """
   """
   Parse hive-site.xml and store in _HIVE_SITE_DICT
   Parse hive-site.xml and store in _HIVE_SITE_DICT

+ 8 - 8
apps/oozie/src/oozie/migrations/0021_auto__chg_field_java_args__add_field_job_is_trashed.py

@@ -5,25 +5,25 @@ from south.v2 import SchemaMigration
 from django.db import models
 from django.db import models
 
 
 class Migration(SchemaMigration):
 class Migration(SchemaMigration):
-    
+
     def forwards(self, orm):
     def forwards(self, orm):
-        
+
         # Changing field 'Java.args'
         # Changing field 'Java.args'
         db.alter_column('oozie_java', 'args', self.gf('django.db.models.fields.TextField')(blank=True))
         db.alter_column('oozie_java', 'args', self.gf('django.db.models.fields.TextField')(blank=True))
 
 
         # Adding field 'Job.is_trashed'
         # Adding field 'Job.is_trashed'
         db.add_column('oozie_job', 'is_trashed', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True, blank=True), keep_default=False)
         db.add_column('oozie_job', 'is_trashed', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True, blank=True), keep_default=False)
-    
-    
+
+
     def backwards(self, orm):
     def backwards(self, orm):
-        
+
         # Changing field 'Java.args'
         # Changing field 'Java.args'
         db.alter_column('oozie_java', 'args', self.gf('django.db.models.fields.CharField')(max_length=4096, blank=True))
         db.alter_column('oozie_java', 'args', self.gf('django.db.models.fields.CharField')(max_length=4096, blank=True))
 
 
         # Deleting field 'Job.is_trashed'
         # Deleting field 'Job.is_trashed'
         db.delete_column('oozie_job', 'is_trashed')
         db.delete_column('oozie_job', 'is_trashed')
-    
-    
+
+
     models = {
     models = {
         'auth.group': {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
             'Meta': {'object_name': 'Group'},
@@ -322,5 +322,5 @@ class Migration(SchemaMigration):
             'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
             'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
         }
         }
     }
     }
-    
+
     complete_apps = ['oozie']
     complete_apps = ['oozie']

+ 8 - 8
apps/oozie/src/oozie/migrations/0022_auto__chg_field_mapreduce_node_ptr__chg_field_start_node_ptr.py

@@ -5,25 +5,25 @@ from south.v2 import SchemaMigration
 from django.db import models
 from django.db import models
 
 
 class Migration(SchemaMigration):
 class Migration(SchemaMigration):
-    
+
     def forwards(self, orm):
     def forwards(self, orm):
-        
+
         # Changing field 'Mapreduce.node_ptr'
         # Changing field 'Mapreduce.node_ptr'
         db.alter_column('oozie_mapreduce', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True, primary_key=True))
         db.alter_column('oozie_mapreduce', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True, primary_key=True))
 
 
         # Changing field 'Start.node_ptr'
         # Changing field 'Start.node_ptr'
         db.alter_column('oozie_start', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True, primary_key=True))
         db.alter_column('oozie_start', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True, primary_key=True))
-    
-    
+
+
     def backwards(self, orm):
     def backwards(self, orm):
-        
+
         # Changing field 'Mapreduce.node_ptr'
         # Changing field 'Mapreduce.node_ptr'
         db.alter_column('oozie_mapreduce', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True))
         db.alter_column('oozie_mapreduce', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True))
 
 
         # Changing field 'Start.node_ptr'
         # Changing field 'Start.node_ptr'
         db.alter_column('oozie_start', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True))
         db.alter_column('oozie_start', 'node_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['oozie.Node'], unique=True))
-    
-    
+
+
     models = {
     models = {
         'auth.group': {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
             'Meta': {'object_name': 'Group'},
@@ -322,5 +322,5 @@ class Migration(SchemaMigration):
             'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
             'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
         }
         }
     }
     }
-    
+
     complete_apps = ['oozie']
     complete_apps = ['oozie']

+ 60 - 49
apps/oozie/src/oozie/models.py

@@ -60,6 +60,7 @@ LOG = logging.getLogger(__name__)
 PATH_MAX = 512
 PATH_MAX = 512
 name_validator = RegexValidator(regex='^[a-zA-Z_][\-_a-zA-Z0-9]{1,39}$',
 name_validator = RegexValidator(regex='^[a-zA-Z_][\-_a-zA-Z0-9]{1,39}$',
                                 message=_('Enter a valid value: combination of 2 - 40 letters and digits starting by a letter'))
                                 message=_('Enter a valid value: combination of 2 - 40 letters and digits starting by a letter'))
+# To sync in worklow.models.js
 DEFAULT_SLA = [
 DEFAULT_SLA = [
     {'key': 'enabled', 'value': False},
     {'key': 'enabled', 'value': False},
     {'key': 'nominal-time', 'value': ''},
     {'key': 'nominal-time', 'value': ''},
@@ -71,8 +72,7 @@ DEFAULT_SLA = [
     {'key': 'notification-msg', 'value': ''},
     {'key': 'notification-msg', 'value': ''},
     {'key': 'upstream-apps', 'value': ''},
     {'key': 'upstream-apps', 'value': ''},
 ]
 ]
-DEFAULT_GLOBAL_PROPERTIES = []
-DEFAULT_GLOBAL_CONFIG = []
+
 
 
 class JobManager(models.Manager):
 class JobManager(models.Manager):
 
 
@@ -123,10 +123,10 @@ class Job(models.Model):
                                   help_text=_t('Enable other users to have access to this job.'))
                                   help_text=_t('Enable other users to have access to this job.'))
   parameters = models.TextField(default='[{"name":"oozie.use.system.libpath","value":"true"}]', verbose_name=_t('Oozie parameters'),
   parameters = models.TextField(default='[{"name":"oozie.use.system.libpath","value":"true"}]', verbose_name=_t('Oozie parameters'),
                                 help_text=_t('Parameters used at the submission time (e.g. market=US, oozie.use.system.libpath=true).'))
                                 help_text=_t('Parameters used at the submission time (e.g. market=US, oozie.use.system.libpath=true).'))
-  is_trashed = models.BooleanField(default=False, db_index=True, verbose_name=_t('Is trashed'), blank=True,# Deprecated
+  is_trashed = models.BooleanField(default=False, db_index=True, verbose_name=_t('Is trashed'), blank=True, # Deprecated
                                    help_text=_t('If this job is trashed.'))
                                    help_text=_t('If this job is trashed.'))
   doc = generic.GenericRelation(Document, related_name='oozie_doc')
   doc = generic.GenericRelation(Document, related_name='oozie_doc')
-  data = models.TextField(blank=True, default=json.dumps({}))  # e.g. data=json.dump({'sla': [python data], 'global': [python data], 'credentials': [python data]})
+  data = models.TextField(blank=True, default=json.dumps({}))  # e.g. data=json.dump({'sla': [python data], ...})
 
 
   objects = JobManager()
   objects = JobManager()
   unique_together = ('owner', 'name')
   unique_together = ('owner', 'name')
@@ -222,16 +222,16 @@ class Job(models.Model):
     return user.is_superuser or self.owner == user
     return user.is_superuser or self.owner == user
 
 
   @property
   @property
-  def data_dict(self): 
+  def data_dict(self):
+    if not self.data:
+      self.data = json.dumps({})
     data_python = json.loads(self.data)
     data_python = json.loads(self.data)
     # Backward compatibility
     # Backward compatibility
     if 'sla' not in data_python:
     if 'sla' not in data_python:
       data_python['sla'] = copy.deepcopy(DEFAULT_SLA)
       data_python['sla'] = copy.deepcopy(DEFAULT_SLA)
-    if 'globalProperties' not in data_python:
-      data_python['globalProperties'] = DEFAULT_GLOBAL_PROPERTIES
-    if 'globalConfig' not in data_python:
-      data_python['globalConfig'] = DEFAULT_GLOBAL_CONFIG     
-    return data_python 
+    if 'credentials' not in data_python:
+      data_python['credentials'] = []
+    return data_python
 
 
   @property
   @property
   def data_js_escaped(self):
   def data_js_escaped(self):
@@ -242,7 +242,7 @@ class Job(models.Model):
     return self.data_dict['sla']
     return self.data_dict['sla']
 
 
   @sla.setter
   @sla.setter
-  def sla(self, sla): 
+  def sla(self, sla):
     data_ = self.data_dict
     data_ = self.data_dict
     data_['sla'] = sla
     data_['sla'] = sla
     self.data = json.dumps(data_)
     self.data = json.dumps(data_)
@@ -251,31 +251,11 @@ class Job(models.Model):
   def sla_enabled(self):
   def sla_enabled(self):
     return self.sla[0]['value'] # #1 is enabled
     return self.sla[0]['value'] # #1 is enabled
 
 
-  @property
-  def global_properties(self):
-    return self.data_dict['globalProperties']
-
-  @global_properties.setter
-  def global_properties(self, global_properties): 
-    data_ = self.data_dict
-    data_['globalProperties'] = global_properties
-    self.data = json.dumps(data_)
-
-  @property
-  def global_config(self):
-    return self.data_dict['globalConfig']
-
-  @global_config.setter
-  def global_config(self, global_config): 
-    data_ = self.data_dict
-    data_['globalConfig'] = global_config
-    self.data = json.dumps(data_)
-
 
 
 class WorkflowManager(models.Manager):
 class WorkflowManager(models.Manager):
   SCHEMA_VERSION = {
   SCHEMA_VERSION = {
-    '0.4': 'url:oozie:workflow:0.4',
-    '0.5': 'url:oozie:workflow:0.5'
+    '0.4': 'uri:oozie:workflow:0.4',
+    '0.5': 'uri:oozie:workflow:0.5'
   }
   }
 
 
   def new_workflow(self, owner):
   def new_workflow(self, owner):
@@ -611,6 +591,11 @@ class Workflow(Job):
   def sla_workflow_enabled(self):
   def sla_workflow_enabled(self):
     return self.sla_enabled or any([node.sla_enabled for node in self.node_list if hasattr(node, 'sla_enabled')])
     return self.sla_enabled or any([node.sla_enabled for node in self.node_list if hasattr(node, 'sla_enabled')])
 
 
+  @property
+  def credentials(self):
+    sub_lists = [node.credentials for node in self.node_list if hasattr(node, 'credentials')]
+    return set([item['name'] for l in sub_lists for item in l if item['value']])
+
 
 
 class Link(models.Model):
 class Link(models.Model):
   # Links to exclude when using get_children_link(), get_parent_links() in the API
   # Links to exclude when using get_children_link(), get_parent_links() in the API
@@ -697,7 +682,7 @@ class Node(models.Model):
 
 
     return node
     return node
 
 
-  def find_parameters(self):    
+  def find_parameters(self):
     return find_parameters(self, self.PARAM_FIELDS)
     return find_parameters(self, self.PARAM_FIELDS)
 
 
   def __unicode__(self):
   def __unicode__(self):
@@ -784,20 +769,42 @@ class Node(models.Model):
       'node_type': self.node_type
       'node_type': self.node_type
     })
     })
 
 
+  @property
+  def data_dict(self):
+    if not self.data:
+      self.data = json.dumps({})
+    data_python = json.loads(self.data)
+    # Backward compatibility
+    if 'sla' not in data_python:
+      data_python['sla'] = copy.deepcopy(DEFAULT_SLA)
+    if 'credentials' not in data_python:
+      data_python['credentials'] = []
+    return data_python
+
   @property
   @property
   def sla(self):
   def sla(self):
-    return json.loads(self.data).get('sla', copy.deepcopy(DEFAULT_SLA))
+    return self.data_dict['sla']
 
 
   @sla.setter
   @sla.setter
   def sla(self, sla):
   def sla(self, sla):
-    data_json = json.loads(self.data)
-    data_json['sla'] = sla
-    self.data = json.dumps(data_json)
+    data_ = self.data_dict
+    data_['sla'] = sla
+    self.data = json.dumps(data_)
 
 
   @property
   @property
   def sla_enabled(self):
   def sla_enabled(self):
     return self.sla[0]['value'] # #1 is enabled
     return self.sla[0]['value'] # #1 is enabled
 
 
+  @property
+  def credentials(self):
+    return self.data_dict['credentials']
+
+  @credentials.setter
+  def credentials(self, credentials):
+    data_ = self.data_dict
+    data_['credentials'] = credentials
+    self.data = json.dumps(data_)
+
 
 
 class Action(Node):
 class Action(Node):
   types = ()
   types = ()
@@ -928,7 +935,7 @@ class Java(Action):
 
 
 
 
 class Pig(Action):
 class Pig(Action):
-  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla')
+  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'pig'
   node_type = 'pig'
 
 
   script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script name'),
   script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script name'),
@@ -966,7 +973,7 @@ class Pig(Action):
 
 
 
 
 class Hive(Action):
 class Hive(Action):
-  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla')
+  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'hive'
   node_type = 'hive'
 
 
   script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script name'),
   script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script name'),
@@ -1003,7 +1010,7 @@ class Hive(Action):
 
 
 
 
 class Sqoop(Action):
 class Sqoop(Action):
-  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla')
+  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'sqoop'
   node_type = 'sqoop'
 
 
   script_path = models.TextField(blank=True, verbose_name=_t('Command'), default='',
   script_path = models.TextField(blank=True, verbose_name=_t('Command'), default='',
@@ -1044,7 +1051,7 @@ class Sqoop(Action):
 
 
 
 
 class Ssh(Action):
 class Ssh(Action):
-  PARAM_FIELDS = ('user', 'host', 'command', 'params', 'sla')
+  PARAM_FIELDS = ('user', 'host', 'command', 'params', 'sla', 'credentials')
   node_type = 'ssh'
   node_type = 'ssh'
 
 
   user = models.CharField(max_length=64, verbose_name=_t('User'),
   user = models.CharField(max_length=64, verbose_name=_t('User'),
@@ -1066,7 +1073,7 @@ class Ssh(Action):
 
 
 
 
 class Shell(Action):
 class Shell(Action):
-  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla')
+  PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'shell'
   node_type = 'shell'
 
 
   command = models.CharField(max_length=256, blank=False, verbose_name=_t('%(type)s command') % {'type': node_type.title()},
   command = models.CharField(max_length=256, blank=False, verbose_name=_t('%(type)s command') % {'type': node_type.title()},
@@ -1109,7 +1116,7 @@ class Shell(Action):
 
 
 
 
 class DistCp(Action):
 class DistCp(Action):
-  PARAM_FIELDS = ('job_properties', 'params', 'prepares', 'sla')
+  PARAM_FIELDS = ('job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'distcp'
   node_type = 'distcp'
 
 
   params = models.TextField(default="[]", verbose_name=_t('Arguments'),
   params = models.TextField(default="[]", verbose_name=_t('Arguments'),
@@ -1137,7 +1144,7 @@ class DistCp(Action):
 
 
 
 
 class Fs(Action):
 class Fs(Action):
-  PARAM_FIELDS = ('deletes', 'mkdirs', 'moves', 'chmods', 'touchzs', 'sla')
+  PARAM_FIELDS = ('deletes', 'mkdirs', 'moves', 'chmods', 'touchzs', 'sla', 'credentials')
   node_type = 'fs'
   node_type = 'fs'
 
 
   deletes = models.TextField(default="[]", verbose_name=_t('Delete path'), blank=True,
   deletes = models.TextField(default="[]", verbose_name=_t('Delete path'), blank=True,
@@ -1172,7 +1179,7 @@ class Fs(Action):
 
 
 
 
 class Email(Action):
 class Email(Action):
-  PARAM_FIELDS = ('to', 'cc', 'subject', 'body', 'sla')
+  PARAM_FIELDS = ('to', 'cc', 'subject', 'body', 'sla', 'credentials')
   node_type = 'email'
   node_type = 'email'
 
 
   to = models.TextField(default='', verbose_name=_t('TO addresses'), help_text=_t('Comma-separated values.'))
   to = models.TextField(default='', verbose_name=_t('TO addresses'), help_text=_t('Comma-separated values.'))
@@ -1182,7 +1189,7 @@ class Email(Action):
 
 
 
 
 class SubWorkflow(Action):
 class SubWorkflow(Action):
-  PARAM_FIELDS = ('subworkflow', 'propagate_configuration', 'job_properties', 'sla')
+  PARAM_FIELDS = ('subworkflow', 'propagate_configuration', 'job_properties', 'sla', 'credentials')
   node_type = 'subworkflow'
   node_type = 'subworkflow'
 
 
   sub_workflow = models.ForeignKey(Workflow, db_index=True, verbose_name=_t('Sub-workflow'),
   sub_workflow = models.ForeignKey(Workflow, db_index=True, verbose_name=_t('Sub-workflow'),
@@ -1197,7 +1204,7 @@ class SubWorkflow(Action):
 
 
 
 
 class Generic(Action):
 class Generic(Action):
-  PARAM_FIELDS = ('xml',)
+  PARAM_FIELDS = ('xml', 'credentials')
   node_type = 'generic'
   node_type = 'generic'
 
 
   xml = models.TextField(default='', verbose_name=_t('XML of the custom action'),
   xml = models.TextField(default='', verbose_name=_t('XML of the custom action'),
@@ -1559,6 +1566,10 @@ class Coordinator(Job):
     xml = zfile.read('coordinator.xml')
     xml = zfile.read('coordinator.xml')
     return xml, metadata
     return xml, metadata
 
 
+  @property
+  def sla_jsescaped(self):
+    return json.dumps(self.sla, cls=JSONEncoderForHTML)
+
 
 
 class DatasetManager(models.Manager):
 class DatasetManager(models.Manager):
   def can_read_or_exception(self, request, dataset_id):
   def can_read_or_exception(self, request, dataset_id):
@@ -1895,7 +1906,7 @@ def find_json_parameters(fields):
   params = []
   params = []
 
 
   for field in fields:
   for field in fields:
-    for data in field.values():      
+    for data in field.values():
       if isinstance(data, basestring):
       if isinstance(data, basestring):
         for match in Template.pattern.finditer(data):
         for match in Template.pattern.finditer(data):
           name = match.group('braced')
           name = match.group('braced')

+ 2 - 2
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako

@@ -272,9 +272,9 @@ ${ layout.menubar(section='coordinators', dashboard=True) }
       <div class="tab-pane" id="definition">
       <div class="tab-pane" id="definition">
         <textarea id="definitionEditor">${ oozie_coordinator.definition.decode('utf-8', 'replace') }</textarea>
         <textarea id="definitionEditor">${ oozie_coordinator.definition.decode('utf-8', 'replace') }</textarea>
       </div>
       </div>
-      
+
       % if oozie_coordinator.has_sla:
       % if oozie_coordinator.has_sla:
-      <div class="tab-pane" id="sla">          
+      <div class="tab-pane" id="sla">
       </div>
       </div>
       % endif
       % endif
     </div>
     </div>

+ 2 - 2
apps/oozie/src/oozie/templates/dashboard/list_oozie_sla.mako

@@ -82,7 +82,7 @@ ${ layout.menubar(section='sla', dashboard=True) }
     </div>
     </div>
 
 
     <div class="tab-pane" id="graphTab">
     <div class="tab-pane" id="graphTab">
-      MY GRAPH      
+      MY GRAPH
     </div>
     </div>
 
 
     </p>
     </p>
@@ -116,7 +116,7 @@ ${ layout.menubar(section='sla', dashboard=True) }
           slaTable.fnClearTable();
           slaTable.fnClearTable();
           if (data['oozie_slas']) {
           if (data['oozie_slas']) {
             slaTable.fnAddData(data['oozie_slas']); ## Should put a class on status, links to oozie job ids...
             slaTable.fnAddData(data['oozie_slas']); ## Should put a class on status, links to oozie job ids...
-          } 
+          }
         });
         });
       }, 300);
       }, 300);
     });
     });

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

@@ -285,9 +285,9 @@ ${ layout.menubar(section='workflows', dashboard=True) }
         <div class="tab-pane" id="definition">
         <div class="tab-pane" id="definition">
           <textarea id="definitionEditor">${ oozie_workflow.definition.decode('utf-8', 'replace') }</textarea>
           <textarea id="definitionEditor">${ oozie_workflow.definition.decode('utf-8', 'replace') }</textarea>
         </div>
         </div>
-        
+
         % if oozie_workflow.has_sla:
         % if oozie_workflow.has_sla:
-        <div class="tab-pane" id="sla">          
+        <div class="tab-pane" id="sla">
         </div>
         </div>
         % endif
         % endif
       </div>
       </div>

+ 25 - 6
apps/oozie/src/oozie/templates/editor/action_utils.mako

@@ -61,7 +61,7 @@
 
 
   ${ utils.render_constant(_('Action type'), node_type) }
   ${ utils.render_constant(_('Action type'), node_type) }
 
 
-  
+
   <div class="control-group ">
   <div class="control-group ">
     <label class="control-label">
     <label class="control-label">
       <a href="javascript:void(0);" id="advanced-btn" onclick="$('#node-advanced-container').toggle('hide')">
       <a href="javascript:void(0);" id="advanced-btn" onclick="$('#node-advanced-container').toggle('hide')">
@@ -75,10 +75,29 @@
         <div class="control-group">
         <div class="control-group">
           <label class="control-label">
           <label class="control-label">
               ${ _('SLA') }
               ${ _('SLA') }
-          </label>      
-      
-          ${ utils.slaForm() }  
-    </div>
+          </label>
+
+          ${ utils.slaForm() }
+        </div>
+
+        <div class="control-group" data-bind="visible: credentials().length > 0">
+          <label class="control-label">
+              ${ _('Credentials') }
+          </label>
+
+          <div data-bind="foreach: credentials">
+            <div class="controls">
+              <div class="span3">
+                <span></span>
+              </div>
+              <div class="span9">
+                <span data-bind="text: name" class="span2"/>
+                <input type="checkbox" data-bind="checked: value"/>
+                <span data-bind="visible: name() == 'hbase'">${ _('Requires hbase-site.xml in job-xml field') }</span>
+              </div>
+            </div>
+          </div>
+        </div>
 
 
     <!-- ko if: $root.context().nodes && $root.context().error_node -->
     <!-- ko if: $root.context().nodes && $root.context().error_node -->
     <div class="control-group">
     <div class="control-group">
@@ -100,7 +119,7 @@
     <!-- /ko -->
     <!-- /ko -->
 
 
   </div>
   </div>
-  
+
 
 
   <hr/>
   <hr/>
 % endif
 % endif

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

@@ -70,7 +70,7 @@ ${ layout.menubar(section='coordinators') }
             <div class="fieldWrapper">
             <div class="fieldWrapper">
               ${ utils.render_field_no_popover(coordinator_form['name'], extra_attrs = {'validate':'true'}) }
               ${ utils.render_field_no_popover(coordinator_form['name'], extra_attrs = {'validate':'true'}) }
               ${ utils.render_field_no_popover(coordinator_form['description']) }
               ${ utils.render_field_no_popover(coordinator_form['description']) }
-              ${ utils.render_field_no_popover(coordinator_form['workflow'], extra_attrs = {'validate':'true'}) }              
+              ${ utils.render_field_no_popover(coordinator_form['workflow'], extra_attrs = {'validate':'true'}) }
               ${ coordinator_form['parameters'] | n,unicode }
               ${ coordinator_form['parameters'] | n,unicode }
               ${ coordinator_form['job_properties'] | n,unicode }
               ${ coordinator_form['job_properties'] | n,unicode }
               <div class="hide">
               <div class="hide">

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

@@ -46,7 +46,7 @@ ${ layout.menubar(section='workflows') }
               <form class="form-horizontal" id="workflowForm" action="${ url('oozie:create_workflow') }" method="POST">
               <form class="form-horizontal" id="workflowForm" action="${ url('oozie:create_workflow') }" method="POST">
               <fieldset>
               <fieldset>
               ${ utils.render_field(workflow_form['name']) }
               ${ utils.render_field(workflow_form['name']) }
-              ${ utils.render_field(workflow_form['description']) }              
+              ${ utils.render_field(workflow_form['description']) }
 
 
               <div class="control-group ">
               <div class="control-group ">
                 <label class="control-label">
                 <label class="control-label">

+ 11 - 8
apps/oozie/src/oozie/templates/editor/dataset_utils.mako

@@ -14,6 +14,9 @@
 ## See the License for the specific language governing permissions and
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## limitations under the License.
 
 
+<%!
+  from django.utils.translation import ugettext as _
+%>
 
 
 ## How to use:
 ## How to use:
 ## <%include file="dataset_utils.mako"/>
 ## <%include file="dataset_utils.mako"/>
@@ -23,33 +26,33 @@
 
 
 
 
 <div class="control-group">
 <div class="control-group">
-  <label class="control-label">Instance</label>
+  <label class="control-label">${ _('Instance') }</label>
   <div class="controls">
   <div class="controls">
       <div class="btn-group" data-toggle="buttons-radio">
       <div class="btn-group" data-toggle="buttons-radio">
-          <button id="default-btn" type="button" class="btn" data-bind="click: setDefault, css: { active: instance_choice() == 'default' }">Default</button>
-          <button id="single-btn" type="button" class="btn" data-bind="click: setSingle, css: { active: instance_choice() == 'single' }">Single</button>
-          <button id="range-btn" type="button" class="btn" data-bind="click: setRange, css: { active: instance_choice() == 'range' }">Range</button>
+          <button id="default-btn" type="button" class="btn" data-bind="click: setDefault, css: { active: instance_choice() == 'default' }">${ _('Default') }</button>
+          <button id="single-btn" type="button" class="btn" data-bind="click: setSingle, css: { active: instance_choice() == 'single' }">${ _('Single') }</button>
+          <button id="range-btn" type="button" class="btn" data-bind="click: setRange, css: { active: instance_choice() == 'range' }">${ _('Range') }</button>
       </div>
       </div>
       <span class="help-block">${ dataset_form['instance_choice'].help_text }</span>
       <span class="help-block">${ dataset_form['instance_choice'].help_text }</span>
 
 
       <div data-bind="visible: $.inArray(instance_choice(), ['single', 'range']) != -1">
       <div data-bind="visible: $.inArray(instance_choice(), ['single', 'range']) != -1">
-          <span class="span1">Start</span>
+          <span class="span1">${ _('Start') }</span>
           <input name="instance_start" type="number" data-bind="value: start_instance, enable: ! is_advanced_start_instance()"/>
           <input name="instance_start" type="number" data-bind="value: start_instance, enable: ! is_advanced_start_instance()"/>
           <label style="display: inline">
           <label style="display: inline">
               &nbsp;
               &nbsp;
               <input type="checkbox" data-bind="checked: is_advanced_start_instance">
               <input type="checkbox" data-bind="checked: is_advanced_start_instance">
-              (advanced)
+              ${ _('(advanced)') }
           </label>
           </label>
           <input type="text" data-bind="value: advanced_start_instance, visible: is_advanced_start_instance()" class="span4"/>
           <input type="text" data-bind="value: advanced_start_instance, visible: is_advanced_start_instance()" class="span4"/>
           <span class="help-block">${ dataset_form['advanced_start_instance'].help_text }</span>
           <span class="help-block">${ dataset_form['advanced_start_instance'].help_text }</span>
       </div>
       </div>
       <div data-bind="visible: instance_choice() == 'range'">
       <div data-bind="visible: instance_choice() == 'range'">
-          <span class="span1">End</span>
+          <span class="span1">${ _('End') }</span>
           <input name="instance_end" type="number" data-bind="value: end_instance, enable: ! is_advanced_end_instance()" />
           <input name="instance_end" type="number" data-bind="value: end_instance, enable: ! is_advanced_end_instance()" />
           <label style="display: inline">
           <label style="display: inline">
               &nbsp;
               &nbsp;
               <input type="checkbox" data-bind="checked: is_advanced_end_instance">
               <input type="checkbox" data-bind="checked: is_advanced_end_instance">
-              (advanced)
+              ${ _('(advanced)') }
           </label>
           </label>
           <input type="text" data-bind="value: advanced_end_instance, visible: is_advanced_end_instance()" class="span4"/>
           <input type="text" data-bind="value: advanced_end_instance, visible: is_advanced_end_instance()" class="span4"/>
           <span class="help-block">${ dataset_form['advanced_end_instance'].help_text }</span>
           <span class="help-block">${ dataset_form['advanced_end_instance'].help_text }</span>

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

@@ -113,7 +113,7 @@ ${ layout.menubar(section='bundles') }
                       ${ _('UTC time only. (e.g. if you want 10pm PST (UTC+8) set it 8 hours later to 6am the next day.') }
                       ${ _('UTC time only. (e.g. if you want 10pm PST (UTC+8) set it 8 hours later to 6am the next day.') }
                     </div>
                     </div>
                   </div>
                   </div>
-                </div>                
+                </div>
                 ${ bundle_form['parameters'] | n,unicode }
                 ${ bundle_form['parameters'] | n,unicode }
                 <div class="hide">
                 <div class="hide">
                   ${ utils.render_field_no_popover(bundle_form['is_shared']) }
                   ${ utils.render_field_no_popover(bundle_form['is_shared']) }

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

@@ -764,9 +764,9 @@ ${ layout.menubar(section='coordinators') }
 
 
       $("a[rel='tooltip']").tooltip();
       $("a[rel='tooltip']").tooltip();
     });
     });
-    
-    ${ utils.slaGlobal() } 
-   
+
+    ${ utils.slaGlobal() }
+
   </script>
   </script>
 % endif
 % endif
 
 

+ 8 - 33
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -81,7 +81,7 @@ ${ layout.menubar(section='workflows') }
   <div class="span10">
   <div class="span10">
     <div id="properties" class="section hide">
     <div id="properties" class="section hide">
     <div class="card card-small">
     <div class="card card-small">
-    
+
       <div class="alert alert-info"><h3 data-bind="text: name()"></h3></div>
       <div class="alert alert-info"><h3 data-bind="text: name()"></h3></div>
       <div class="card-body">
       <div class="card-body">
         <p>
         <p>
@@ -117,41 +117,14 @@ ${ layout.menubar(section='workflows') }
         </div>
         </div>
 
 
       <div id="advanced-container" class="hide">
       <div id="advanced-container" class="hide">
-
-        <div id="globalPropertiesEditord" class="control-group">
-          <label class="control-label">
-            ${ _('Global properites') }
-          </label>
-          <%
-          workflows.key_value_field(_("Global properties"), _("Global properties"), {
-          'name': 'data.global_properties',
-          'remove': '$root.removeGlobalProperty',
-          'add': '$root.addGlobalProperty',
-          })
-          %>
-        </div>
-
-        <div id="globalConfigEditord" class="control-group">
-          <label class="control-label">
-            ${ _('Global config') }
-          </label>
-          <%
-          workflows.key_value_field(_("Global config"), _("Global config"), {
-          'name': 'data.global_config',
-          'remove': '$root.removeGlobalConfig',
-          'add': '$root.addGlobalConfig',
-          })
-          %>
-        </div>
-      
         <div id="slaEditord" class="control-group">
         <div id="slaEditord" class="control-group">
           <label class="control-label">
           <label class="control-label">
             ${ _('SLA') }
             ${ _('SLA') }
           </label>
           </label>
-          
+
           ${ utils.slaForm() }
           ${ utils.slaForm() }
-        </div>      
-      
+        </div>
+
         % if user_can_edit_job:
         % if user_can_edit_job:
           ${ utils.render_field_with_error_js(workflow_form['deployment_dir'], workflow_form['deployment_dir'].name, extra_attrs={'data-bind': 'value: %s' % workflow_form['deployment_dir'].name}) }
           ${ utils.render_field_with_error_js(workflow_form['deployment_dir'], workflow_form['deployment_dir'].name, extra_attrs={'data-bind': 'value: %s' % workflow_form['deployment_dir'].name}) }
         % endif
         % endif
@@ -674,7 +647,9 @@ function save_workflow() {
   }
   }
 }
 }
 
 
-// Fetch all nodes from server.
+var OOZIE_CREDENTIALS = ${ credentials | n,unicode };
+
+// Fetch workflow properties from server.
 var workflow_model = new WorkflowModel({
 var workflow_model = new WorkflowModel({
   id: ${ workflow.id },
   id: ${ workflow.id },
   name: "${ workflow.name }",
   name: "${ workflow.name }",
@@ -700,7 +675,7 @@ var import_jobsub_action = new ImportJobsubAction({workflow: workflow});
 var import_workflow_action = new ImportWorkflowAction({workflow: workflow});
 var import_workflow_action = new ImportWorkflowAction({workflow: workflow});
 var modal = new Modal($('#node-modal'));
 var modal = new Modal($('#node-modal'));
 
 
-// Load data.
+// Fetch nodes
 import_jobsub_action.fetchWorkflows({ success: import_jobsub_load_success });
 import_jobsub_action.fetchWorkflows({ success: import_jobsub_load_success });
 import_workflow_action.fetchWorkflows({ success: import_workflow_load_success });
 import_workflow_action.fetchWorkflows({ success: import_workflow_load_success });
 {
 {

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

@@ -24,6 +24,9 @@
 <%def name="filelink(path)">${ '#' in path and path or path + '#' + posixpath.basename(path) }</%def>
 <%def name="filelink(path)">${ '#' in path and path or path + '#' + posixpath.basename(path) }</%def>
 
 
 
 
+<%def name="credentials(credentials)">${ ' cred="%s"' % ' '.join([cred['name'] for cred in credentials if cred['value']]) if credentials else '' | n,unicode }</%def>
+
+
 <%def name="prepares(prepares)">
 <%def name="prepares(prepares)">
         % if prepares:
         % if prepares:
             <prepare>
             <prepare>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <distcp xmlns="uri:oozie:distcp-action:0.1">
         <distcp xmlns="uri:oozie:distcp-action:0.1">
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <email xmlns="uri:oozie:email-action:0.1">
         <email xmlns="uri:oozie:email-action:0.1">
             <to>${ node.to }</to>
             <to>${ node.to }</to>
             % if node.cc:
             % if node.cc:

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

@@ -21,7 +21,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <fs>
         <fs>
             % for param in node.get_deletes():
             % for param in node.get_deletes():
               <delete path='${ smart_path(param['name'], mapping) }'/>
               <delete path='${ smart_path(param['name'], mapping) }'/>

+ 3 - 1
apps/oozie/src/oozie/templates/editor/gen/workflow-generic.xml.mako

@@ -15,7 +15,9 @@
 ## See the License for the specific language governing permissions and
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## limitations under the License.
 
 
-    <action name="${ node }">
+<%namespace name="common" file="workflow-common.xml.mako" />
+
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         ${ node.xml | n,unicode }
         ${ node.xml | n,unicode }
         <ok to="${ node.get_oozie_child('ok') }"/>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
         <error to="${ node.get_oozie_child('error') }"/>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }"${ " cred='hive_credentials'" if mapping.get('is_kerberized_hive') else '' | n,unicode}>
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <hive xmlns="uri:oozie:hive-action:0.2">
         <hive xmlns="uri:oozie:hive-action:0.2">
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <java>
         <java>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <map-reduce>
         <map-reduce>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <pig>
         <pig>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <shell xmlns="uri:oozie:shell-action:0.1">
         <shell xmlns="uri:oozie:shell-action:0.1">
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <sqoop xmlns="uri:oozie:sqoop-action:0.2">
         <sqoop xmlns="uri:oozie:sqoop-action:0.2">
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <job-tracker>${'${'}jobTracker}</job-tracker>
             <name-node>${'${'}nameNode}</name-node>
             <name-node>${'${'}nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <ssh xmlns="uri:oozie:ssh-action:0.1">
         <ssh xmlns="uri:oozie:ssh-action:0.1">
             <host>${ node.user }@${ node.host }</host>
             <host>${ node.user }@${ node.host }</host>
             <command>${ node.command }</command>
             <command>${ node.command }</command>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <map-reduce>
         <map-reduce>
             <job-tracker>${ '${' }jobTracker}</job-tracker>
             <job-tracker>${ '${' }jobTracker}</job-tracker>
             <name-node>${ '${' }nameNode}</name-node>
             <name-node>${ '${' }nameNode}</name-node>

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

@@ -17,7 +17,7 @@
 
 
 <%namespace name="common" file="workflow-common.xml.mako" />
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-    <action name="${ node }">
+    <action name="${ node }"${ common.credentials(node.credentials) }>
         <sub-workflow>
         <sub-workflow>
             <app-path>${'${'}nameNode}${ node.sub_workflow.deployment_dir }</app-path>
             <app-path>${'${'}nameNode}${ node.sub_workflow.deployment_dir }</app-path>
 
 

+ 12 - 9
apps/oozie/src/oozie/templates/editor/gen/workflow.xml.mako

@@ -29,19 +29,22 @@
     % endif
     % endif
   </global>
   </global>
   % endif
   % endif
-  % if mapping.get('is_kerberized_hive'):
+  % if workflow.credentials:
   <credentials>
   <credentials>
-    <credential name='hive_credentials' type='${ mapping['credential_type'] }'>
+    % for cred_type in workflow.credentials:
+    <%
+      credential = mapping['credentials'][cred_type]
+    %>
+    <credential name="${ credential['xml_name'] }" type="${ cred_type }">
+    % for name, value in credential['properties']:
       <property>
       <property>
-        <name>hcat.metastore.uri</name>
-        <value>${ mapping['thrift_server'] }</value>
-      </property>
-      <property>
-        <name>hcat.metastore.principal</name>
-        <value>${ mapping['hive_principal'] }</value>
+        <name>${ name }</name>
+        <value>${ value }</value>
       </property>
       </property>
+    % endfor
     </credential>
     </credential>
-   </credentials>
+    % endfor
+  </credentials>
   % endif
   % endif
   % for node in workflow.node_list:
   % for node in workflow.node_list:
       ${ node.to_xml(mapping) | n }
       ${ node.to_xml(mapping) | n }

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

@@ -52,7 +52,7 @@ ${ layout.menubar(section='coordinators') }
           </div>
           </div>
         </div>
         </div>
 
 
-        <div class="form-actions" style="padding-left: 20px !important;">        
+        <div class="form-actions" style="padding-left: 20px !important;">
           <a class="btn" onclick="history.back()">${ _('Back') }</a>
           <a class="btn" onclick="history.back()">${ _('Back') }</a>
           <input class="btn btn-primary" type="submit" value="${ _('Import') }" />
           <input class="btn btn-primary" type="submit" value="${ _('Import') }" />
         </div>
         </div>

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

@@ -74,7 +74,7 @@ ${ layout.menubar(section='workflows') }
 
 
       </div>
       </div>
 
 
-      <div class="form-actions" style="padding-left: 20px !important;">        
+      <div class="form-actions" style="padding-left: 20px !important;">
         <a class="btn" onclick="history.back()">${ _('Back') }</a>
         <a class="btn" onclick="history.back()">${ _('Back') }</a>
         <input class="btn btn-primary" type="submit" value="${ _('Import') }" />
         <input class="btn btn-primary" type="submit" value="${ _('Import') }" />
       </div>
       </div>

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

@@ -50,7 +50,7 @@ ${ layout.menubar(section='coordinators') }
        &nbsp;&nbsp;
        &nbsp;&nbsp;
        <a href="${ url('oozie:list_coordinators') }" id="home-btn" class="btn" title="${ _('Got to coordinator manager') }">
        <a href="${ url('oozie:list_coordinators') }" id="home-btn" class="btn" title="${ _('Got to coordinator manager') }">
          <i class="fa fa-home"></i> ${ _('Back') }
          <i class="fa fa-home"></i> ${ _('Back') }
-       </a>        
+       </a>
     </%def>
     </%def>
   </%actionbar:render>
   </%actionbar:render>
 
 

+ 34 - 12
apps/oozie/src/oozie/tests.py

@@ -186,6 +186,16 @@ class MockOozieApi:
   def get_oozie_slas(self, **kwargs):
   def get_oozie_slas(self, **kwargs):
     return MockOozieApi.WORKFLOWS_SLAS
     return MockOozieApi.WORKFLOWS_SLAS
 
 
+  def get_configuration(self):
+    oozie_credentialclasses = """
+           hbase=org.apache.oozie.action.hadoop.HbaseCredentials,
+           hcat=org.apache.oozie.action.hadoop.HCatCredentials,
+           hive2=org.apache.oozie.action.hadoop.Hive2Credentials
+    """
+    return {
+        'oozie.credentials.credentialclasses': oozie_credentialclasses
+    }
+
 
 
 class OozieMockBase(object):
 class OozieMockBase(object):
 
 
@@ -1206,12 +1216,22 @@ class TestEditor(OozieMockBase):
       saved = beeswax.conf.HIVE_CONF_DIR
       saved = beeswax.conf.HIVE_CONF_DIR
       beeswax.conf.HIVE_CONF_DIR = Getter()
       beeswax.conf.HIVE_CONF_DIR = Getter()
 
 
+      action1 = Node.objects.get(workflow=self.wf, name='MyHive')
+      action1.credentials = [{'name': 'hcat', 'value': True}, {'name': 'hbase', 'value': False}]
+      action1.save()
+
       xml = self.wf.to_xml(mapping={
       xml = self.wf.to_xml(mapping={
-         'is_kerberized_hive': True,
-         'credential_type': 'hcat',
-         'thrift_server': 'thrift://darkside-1234:9999',
-         'hive_principal': 'hive/darkside-1234@test.com'
-      })
+          'credentials': {
+              'hcat': {
+                  'xml_name': 'hcat',
+                  'properties': [
+                      ('hcat.metastore.uri', 'thrift://hue-koh-chang:9999'),
+                      ('hcat.metastore.principal', 'hive')
+                  ]
+              }
+          }
+        }
+      )
 
 
       assert_true("""
       assert_true("""
 <workflow-app name="wf-name-1" xmlns="uri:oozie:workflow:0.4">
 <workflow-app name="wf-name-1" xmlns="uri:oozie:workflow:0.4">
@@ -1225,19 +1245,19 @@ class TestEditor(OozieMockBase):
             </configuration>
             </configuration>
   </global>
   </global>
   <credentials>
   <credentials>
-    <credential name='hive_credentials' type='hcat'>
+    <credential name="hcat" type="hcat">
       <property>
       <property>
         <name>hcat.metastore.uri</name>
         <name>hcat.metastore.uri</name>
-        <value>thrift://darkside-1234:9999</value>
+        <value>thrift://hue-koh-chang:9999</value>
       </property>
       </property>
       <property>
       <property>
         <name>hcat.metastore.principal</name>
         <name>hcat.metastore.principal</name>
-        <value>hive/darkside-1234@test.com</value>
+        <value>hive</value>
       </property>
       </property>
     </credential>
     </credential>
-   </credentials>
+  </credentials>
     <start to="MyHive"/>
     <start to="MyHive"/>
-    <action name="MyHive" cred='hive_credentials'>
+    <action name="MyHive" cred="hcat">
         <hive xmlns="uri:oozie:hive-action:0.2">
         <hive xmlns="uri:oozie:hive-action:0.2">
             <job-tracker>${jobTracker}</job-tracker>
             <job-tracker>${jobTracker}</job-tracker>
             <name-node>${nameNode}</name-node>
             <name-node>${nameNode}</name-node>
@@ -1422,7 +1442,8 @@ class TestEditor(OozieMockBase):
 """<coordinator-app name="MyCoord"
 """<coordinator-app name="MyCoord"
   frequency="${coord:days(1)}"
   frequency="${coord:days(1)}"
   start="2012-07-01T00:00Z" end="2012-07-04T00:00Z" timezone="America/Los_Angeles"
   start="2012-07-01T00:00Z" end="2012-07-04T00:00Z" timezone="America/Los_Angeles"
-  xmlns="uri:oozie:coordinator:0.2">
+  xmlns="uri:oozie:coordinator:0.2"
+  >
   <controls>
   <controls>
     <timeout>100</timeout>
     <timeout>100</timeout>
     <concurrency>3</concurrency>
     <concurrency>3</concurrency>
@@ -1500,7 +1521,8 @@ class TestEditor(OozieMockBase):
 """<coordinator-app name="MyCoord"
 """<coordinator-app name="MyCoord"
   frequency="${coord:days(1)}"
   frequency="${coord:days(1)}"
   start="2012-07-01T00:00Z" end="2012-07-04T00:00Z" timezone="America/Los_Angeles"
   start="2012-07-01T00:00Z" end="2012-07-04T00:00Z" timezone="America/Los_Angeles"
-  xmlns="uri:oozie:coordinator:0.2">
+  xmlns="uri:oozie:coordinator:0.2"
+  >
   <controls>
   <controls>
     <timeout>100</timeout>
     <timeout>100</timeout>
     <concurrency>3</concurrency>
     <concurrency>3</concurrency>

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

@@ -100,6 +100,6 @@ urlpatterns += patterns(
   url(r'^submit_external_job/(?P<application_path>.+?)$', 'submit_external_job', name='submit_external_job'),
   url(r'^submit_external_job/(?P<application_path>.+?)$', 'submit_external_job', name='submit_external_job'),
 
 
   url(r'^list_oozie_info/$', 'list_oozie_info', name='list_oozie_info'),
   url(r'^list_oozie_info/$', 'list_oozie_info', name='list_oozie_info'),
-  
+
   url(r'^list_oozie_sla/$', 'list_oozie_sla', name='list_oozie_sla'),
   url(r'^list_oozie_sla/$', 'list_oozie_sla', name='list_oozie_sla'),
 )
 )

+ 1 - 4
apps/oozie/src/oozie/utils.py

@@ -62,9 +62,6 @@ def model_to_dict(model):
   for field in model._meta.fields:
   for field in model._meta.fields:
     try:
     try:
       attr = getattr(model, field.name, None)
       attr = getattr(model, field.name, None)
-#      if field.name == 'data' and issubclass(type(model), Node):
-#        if hasattr(model, 'sla'):
-#          dictionary['sla'] = model.sla
       if isinstance(attr, models.Model):
       if isinstance(attr, models.Model):
         dictionary[field.name] = attr.id
         dictionary[field.name] = attr.id
       elif isinstance(attr, datetime):
       elif isinstance(attr, datetime):
@@ -142,4 +139,4 @@ def oozie_to_hue_frequency(frequency_string):
   # Get frequency number and units from frequency
   # Get frequency number and units from frequency
   # frequency units and number are just different parts of the EL function.
   # frequency units and number are just different parts of the EL function.
   matches = re.match(FREQUENCY_REGEX, frequency_string)
   matches = re.match(FREQUENCY_REGEX, frequency_string)
-  return matches.group('frequency_unit'), matches.group('frequency_number')
+  return matches.group('frequency_unit'), matches.group('frequency_number')

+ 0 - 1
apps/oozie/src/oozie/views/api.py

@@ -32,7 +32,6 @@ from oozie.decorators import check_job_access_permission, check_job_edition_perm
 from oozie.utils import model_to_dict, format_dict_field_values, format_field_value
 from oozie.utils import model_to_dict, format_dict_field_values, format_field_value
 
 
 
 
-
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
 
 

+ 10 - 10
apps/oozie/src/oozie/views/dashboard.py

@@ -335,27 +335,27 @@ def list_oozie_sla(request):
     # filter=nominal_start=2013-06-18T00:01Z;nominal_end=2013-06-23T00:01Z;app_name=my-sla-app
     # filter=nominal_start=2013-06-18T00:01Z;nominal_end=2013-06-23T00:01Z;app_name=my-sla-app
     params = {}
     params = {}
     print request.POST
     print request.POST
-    
+
     job_name = request.POST.get('job_name')
     job_name = request.POST.get('job_name')
     if job_name.endswith('-oozie-oozi-W'):
     if job_name.endswith('-oozie-oozi-W'):
       if 'isParent' in request.POST:
       if 'isParent' in request.POST:
         params['parent_id'] = job_name
         params['parent_id'] = job_name
-      else:    
+      else:
         params['id'] = job_name
         params['id'] = job_name
     else:
     else:
       params['app_name'] = job_name
       params['app_name'] = job_name
-      
+
     if request.POST.get('start'):
     if request.POST.get('start'):
       params['nominal_start'] = request.POST.get('start')
       params['nominal_start'] = request.POST.get('start')
     if request.POST.get('end'):
     if request.POST.get('end'):
       params['nominal_end'] = request.POST.get('end')
       params['nominal_end'] = request.POST.get('end')
-      
+
     oozie_slas = api.get_oozie_slas(**params)
     oozie_slas = api.get_oozie_slas(**params)
-    print oozie_slas  
-    
+    print oozie_slas
+
   else:
   else:
     oozie_slas = [] # or get latest?
     oozie_slas = [] # or get latest?
-  
+
   columns = [
   columns = [
     'slaStatus',
     'slaStatus',
     'id',
     'id',
@@ -366,13 +366,13 @@ def list_oozie_sla(request):
     'expectedStart',
     'expectedStart',
     'actualStart',
     'actualStart',
     'expectedEnd',
     'expectedEnd',
-    'actualEnd',                  
+    'actualEnd',
     'jobStatus',
     'jobStatus',
     #'expectedDuration',
     #'expectedDuration',
     #'actualDuration',
     #'actualDuration',
     'lastModified'
     'lastModified'
-  ]  
-  
+  ]
+
   if request.REQUEST.get('format') == 'json':
   if request.REQUEST.get('format') == 'json':
     massaged_slas = []
     massaged_slas = []
     for sla in oozie_slas:
     for sla in oozie_slas:

+ 12 - 5
apps/oozie/src/oozie/views/editor.py

@@ -35,6 +35,9 @@ from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.i18n import smart_str
 from desktop.lib.i18n import smart_str
 from desktop.lib.rest.http_client import RestException
 from desktop.lib.rest.http_client import RestException
 from desktop.models import Document
 from desktop.models import Document
+
+from liboozie.credentials import Credentials
+from liboozie.oozie_api import get_oozie
 from liboozie.submittion import Submission
 from liboozie.submittion import Submission
 
 
 from filebrowser.lib.archives import archive_factory
 from filebrowser.lib.archives import archive_factory
@@ -78,7 +81,7 @@ def list_trashed_workflows(request):
 
 
 def list_coordinators(request, workflow_id=None):
 def list_coordinators(request, workflow_id=None):
   data = Document.objects.available(Coordinator, request.user)
   data = Document.objects.available(Coordinator, request.user)
-    
+
   if workflow_id is not None:
   if workflow_id is not None:
     data = [job for job in data if job.workflow.id == workflow_id]
     data = [job for job in data if job.workflow.id == workflow_id]
 
 
@@ -211,7 +214,7 @@ def import_coordinator(request):
         else:
         else:
           Coordinator.objects.filter(id=coordinator.id).delete()
           Coordinator.objects.filter(id=coordinator.id).delete()
           raise PopupException(_('Archive should be a Zip.'))
           raise PopupException(_('Archive should be a Zip.'))
-      
+
       Document.objects.link(coordinator, owner=request.user, name=coordinator.name, description=coordinator.description)
       Document.objects.link(coordinator, owner=request.user, name=coordinator.name, description=coordinator.description)
       request.info(_('Coordinator imported'))
       request.info(_('Coordinator imported'))
       return redirect(reverse('oozie:edit_coordinator', kwargs={'coordinator': coordinator.id}))
       return redirect(reverse('oozie:edit_coordinator', kwargs={'coordinator': coordinator.id}))
@@ -245,6 +248,9 @@ def edit_workflow(request, workflow):
   workflow_form = WorkflowForm(instance=workflow)
   workflow_form = WorkflowForm(instance=workflow)
   user_can_access_job = workflow.can_read(request.user)
   user_can_access_job = workflow.can_read(request.user)
   user_can_edit_job = workflow.is_editable(request.user)
   user_can_edit_job = workflow.is_editable(request.user)
+  api = get_oozie(request.user)
+  credentials = Credentials()
+  credentials.fetch(api)
 
 
   return render('editor/edit_workflow.mako', request, {
   return render('editor/edit_workflow.mako', request, {
     'workflow_form': workflow_form,
     'workflow_form': workflow_form,
@@ -257,7 +263,8 @@ def edit_workflow(request, workflow):
     'default_link_form': DefaultLinkForm(action=workflow.start),
     'default_link_form': DefaultLinkForm(action=workflow.start),
     'node_form': NodeForm(),
     'node_form': NodeForm(),
     'action_forms': [(node_type, design_form_by_type(node_type, request.user, workflow)())
     'action_forms': [(node_type, design_form_by_type(node_type, request.user, workflow)())
-                     for node_type in ACTION_TYPES.iterkeys()]
+                     for node_type in ACTION_TYPES.iterkeys()],
+    'credentials': json.dumps(credentials.credentials.keys())
   })
   })
 
 
 
 
@@ -459,7 +466,7 @@ def edit_coordinator(request, coordinator):
     data_output_formset = DataOutputFormSet(request.POST, request.FILES, instance=coordinator)
     data_output_formset = DataOutputFormSet(request.POST, request.FILES, instance=coordinator)
     new_data_input_formset = NewDataInputFormSet(request.POST, request.FILES, instance=coordinator, prefix='input')
     new_data_input_formset = NewDataInputFormSet(request.POST, request.FILES, instance=coordinator, prefix='input')
     new_data_output_formset = NewDataOutputFormSet(request.POST, request.FILES, instance=coordinator, prefix='output')
     new_data_output_formset = NewDataOutputFormSet(request.POST, request.FILES, instance=coordinator, prefix='output')
-    print request.POST
+
     if coordinator_form.is_valid() and dataset_formset.is_valid() and data_input_formset.is_valid() and data_output_formset.is_valid() \
     if coordinator_form.is_valid() and dataset_formset.is_valid() and data_input_formset.is_valid() and data_output_formset.is_valid() \
         and new_data_input_formset.is_valid() and new_data_output_formset.is_valid():
         and new_data_input_formset.is_valid() and new_data_output_formset.is_valid():
       coordinator = coordinator_form.save()
       coordinator = coordinator_form.save()
@@ -468,7 +475,7 @@ def edit_coordinator(request, coordinator):
       data_output_formset.save()
       data_output_formset.save()
       new_data_input_formset.save()
       new_data_input_formset.save()
       new_data_output_formset.save()
       new_data_output_formset.save()
-      
+
       coordinator.sla = json.loads(request.POST.get('sla'))
       coordinator.sla = json.loads(request.POST.get('sla'))
       coordinator.save()
       coordinator.save()
 
 

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

@@ -435,12 +435,7 @@ var WorkflowModule = function($, NodeModelChooser, Node, ForkNode, DecisionNode,
     self.sla = ko.computed(function() {
     self.sla = ko.computed(function() {
       return self.data.sla();
       return self.data.sla();
     });
     });
-    self.globalProperties = ko.computed(function() {
-      return self.data.globalProperties();
-    });
-    self.globalConfig = ko.computed(function() {
-      return self.data.globalConfig();
-    });
+
 
 
     self.url = ko.computed(function() {
     self.url = ko.computed(function() {
       return '/oozie/workflows/' + self.id();
       return '/oozie/workflows/' + self.id();
@@ -687,41 +682,6 @@ var WorkflowModule = function($, NodeModelChooser, Node, ForkNode, DecisionNode,
       var self = this;
       var self = this;
       self.job_properties.remove(data);
       self.job_properties.remove(data);
     },
     },
-
-    addGlobalProperty: function(data, event) {
-      var self = this;
-      var prop = { name: ko.observable(""), value: ko.observable("") };
-      // force bubble up to containing observable array.
-      prop.name.subscribe(function(){
-        self.data.global_properties.valueHasMutated();
-      });
-      prop.value.subscribe(function(){
-        self.data.global_properties.valueHasMutated();
-      });
-      self.data.global_properties.push(prop);
-    },
-
-    removeGlobalProperty: function(data, event) {
-      var self = this;
-      self.data.global_properties.remove(data);
-    },
-
-    addGlobalConfig: function(data, event) {
-      var self = this;
-      var prop = { name: ko.observable(""), value: ko.observable("") };
-      // force bubble up to containing observable array.
-      prop.name.subscribe(function(){
-        self.data.global_config.valueHasMutated();
-      });
-      prop.value.subscribe(function(){
-        self.data.global_config.valueHasMutated();
-      });
-      self.data.global_config.push(prop);
-    },
-    removeGlobalConfig: function(data, event) {
-      var self = this;
-      self.data.global_config.remove(data);
-    },
     
     
     // Workflow UI
     // Workflow UI
     // Function to build nodes... recursively.
     // Function to build nodes... recursively.

+ 29 - 24
apps/oozie/static/js/workflow.models.js

@@ -32,10 +32,9 @@ var DEFAULT_SLA = [
 ];
 ];
 
 
 function getDefaultData() {
 function getDefaultData() {
- return {
-  'sla': DEFAULT_SLA.slice(0),
-  'global_properties': [],
-  'global_config': []
+  return {
+    'sla': DEFAULT_SLA.slice(0),
+    'credentials': []
   };
   };
 }
 }
 
 
@@ -389,22 +388,28 @@ var ModelModule = function($) {
   return module;
   return module;
 };
 };
 
 
-function initializeData() {
+function initializeWorkflowData() {
   var self = this;
   var self = this;
 
 
   self.data = ($.type(self.data) == "string") ? $.parseJSON(self.data) : self.data;
   self.data = ($.type(self.data) == "string") ? $.parseJSON(self.data) : self.data;
 
 
-  if (!('sla' in self.data)) {
+  if (! ('sla' in self.data)) {
     self.data['sla'] = DEFAULT_SLA.slice(0);
     self.data['sla'] = DEFAULT_SLA.slice(0);
   }
   }
+}
 
 
-  if (!('global_properties' in self.data)) {
-    self.data['global_properties'] = [];
-  }
+function initializeNodeData() {
+  var self = this;
 
 
-  if (!('global_config' in self.data)) {
-    self.data['global_config'] = [];
+  self.data = ($.type(self.data) == "string") ? $.parseJSON(self.data) : self.data;
+
+  if (! ('sla' in self.data)) {
+    self.data['sla'] = DEFAULT_SLA.slice(0);
   }
   }
+  
+  if (! ('credentials' in self.data)) {
+	self.data['credentials'] = getDefaultData()['credentials'].slice(0);
+  }  
 }
 }
 
 
 var WorkflowModel = ModelModule($);
 var WorkflowModel = ModelModule($);
@@ -420,7 +425,7 @@ $.extend(WorkflowModel.prototype, {
   parameters: '[]',
   parameters: '[]',
   job_xml: '',
   job_xml: '',
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeWorkflowData
 });
 });
 
 
 var NodeModel = ModelModule($);
 var NodeModel = ModelModule($);
@@ -466,7 +471,7 @@ $.extend(DistCPModel.prototype, {
   params: '[]',
   params: '[]',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var MapReduceModel = ModelModule($);
 var MapReduceModel = ModelModule($);
@@ -484,7 +489,7 @@ $.extend(MapReduceModel.prototype, {
   job_xml: '',
   job_xml: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var StreamingModel = ModelModule($);
 var StreamingModel = ModelModule($);
@@ -501,7 +506,7 @@ $.extend(StreamingModel.prototype, {
   reducer: '',
   reducer: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var JavaModel = ModelModule($);
 var JavaModel = ModelModule($);
@@ -523,7 +528,7 @@ $.extend(JavaModel.prototype, {
   capture_output: false,
   capture_output: false,
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var PigModel = ModelModule($);
 var PigModel = ModelModule($);
@@ -542,7 +547,7 @@ $.extend(PigModel.prototype, {
   script_path: '',
   script_path: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var HiveModel = ModelModule($);
 var HiveModel = ModelModule($);
@@ -561,7 +566,7 @@ $.extend(HiveModel.prototype, {
   script_path: '',
   script_path: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var SqoopModel = ModelModule($);
 var SqoopModel = ModelModule($);
@@ -580,7 +585,7 @@ $.extend(SqoopModel.prototype, {
   script_path: '',
   script_path: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var ShellModel = ModelModule($);
 var ShellModel = ModelModule($);
@@ -600,7 +605,7 @@ $.extend(ShellModel.prototype, {
   capture_output: false,
   capture_output: false,
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var SshModel = ModelModule($);
 var SshModel = ModelModule($);
@@ -617,7 +622,7 @@ $.extend(SshModel.prototype, {
   capture_output: false,
   capture_output: false,
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var FsModel = ModelModule($);
 var FsModel = ModelModule($);
@@ -634,7 +639,7 @@ $.extend(FsModel.prototype, {
   touchzs: '[]',
   touchzs: '[]',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var EmailModel = ModelModule($);
 var EmailModel = ModelModule($);
@@ -650,7 +655,7 @@ $.extend(EmailModel.prototype, {
   body: '',
   body: '',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var SubWorkflowModel = ModelModule($);
 var SubWorkflowModel = ModelModule($);
@@ -665,7 +670,7 @@ $.extend(SubWorkflowModel.prototype, {
   job_properties: '[]',
   job_properties: '[]',
   child_links: [],
   child_links: [],
   data: getDefaultData(),
   data: getDefaultData(),
-  initialize: initializeData
+  initialize: initializeNodeData
 });
 });
 
 
 var GenericModel = ModelModule($);
 var GenericModel = ModelModule($);

+ 40 - 2
apps/oozie/static/js/workflow.node.js

@@ -132,6 +132,46 @@ var NodeModule = function($, IdGeneratorTable, NodeFields) {
       });
       });
     }
     }
 
 
+    if (self.data && self.data.credentials) {
+      self.credentials = ko.computed(function() {
+        return self.data.credentials();
+      });
+
+      // A bit complicated but just update the available credentials
+      var new_creds = OOZIE_CREDENTIALS.slice(0);
+      var old_creds = [];
+      var to_remove = [];
+
+      $.each(self.credentials(), function(index, credential) {
+        if (credential != null) {
+          if ($.inArray(credential.name(), OOZIE_CREDENTIALS) != -1) {
+            // A new credential was added to the Oozie server
+            new_creds = jQuery.grep(new_creds, function(value) {
+              return value != credential.name();
+            });
+          } else {
+           // A credential was removed from the Oozie server
+           to_remove.push(credential);
+          }
+        }
+      });
+
+      $.each(new_creds, function(index, name) {
+        var prop = { name: ko.observable(name), value: ko.observable(false) };
+        prop.name.subscribe(function(){
+          self.data.credentials.valueHasMutated();
+        });
+        prop.value.subscribe(function(){
+          self.data.credentials.valueHasMutated();
+        });
+        self.data.credentials.push(prop);
+      });
+
+      $.each(to_remove, function(index, name) {
+        self.data.credentials.remove(name);
+      });
+    }
+
     if ('files' in model) {
     if ('files' in model) {
       //// WARNING: The following order should be preserved!
       //// WARNING: The following order should be preserved!
 
 
@@ -230,8 +270,6 @@ var NodeModule = function($, IdGeneratorTable, NodeFields) {
 
 
       var options = {};
       var options = {};
 
 
-      // data = $.extend(true, {}, self.model);
-
       var success = false;
       var success = false;
       var request = $.extend({
       var request = $.extend({
         url: '/oozie/workflows/' + self._workflow.id() + '/nodes/' + self.node_type() + '/validate',
         url: '/oozie/workflows/' + self._workflow.id() + '/nodes/' + self.node_type() + '/validate',

+ 93 - 0
desktop/libs/liboozie/src/liboozie/credentials.py

@@ -0,0 +1,93 @@
+#!/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
+
+from django.utils.translation import ugettext as _
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Credentials(object):
+  NAME_TO_CLASS_MAPPING = {
+      "hcat": "org.apache.oozie.action.hadoop.HCatCredentials",
+      "hive2": "org.apache.oozie.action.hadoop.Hive2Credentials",
+      "hbase": "org.apache.oozie.action.hadoop.HbaseCredentials",
+  }
+
+  def __init__(self, credentials=None):
+    self.credentials = credentials
+
+  def fetch(self, oozie_api):
+    configuration = oozie_api.get_configuration()
+    self.credentials = self._parse_oozie(configuration)
+
+  def _parse_oozie(self, configuration_dic):
+    return dict([cred.strip().split('=') for cred in configuration_dic.get('oozie.credentials.credentialclasses', '').strip().split(',') if cred])
+
+  @property
+  def class_to_name_credentials(self):
+    return dict((v,k) for k, v in self.credentials.iteritems())
+
+  def get_properties(self, metastore=None):
+    credentials = {}
+    from beeswax import hive_site
+
+    if metastore is None:
+      metastore = hive_site.get_metastore()
+
+    if not metastore:
+      metastore = {}
+      LOG.warn('Could not get all the Oozie credentials: hive-site.xml required on the Hue host.')
+
+    credentials[self.hive_name] = {
+      'xml_name': self.hive_name,
+      'properties': [
+         ('hcat.metastore.uri', metastore.get('thrift_uri')),
+         ('hcat.metastore.principal', metastore.get('kerberos_principal')),
+      ]
+    }
+
+    credentials[self.hiveserver2_name] = {
+      'xml_name': self.hiveserver2_name,
+      'properties': [
+         ('hive2.jdbc.url', hive_site.hiveserver2_jdbc_url()),
+         ('hive2.server.principal', metastore.get('kerberos_principal')),
+      ]
+    }
+
+    credentials[self.hbase_name] = {
+      'xml_name': self.hbase_name,
+      'properties': []
+    }
+
+    LOG.info('Using credentials: %s' % credentials)
+
+    return credentials
+
+  @property
+  def hive_name(self):
+    return self.class_to_name_credentials.get('org.apache.oozie.action.hadoop.HCatCredentials')
+
+  @property
+  def hiveserver2_name(self):
+    return self.class_to_name_credentials.get('org.apache.oozie.action.hadoop.Hive2Credentials')
+
+  @property
+  def hbase_name(self):
+    return self.class_to_name_credentials.get('org.apache.oozie.action.hadoop.HbaseCredentials')

+ 88 - 0
desktop/libs/liboozie/src/liboozie/credentials_test.py

@@ -0,0 +1,88 @@
+#!/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
+
+from nose.tools import assert_equal, assert_true
+
+import beeswax.conf
+
+from liboozie.credentials import Credentials
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestCredentials():
+  CREDENTIALS = {
+    "hcat": "org.apache.oozie.action.hadoop.HCatCredentials",
+    "hive2": "org.apache.oozie.action.hadoop.Hive2Credentials",
+    "hbase": "org.apache.oozie.action.hadoop.HbaseCredentials"
+  }
+
+  def test_parse_oozie(self):
+    oozie_credentialclasses = """
+           hbase=org.apache.oozie.action.hadoop.HbaseCredentials,
+           hcat=org.apache.oozie.action.hadoop.HCatCredentials,
+           hive2=org.apache.oozie.action.hadoop.Hive2Credentials
+    """
+    oozie_config = {'oozie.credentials.credentialclasses': oozie_credentialclasses}
+
+    creds = Credentials()
+
+    assert_equal({
+        'hive2': 'org.apache.oozie.action.hadoop.Hive2Credentials',
+        'hbase': 'org.apache.oozie.action.hadoop.HbaseCredentials',
+        'hcat': 'org.apache.oozie.action.hadoop.HCatCredentials'
+      }, creds._parse_oozie(oozie_config)
+    )
+
+  def test_gen_properties(self):
+    creds = Credentials(credentials=TestCredentials.CREDENTIALS.copy())
+
+    metastore = {
+      'thrift_uri': 'thrift://hue-koh-chang:9999',
+      'kerberos_principal': 'hive',
+    }
+
+    finish = (
+      beeswax.conf.HIVE_SERVER_HOST.set_for_testing('hue-koh-chang'),
+      beeswax.conf.HIVE_SERVER_PORT.set_for_testing(12345),
+    )
+
+    try:
+      assert_equal({
+          'hcat': {
+            'xml_name': 'hcat',
+            'properties': [
+                ('hcat.metastore.uri', 'thrift://hue-koh-chang:9999'),
+                ('hcat.metastore.principal', 'hive')
+            ]},
+         'hive2': {
+            'xml_name': 'hive2',
+            'properties': [
+                ('hive2.jdbc.url', 'jdbc:hive2://hue-koh-chang:12345'),
+                ('hive2.server.principal', 'hive')
+          ]},
+         'hbase': {
+             'xml_name': 'hbase',
+             'properties': []
+          }
+        }, creds.get_properties(metastore))
+    finally:
+      for f in finish:
+        f()

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

@@ -298,6 +298,6 @@ class OozieApi(object):
       nominal_end=2013-06-23T00:01Z
       nominal_end=2013-06-23T00:01Z
     """
     """
     params = self._get_params()
     params = self._get_params()
-    params['filter'] = ';'.join(['%s=%s' % (key, val) for key, val in kwargs.iteritems()])    
+    params['filter'] = ';'.join(['%s=%s' % (key, val) for key, val in kwargs.iteritems()])
     resp = self._root.get('sla', params)
     resp = self._root.get('sla', params)
-    return resp['slaSummaryList'] 
+    return resp['slaSummaryList']

+ 6 - 11
desktop/libs/liboozie/src/liboozie/submittion.py

@@ -29,6 +29,7 @@ from hadoop.fs.hadoopfs import Hdfs
 from liboozie.oozie_api import get_oozie
 from liboozie.oozie_api import get_oozie
 from liboozie.conf import REMOTE_DEPLOYMENT_DIR
 from liboozie.conf import REMOTE_DEPLOYMENT_DIR
 from jobsub.parameterization import find_variables
 from jobsub.parameterization import find_variables
+from liboozie.credentials import Credentials
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
@@ -186,17 +187,11 @@ class Submission(object):
         self.job.HUE_ID: self.job.id
         self.job.HUE_ID: self.job.id
       })
       })
 
 
-    # Even if no Hive action for now
-    from beeswax.hive_site import get_metastore
-    metastore = get_metastore()
-
-    if metastore and metastore.get('use_sasl') and False: # Disabled for now
-      self.properties.update({
-         'is_kerberized_hive': True,
-         'credential_type': 'hcat',
-         'thrift_server': metastore.get('thrift_uri'),
-         'hive_principal': metastore.get('kerberos_principal')
-      })
+    # Generate credentials when using security
+    if self.api.security_enabled:
+      credentials = Credentials()
+      credentials.fetch(self.api)
+      self.properties['credentials'] = credentials.get_properties()
 
 
   def _create_deployment_dir(self):
   def _create_deployment_dir(self):
     """
     """

+ 0 - 2
desktop/libs/liboozie/src/liboozie/tests.py

@@ -116,8 +116,6 @@ class TestSubmission():
 
 
   def setUp(self):
   def setUp(self):
     self.c = make_logged_in_client(is_superuser=False)
     self.c = make_logged_in_client(is_superuser=False)
-#    grant_access("test", "test", "oozie")
-#    add_to_group("test")
     self.user = User.objects.get(username='test')
     self.user = User.objects.get(username='test')
 
 
   def test_get_external_parameters(self):
   def test_get_external_parameters(self):