Преглед изворни кода

HUE-1656 [oozie] SLA edition by Workflow action

Romain Rigaux пре 12 година
родитељ
комит
e3bc37b
25 измењених фајлова са 572 додато и 114 уклоњено
  1. 1 1
      apps/oozie/src/oozie/forms.py
  2. 342 0
      apps/oozie/src/oozie/migrations/0025_auto__add_field_node_data.py
  3. 29 15
      apps/oozie/src/oozie/models.py
  4. 16 3
      apps/oozie/src/oozie/templates/editor/action_utils.mako
  5. 22 37
      apps/oozie/src/oozie/templates/editor/edit_workflow.mako
  6. 13 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-common.xml.mako
  7. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-distcp.xml.mako
  8. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-email.xml.mako
  9. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-fs.xml.mako
  10. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-hive.xml.mako
  11. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-java.xml.mako
  12. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-mapreduce.xml.mako
  13. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-pig.xml.mako
  14. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-shell.xml.mako
  15. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-sqoop.xml.mako
  16. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-ssh.xml.mako
  17. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-streaming.xml.mako
  18. 1 0
      apps/oozie/src/oozie/templates/editor/gen/workflow-subworkflow.xml.mako
  19. 2 10
      apps/oozie/src/oozie/templates/editor/gen/workflow.xml.mako
  20. 18 0
      apps/oozie/src/oozie/templates/utils.inc.mako
  21. 48 5
      apps/oozie/src/oozie/tests.py
  22. 5 1
      apps/oozie/src/oozie/utils.py
  23. 4 2
      apps/oozie/src/oozie/views/api.py
  24. 24 12
      apps/oozie/static/js/workflow.models.js
  25. 36 28
      apps/oozie/static/js/workflow.node.js

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

@@ -94,7 +94,7 @@ class ImportJobsubDesignForm(forms.Form):
 
 class NodeForm(forms.ModelForm):
   class Meta:
-    ALWAYS_HIDE = ('workflow', 'children', 'node_type')
+    ALWAYS_HIDE = ('workflow', 'children', 'node_type', 'data')
     model = Node
     exclude = ALWAYS_HIDE
 

+ 342 - 0
apps/oozie/src/oozie/migrations/0025_auto__add_field_node_data.py

@@ -0,0 +1,342 @@
+# -*- coding: 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 field 'Node.data'
+        db.add_column('oozie_node', 'data',
+                      self.gf('django.db.models.fields.TextField')(default='{}', blank=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Node.data'
+        db.delete_column('oozie_node', 'data')
+
+
+    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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            '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': {'ordering': "('name',)", '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'})
+        },
+        'desktop.document': {
+            'Meta': {'object_name': 'Document'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'extra': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'doc_owner'", 'to': "orm['auth.User']"}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['desktop.DocumentTag']", 'db_index': 'True', 'symmetrical': 'False'}),
+            'version': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+        },
+        'desktop.documenttag': {
+            'Meta': {'object_name': 'DocumentTag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'tag': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+        },
+        'oozie.bundle': {
+            'Meta': {'object_name': 'Bundle', '_ormbases': ['oozie.Job']},
+            'coordinators': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['oozie.Coordinator']", 'through': "orm['oozie.BundledCoordinator']", 'symmetrical': 'False'}),
+            'job_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Job']", 'unique': 'True', 'primary_key': 'True'}),
+            'kick_off_time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 11, 0, 0)'})
+        },
+        'oozie.bundledcoordinator': {
+            'Meta': {'object_name': 'BundledCoordinator'},
+            'bundle': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Bundle']"}),
+            'coordinator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Coordinator']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'parameters': ('django.db.models.fields.TextField', [], {'default': '\'[{"name":"oozie.use.system.libpath","value":"true"}]\''})
+        },
+        '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(2013, 12, 14, 0, 0)'}),
+            '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_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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(2013, 12, 11, 0, 0)'}),
+            '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'},
+            'advanced_end_instance': ('django.db.models.fields.CharField', [], {'default': "'0'", 'max_length': '128', 'blank': 'True'}),
+            'advanced_start_instance': ('django.db.models.fields.CharField', [], {'default': "'0'", 'max_length': '128'}),
+            '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'}),
+            'instance_choice': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '10'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'start': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 11, 0, 0)'}),
+            '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.decision': {
+            'Meta': {'object_name': 'Decision'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.decisionend': {
+            'Meta': {'object_name': 'DecisionEnd'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.distcp': {
+            'Meta': {'object_name': 'DistCp'},
+            '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', 'primary_key': 'True'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"})
+        },
+        'oozie.email': {
+            'Meta': {'object_name': 'Email'},
+            'body': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'cc': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'subject': ('django.db.models.fields.TextField', [], {'default': "''"}),
+            'to': ('django.db.models.fields.TextField', [], {'default': "''"})
+        },
+        '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.fs': {
+            'Meta': {'object_name': 'Fs'},
+            'chmods': ('django.db.models.fields.TextField', [], {'default': "'[]'", 'blank': 'True'}),
+            'deletes': ('django.db.models.fields.TextField', [], {'default': "'[]'", 'blank': 'True'}),
+            'mkdirs': ('django.db.models.fields.TextField', [], {'default': "'[]'", 'blank': 'True'}),
+            'moves': ('django.db.models.fields.TextField', [], {'default': "'[]'", 'blank': 'True'}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'touchzs': ('django.db.models.fields.TextField', [], {'default': "'[]'", 'blank': 'True'})
+        },
+        'oozie.generic': {
+            'Meta': {'object_name': 'Generic'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'xml': ('django.db.models.fields.TextField', [], {'default': "''"})
+        },
+        '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.TextField', [], {'default': "'[]'"}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': '\'[{"name":"oozie.hive.defaults","value":"hive-site.xml"}]\''}),
+            '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.TextField', [], {'default': "'[]'"}),
+            'args': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'capture_output': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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'},
+            'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'blank': 'True'}),
+            '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'}),
+            'is_trashed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': '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': '\'[{"name":"oozie.use.system.libpath","value":"true"}]\''}),
+            '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.TextField', [], {'default': "'[]'"}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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', 'primary_key': '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']"}),
+            'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'blank': 'True'}),
+            '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.TextField', [], {'default': "'[]'"}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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', '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.shell': {
+            'Meta': {'object_name': 'Shell'},
+            'archives': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'capture_output': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'command': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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', 'primary_key': 'True'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"})
+        },
+        'oozie.sqoop': {
+            'Meta': {'object_name': 'Sqoop'},
+            'archives': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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', 'primary_key': 'True'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'prepares': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'script_path': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
+        },
+        'oozie.ssh': {
+            'Meta': {'object_name': 'Ssh'},
+            'capture_output': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'command': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'host': ('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'}),
+            'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'user': ('django.db.models.fields.CharField', [], {'max_length': '64'})
+        },
+        'oozie.start': {
+            'Meta': {'object_name': 'Start'},
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'oozie.streaming': {
+            'Meta': {'object_name': 'Streaming'},
+            'archives': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'files': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            '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.subworkflow': {
+            'Meta': {'object_name': 'SubWorkflow'},
+            'job_properties': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
+            'node_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['oozie.Node']", 'unique': 'True', 'primary_key': 'True'}),
+            'propagate_configuration': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'sub_workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['oozie.Workflow']"})
+        },
+        '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'}),
+            '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'}),
+            'managed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'start': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'start_workflow'", 'null': 'True', 'to': "orm['oozie.Start']"})
+        }
+    }
+
+    complete_apps = ['oozie']

+ 29 - 15
apps/oozie/src/oozie/models.py

@@ -16,6 +16,7 @@
 # limitations under the License.
 
 import json
+import copy
 import logging
 import re
 import StringIO
@@ -48,7 +49,6 @@ from hadoop.fs.hadoopfs import Hdfs
 from liboozie.submittion import Submission
 from liboozie.submittion import create_directories
 
-
 from oozie.conf import REMOTE_SAMPLE_DIR
 from oozie.utils import utc_datetime_format
 from oozie.timezones import TIMEZONES
@@ -60,7 +60,15 @@ LOG = logging.getLogger(__name__)
 PATH_MAX = 512
 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'))
-
+DEFAULT_SLA = [
+    {'key': 'enabled', 'value': False},
+    {'key': 'nominal-time', 'value': ''},
+    {'key': 'should-start', 'value': ''},
+    {'key': 'should-end', 'value': ''},
+    {'key': 'max-duration', 'value': ''},
+    {'key': 'alert-events', 'value': ''},
+    {'key': 'alert-contact', 'value': ''},        
+]
 
 class JobManager(models.Manager):
 
@@ -206,15 +214,7 @@ class Job(models.Model):
 
   @property
   def sla(self):
-    return json.loads(self.data).get('sla', [
-        {'key': 'enabled', 'value': False},
-        {'key': 'nominal-time', 'value': ''},
-        {'key': 'should-start', 'value': ''},
-        {'key': 'should-end', 'value': ''},
-        {'key': 'max-duration', 'value': ''},
-        {'key': 'alert-events', 'value': ''},
-        {'key': 'alert-contact', 'value': ''},        
-    ])
+    return json.loads(self.data).get('sla', copy.deepcopy(DEFAULT_SLA))
   
   @property
   def sla_jsescaped(self):
@@ -224,9 +224,6 @@ class Job(models.Model):
     data_json = json.loads(self.data)
     data_json['sla'] = sla  
     self.data = json.dumps(data_json)
-    
-    if self.get_type() == 'workflow':
-      self.schema_version = WorkflowManager.SCHEMA_VERSION_V5 if self.sla_enabled else WorkflowManager.SCHEMA_VERSION_V4
 
   @property
   def sla_enabled(self):
@@ -566,6 +563,10 @@ class Workflow(Job):
     workflow_xml = zfile.read('workflow.xml')
     return workflow_xml, metadata
 
+  @property
+  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')])
+
 
 class Link(models.Model):
   # Links to exclude when using get_children_link(), get_parent_links() in the API
@@ -602,7 +603,7 @@ class Node(models.Model):
                                help_text=_t('The type of action (e.g. MapReduce, Pig...)'))
   workflow = models.ForeignKey(Workflow)
   children = models.ManyToManyField('self', related_name='parents', symmetrical=False, through=Link)
-  #data = models.TextField(default=json.dumps({}))
+  data = models.TextField(blank=True, default=json.dumps({}))
 
   unique_together = ('workflow', 'name')
 
@@ -739,6 +740,19 @@ class Node(models.Model):
       'node_type': self.node_type
     })
 
+  @property
+  def sla(self):
+    return json.loads(self.data).get('sla', copy.deepcopy(DEFAULT_SLA))
+  
+  def set_sla(self, sla):
+    data_json = json.loads(self.data)
+    data_json['sla'] = sla  
+    self.data = json.dumps(data_json)
+
+  @property
+  def sla_enabled(self):
+    return self.sla[0]['value'] # #1 is enabled
+
 
 class Action(Node):
   types = ()

+ 16 - 3
apps/oozie/src/oozie/templates/editor/action_utils.mako

@@ -61,7 +61,7 @@
 
   ${ utils.render_constant(_('Action type'), node_type) }
 
-  <!-- ko if: $root.context().nodes && $root.context().error_node -->
+  
   <div class="control-group ">
     <label class="control-label">
       <a href="javascript:void(0);" id="advanced-btn" onclick="$('#node-advanced-container').toggle('hide')">
@@ -71,6 +71,16 @@
   </div>
 
   <div id="node-advanced-container" class="hide">
+
+        <div class="control-group">
+          <label class="control-label">
+              ${ _('SLA') }
+          </label>      
+      
+          ${ utils.slaForm() }  
+    </div>
+
+    <!-- ko if: $root.context().nodes && $root.context().error_node -->
     <div class="control-group">
       <label class="control-label">${_('Error link to')}</label>
       <div class="controls">
@@ -82,12 +92,15 @@
                              optionsValue: function(item) {
                                return item.id();
                              },
-                             value: $root.context().error_node"></select>
+                             value: $root.context().error_node">
+           </select>
         </div>
       </div>
     </div>
+    <!-- /ko -->
+
   </div>
-  <!-- /ko -->
+  
 
   <hr/>
 % endif

+ 22 - 37
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -112,31 +112,7 @@ ${ layout.menubar(section='workflows') }
       })
       %>
 
-
-        <div id="slaEditord" class="control-group">
-          <label class="control-label">
-              ${ _('SLA') }
-          </label>
-          
-          <div data-bind="foreach: sla">
-            <div class="controls">          
-              <div class="span3">
-                <span data-bind="text: getSLAText(key)['niceName']"></span>
-              </div>
-              <div class="span9">
-                <!-- ko if:  key == 'enabled' -->
-                <input type="checkbox" data-bind="checked: value"/>
-                <!-- /ko -->
-                <!-- ko if:  key != 'enabled' -->
-                <input type="text" data-bind="value: value, attr: {placeholder: getSLAText(key)['placeHolder']}" class="span7">
-                <!-- /ko -->
-              </div>
-            </div>
-          </div>      
-  
-        </div>
-
-        <div class="control-group ">
+        <div class="control-group">
           <label class="control-label">
             <a href="#" id="advanced-btn" onclick="$('#advanced-container').toggle('hide')">
               <i class="fa fa-share"></i> ${ _('advanced') }</a>
@@ -145,6 +121,15 @@ ${ layout.menubar(section='workflows') }
         </div>
 
       <div id="advanced-container" class="hide">
+      
+        <div id="slaEditord" class="control-group">
+          <label class="control-label">
+              ${ _('SLA') }
+          </label>
+          
+          ${ utils.slaForm() }  
+        </div>      
+      
         % 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}) }
         % endif
@@ -946,18 +931,6 @@ window.onbeforeunload = function (e) {
   }
 };
 
-function getSLAText(key) {
-  return {
-     'enabled': {'niceName': '${ _("Enabled") }', 'placeHolder': ''},
-     'nominal-time': {'niceName': '${ _("Nominal time") } *', 'placeHolder': '${"$"}{nominal_time}'},
-     'should-start': {'niceName': '${ _("Should start") }', 'placeHolder': '${"$"}{10 * MINUTES}'},
-     'should-end': {'niceName': '${ _("Should end") } *', 'placeHolder': '${"$"}{30 * MINUTES}'},
-     'max-duration': {'niceName': '${ _("Max duration") }', 'placeHolder': '${"$"}{30 * MINUTES}'},
-     'alert-events': {'niceName': '${ _("Alert events") }', 'placeHolder': 'start_miss,end_miss,duration_miss'},
-     'alert-contact': {'niceName': '${ _("Alert contact") }', 'placeHolder': 'joe@example.com,bob@example.com'} 
-  }[key]
-};
-
 window.onresize = function () {
   if (modal) {
     modal.recenter(280, 0);
@@ -1076,6 +1049,18 @@ function checkModelDirtiness() {
   }
 }
 
+function getSLAText(key) {
+  return {
+     'enabled': {'niceName': '${ _("Enabled") }', 'placeHolder': ''},
+     'nominal-time': {'niceName': '${ _("Nominal time") } *', 'placeHolder': '${"$"}{nominal_time}'},
+     'should-start': {'niceName': '${ _("Should start") }', 'placeHolder': '${"$"}{10 * MINUTES}'},
+     'should-end': {'niceName': '${ _("Should end") } *', 'placeHolder': '${"$"}{30 * MINUTES}'},
+     'max-duration': {'niceName': '${ _("Max duration") }', 'placeHolder': '${"$"}{30 * MINUTES}'},
+     'alert-events': {'niceName': '${ _("Alert events") }', 'placeHolder': 'start_miss,end_miss,duration_miss'},
+     'alert-contact': {'niceName': '${ _("Alert contact") }', 'placeHolder': 'joe@example.com,bob@example.com'} 
+  }[key]
+};
+
 </script>
 
 ${ utils.path_chooser_libs(True) }

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

@@ -65,3 +65,16 @@
         % endif
     % endfor
 </%def>
+
+
+<%def name="sla(element)">
+        % if element.sla_enabled:
+          <sla:info>
+          % for sla in element.sla:
+            % if sla['value'] and sla['key'] != 'enabled':
+            <sla:${ sla['key'] }>${ sla['value'] }</sla:${ sla['key'] }>
+            % endif
+          % endfor
+          </sla:info>
+        % endif
+</%def>

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

@@ -34,4 +34,5 @@
         </distcp>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -28,4 +28,5 @@
         </email>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -51,4 +51,5 @@
         </fs>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -38,4 +38,5 @@
         </hive>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -46,4 +46,5 @@
         </java>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -32,4 +32,5 @@
         </map-reduce>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -38,4 +38,5 @@
         </pig>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -42,4 +42,5 @@
         </shell>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -40,4 +40,5 @@
         </sqoop>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -32,4 +32,5 @@
         </ssh>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -30,4 +30,5 @@
         </map-reduce>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -29,4 +29,5 @@
         </sub-workflow>
         <ok to="${ node.get_oozie_child('ok') }"/>
         <error to="${ node.get_oozie_child('error') }"/>
+        ${ common.sla(node) }
     </action>

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

@@ -18,7 +18,7 @@
 <%namespace name="common" file="workflow-common.xml.mako" />
 
 
-<workflow-app name="${ workflow.name | x }" xmlns="${ workflow.schema_version }"${ ' xmlns:sla="uri:oozie:sla:0.2"' if workflow.sla_enabled else '' | n,unicode}>
+<workflow-app name="${ workflow.name | x }" xmlns="${ 'uri:oozie:workflow:0.5' if workflow.sla_workflow_enabled else workflow.schema_version | n,unicode }"${ ' xmlns:sla="uri:oozie:sla:0.2"' if workflow.sla_workflow_enabled else '' | n,unicode }>
   % if workflow.job_xml or workflow.get_properties():
   <global>
     % if workflow.job_xml:
@@ -46,13 +46,5 @@
   % for node in workflow.node_list:
       ${ node.to_xml(mapping) | n }
   % endfor
-  % if workflow.sla_enabled:
-    <sla:info>
-    % for sla in workflow.sla:
-      % if sla['value'] and sla['key'] != 'enabled':
-        <sla:${ sla['key'] }>${ sla['value'] }</sla:${ sla['key'] }>
-      % endif
-    % endfor
-    </sla:info>
-  % endif
+  ${ common.sla(workflow) }
 </workflow-app>

+ 18 - 0
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -259,6 +259,24 @@
 </%def>
 
 
+<%def name="slaForm()">
+  <div data-bind="foreach: sla">
+    <div class="controls">          
+      <div class="span3">
+        <span data-bind="text: getSLAText(key)['niceName']"></span>
+      </div>
+      <div class="span9">
+        <!-- ko if:  key == 'enabled' -->
+        <input type="checkbox" data-bind="checked: value"/>
+        <!-- /ko -->
+        <!-- ko if:  key != 'enabled' -->
+        <input type="text" data-bind="value: value, attr: {placeholder: getSLAText(key)['placeHolder']}" class="span7">
+        <!-- /ko -->
+      </div>
+    </div>
+  </div>           
+</%def>
+
 <%def name="render_constant(label, value)">
   <div class="control-group">
     <label class="control-label">${ label }</label>

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

@@ -1257,7 +1257,7 @@ class TestEditor(OozieMockBase):
     self.wf.node_set.filter(name='action-name-1').delete()
 
 
-  def test_workflow_gen_sla(self):    
+  def test_workflow_gen_workflow_sla(self):    
     xml = self.wf.to_xml({'output': '/path'})
     assert_false('<sla' in xml, xml)
     assert_false('xmlns="uri:oozie:workflow:0.5"' in xml, xml)
@@ -1273,10 +1273,53 @@ class TestEditor(OozieMockBase):
     xml = self.wf.to_xml({'output': '/path'})
     assert_true('xmlns="uri:oozie:workflow:0.5"' in xml, xml)
     assert_true('xmlns:sla="uri:oozie:sla:0.2"' in xml, xml)
-    assert_true("""<sla:info>
-        <sla:nominal-time>now</sla:nominal-time>
-        <sla:should-end>${ 10 * MINUTES}</sla:should-end>
-    </sla:info>""" in xml, xml)
+    assert_true("""<end name="end"/>
+          <sla:info>
+            <sla:nominal-time>now</sla:nominal-time>
+            <sla:should-end>${ 10 * MINUTES}</sla:should-end>
+          </sla:info>
+</workflow-app>""" in xml, xml)
+
+
+  def test_workflow_gen_action_sla(self):    
+    xml = self.wf.to_xml({'output': '/path'})
+    assert_false('<sla' in xml, xml)
+    assert_false('xmlns="uri:oozie:workflow:0.5"' in xml, xml)
+    assert_false('xmlns:sla="uri:oozie:sla:0.2"' in xml, xml)
+
+    self.wf.node_set.filter(name='action-name-1').delete()
+
+    action1 = add_node(self.wf, 'action-name-1', 'hive', [self.wf.start], {
+        u'job_xml': 'my-job.xml',
+        u'files': '["hello.py"]',
+        u'name': 'MyHive',
+        u'job_properties': '[]',
+        u'script_path': 'hello.sql',
+        u'archives': '[]',
+        u'prepares': '[]',
+        u'params': '[{"value":"World!","type":"argument"}]',
+        u'description': ''
+    })
+    Link(parent=action1, child=self.wf.end, name="ok").save()
+
+    xml = self.wf.to_xml()
+
+    sla = action1.sla
+    sla[0]['value'] = True
+    sla[1]['value'] = 'now' # nominal-time
+    sla[3]['value'] = '${ 10 * MINUTES}' # should-end
+    action1.set_sla(sla)
+    action1.save()
+
+    xml = self.wf.to_xml({'output': '/path'})
+    assert_true('xmlns="uri:oozie:workflow:0.5"' in xml, xml)
+    assert_true('xmlns:sla="uri:oozie:sla:0.2"' in xml, xml)
+    assert_true("""<error to="kill"/>
+          <sla:info>
+            <sla:nominal-time>now</sla:nominal-time>
+            <sla:should-end>${ 10 * MINUTES}</sla:should-end>
+          </sla:info>
+    </action>""" in xml, xml)
 
 
   def test_create_coordinator(self):

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

@@ -57,11 +57,15 @@ def format_dict_field_values(dictionary):
 
 def model_to_dict(model):
   from django.db import models
+  from oozie.models import Node
   dictionary = {}
   for field in model._meta.fields:
     try:
       attr = getattr(model, field.name, None)
-      if isinstance(attr, models.Model):
+      if field.name == 'data' and issubclass(type(model), Node):
+        if hasattr(model, 'sla'):
+          dictionary['sla'] = model.sla
+      elif isinstance(attr, models.Model):
         dictionary[field.name] = attr.id
       elif isinstance(attr, datetime):
         dictionary[field.name] = str(attr)

+ 4 - 2
apps/oozie/src/oozie/views/api.py

@@ -205,7 +205,9 @@ def _update_workflow_nodes_json(workflow, json_nodes, id_map, user):
     id_map[str(json_node['id'])] = node.id
 
     for key in json_node:
-      if key not in ('node_ptr', 'child_nodes', 'workflow', 'id', 'sub_workflow'):
+      if key == 'sla':
+        node.set_sla(json_node[key])
+      elif key not in ('node_ptr', 'child_nodes', 'workflow', 'id', 'sub_workflow'):
         setattr(node, key, format_field_value(key, json_node[key]))
 
     node.workflow = workflow
@@ -277,7 +279,7 @@ def workflow_validate_node(request, workflow, node_type):
 def workflow_save(request, workflow):
   if request.method != 'POST':
     raise StructuredException(code="METHOD_NOT_ALLOWED_ERROR", message=_('Must be POST request.'), error_code=405)
-  print request.POST
+
   json_workflow = format_dict_field_values(json.loads(str(request.POST.get('workflow'))))
   json_workflow.setdefault('schema_version', workflow.schema_version)
 

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

@@ -382,7 +382,8 @@ $.extend(DistCPModel.prototype, {
   prepares: '[]',
   job_xml: '',
   params: '[]',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var MapReduceModel = ModelModule($);
@@ -398,7 +399,8 @@ $.extend(MapReduceModel.prototype, {
   jar_path: '',
   prepares: '[]',
   job_xml: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var StreamingModel = ModelModule($);
@@ -413,7 +415,8 @@ $.extend(StreamingModel.prototype, {
   job_properties: '[]',
   mapper: '',
   reducer: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var JavaModel = ModelModule($);
@@ -433,7 +436,8 @@ $.extend(JavaModel.prototype, {
   args: '',
   java_opts: '',
   capture_output: false,
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var PigModel = ModelModule($);
@@ -450,7 +454,8 @@ $.extend(PigModel.prototype, {
   job_xml: '',
   params: '[]',
   script_path: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var HiveModel = ModelModule($);
@@ -467,7 +472,8 @@ $.extend(HiveModel.prototype, {
   job_xml: '',
   params: '[]',
   script_path: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var SqoopModel = ModelModule($);
@@ -484,7 +490,8 @@ $.extend(SqoopModel.prototype, {
   job_xml: '',
   params: '[]',
   script_path: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var ShellModel = ModelModule($);
@@ -502,7 +509,8 @@ $.extend(ShellModel.prototype, {
   params: '[]',
   command: '',
   capture_output: false,
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var SshModel = ModelModule($);
@@ -517,7 +525,8 @@ $.extend(SshModel.prototype, {
   params: '[]',
   command: '',
   capture_output: false,
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var FsModel = ModelModule($);
@@ -532,7 +541,8 @@ $.extend(FsModel.prototype, {
   moves: '[]',
   chmods: '[]',
   touchzs: '[]',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var EmailModel = ModelModule($);
@@ -546,7 +556,8 @@ $.extend(EmailModel.prototype, {
   cc: '',
   subject: '',
   body: '',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var SubWorkflowModel = ModelModule($);
@@ -559,7 +570,8 @@ $.extend(SubWorkflowModel.prototype, {
   sub_workflow: 0,
   propagate_configuration: true,
   job_properties: '[]',
-  child_links: []
+  child_links: [],
+  sla: []
 });
 
 var GenericModel = ModelModule($);

+ 36 - 28
apps/oozie/static/js/workflow.node.js

@@ -95,34 +95,34 @@ var NodeModule = function($, IdGeneratorTable, NodeFields) {
 
     self.edit_template = model.node_type + 'EditTemplate';
     switch(model.node_type) {
-    case 'start':
-    case 'end':
-      self.view_template = ko.observable('disabledNodeTemplate');
-    break;
-
-    case 'kill':
-      self.view_template = ko.observable('emptyTemplate');
-    break;
-
-    case 'fork':
-      self.view_template = ko.observable('forkTemplate');
-    break;
-
-    case 'join':
-      self.view_template = ko.observable('joinTemplate');
-    break;
-
-    case 'decision':
-      self.view_template = ko.observable('decisionTemplate');
-    break;
-
-    case 'decisionend':
-      self.view_template = ko.observable('decisionEndTemplate');
-    break;
-
-    default:
-      self.view_template = ko.observable('nodeTemplate');
-    break;
+      case 'start':
+      case 'end':
+        self.view_template = ko.observable('disabledNodeTemplate');
+      break;
+
+      case 'kill':
+        self.view_template = ko.observable('emptyTemplate');
+      break;
+
+      case 'fork':
+        self.view_template = ko.observable('forkTemplate');
+      break;
+
+      case 'join':
+        self.view_template = ko.observable('joinTemplate');
+      break;
+
+      case 'decision':
+        self.view_template = ko.observable('decisionTemplate');
+      break;
+
+      case 'decisionend':
+        self.view_template = ko.observable('decisionEndTemplate');
+      break;
+
+      default:
+        self.view_template = ko.observable('nodeTemplate');
+      break;
     }
 
     // Data manipulation
@@ -169,6 +169,14 @@ var NodeModule = function($, IdGeneratorTable, NodeFields) {
         self.files.remove(val);
       };
     }
+    
+    if ('sla' in self.model) {
+      var data = self.model['sla'];
+      //alert(data);
+      self.sla = ko.observableArray(self.model['sla']);
+      ///self.sla = ko.observable('aaa');
+      //delete self.model['data'];
+    }
 
     self.initialize.apply(self, arguments);