Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/tools/apigen/fields.template.txt b/xos/tools/apigen/fields.template.txt
new file mode 100644
index 0000000..ad5a0b4
--- /dev/null
+++ b/xos/tools/apigen/fields.template.txt
@@ -0,0 +1,6 @@
+{% for object in generator.all %}
+Object {{ object }}:
+Fields:
+{% for field in object.fields %}{{ field.name }}:{{ field.type }}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/modelgen b/xos/tools/apigen/modelgen
index 414540d..068b7cb 100644
--- a/xos/tools/apigen/modelgen
+++ b/xos/tools/apigen/modelgen
@@ -11,10 +11,14 @@
 
 # Django set up
 
+import django
 sys.path.append('.')
+sys.path.append('/opt/xos')
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
 from django.db.models.fields.related import ForeignKey, ManyToManyField
 
+django.setup()
+
 options = None
 
 
@@ -30,7 +34,9 @@
 
 def enum_classes(apps):
     global app_map
+    global class_map
     app_map = {}
+    class_map = {}
     model_classes = []
     for app in apps:
             orig_app=app
@@ -49,6 +55,16 @@
                     if type(c)==type(PlCoreBase) and c.__name__ not in options.blacklist:
                             model_classes.append(c)
                             app_map[c.__name__]=orig_app
+                            c.class_name = c.__name__
+                            file_name = c.__module__.rsplit('.',1)[1]
+                            try:
+                                if (file_name not in class_map[orig_app]):
+                                    class_map[orig_app].append({file_name:[c]})
+                                else:
+                                    class_map[orig_app][file_name].append(c)
+
+                            except KeyError:
+                                class_map[orig_app] = [{file_name:[c]}]
 
 
     return model_classes
@@ -98,6 +114,9 @@
 		return name
 		
 class Generator(dict):
+        def __init__(self):
+            self.apps = {}
+
 	def all(self):
 		return self.values()
 
@@ -117,7 +136,19 @@
                     obj.app = app_map[o.__name__]
                 except KeyError:
                     print "KeyError: %r"%o.__name__
-                    pdb.set_trace()
+                obj.class_name = o.class_name
+
+                file_name = o.__module__.rsplit('.',1)[1]
+
+                try:
+                    if (file_name not in self.apps[obj.app]):
+                        self.apps[obj.app][file_name]=[obj]
+                    else:
+                        self.apps[obj.app][file_name].append(obj)
+
+                except KeyError:
+                    self.apps[obj.app] = {file_name:[obj]}
+
 		self[str(obj).lower()]=obj
 
 	def compute_links(self):
@@ -150,7 +181,8 @@
                                         pass
 				else:
                                         f.type = f.__class__.__name__
-
+                                        if (type(f)==ForeignKey):
+                                            f.related.model.class_name = f.related.model.__name__
                                         if (f.name not in base_props):
                                             obj.fields.append(f) 
                                         obj.props.append(f.name)
@@ -189,6 +221,9 @@
         global options
         parser = OptionParser(usage="modelgen [options] <template_fn>", )
 
+        parser.add_option("-d", "--dict", dest="dict",
+             help="dictionary to replace text in output", metavar="DICT", default=[], action="append")
+
         parser.add_option("-a", "--app", dest="apps",
              help="list of applications to parse", metavar="APP", default=[], action="append")
         parser.add_option("-b", "--blacklist", dest="blacklist",
@@ -228,7 +263,31 @@
 	template_contents = open(template_name).read()
 	template = Template(template_contents)
 	context = Context({'generator':generator})
-	print template.render(context)
+	rendered = template.render(context)
+        lines = rendered.splitlines()
+        current_buffer = []
+        for l in lines:
+            if (l.startswith('+++')):
+                filename = l[4:]
+                fil = open(filename,'w')
+                buf = '\n'.join(current_buffer)
+
+                obuf = buf
+                for d in options.dict:
+                    df = open(d).read()
+                    d = json.loads(df)
+
+                    pattern = re.compile(r'\b(' + '|'.join(d.keys()) + r')\b')
+                    obuf = pattern.sub(lambda x: d[x.group()], buf)
+                fil.write(obuf)
+                fil.close()
+
+                print 'Written to file %s'%filename
+                current_buffer = []
+            else:
+                current_buffer.append(l)
+        if (current_buffer):
+            print '\n'.join(current_buffer)
 
 
 if (__name__=='__main__'):
diff --git a/xos/tools/apigen/modelyaml.fields.template.txt b/xos/tools/apigen/modelyaml.fields.template.txt
new file mode 100644
index 0000000..69129ed
--- /dev/null
+++ b/xos/tools/apigen/modelyaml.fields.template.txt
@@ -0,0 +1,12 @@
+{% for object in generator.all %}
+{{ object.camel }}:
+fields:
+  {% for f in object.fields %}
+  - name: {{ f.name }}
+    type: {{ f.type }}
+    {% if f.help_text %}
+    help_text: {{ f.help_text }}
+    {% endif %}
+    null: {{ f.null }}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/synchronizer.template.txt b/xos/tools/apigen/synchronizer.template.txt
new file mode 100644
index 0000000..c784e21
--- /dev/null
+++ b/xos/tools/apigen/synchronizer.template.txt
@@ -0,0 +1,71 @@
+{% for app,files in generator.apps.items %}
+{% for file,models in files.items %}
+{% for m in models %}
+# This file implements the synchronization of the {{ m.class_name }} model
+# TODO (see below):
+
+import os
+import base64
+import socket
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from {{app}}.models.{{ file }} import {{ m.class_name.title }}
+from synchronizers.base.ansible import *
+from synchronizers.base.syncstep import *
+from xos.logger import observer_logger as logger
+
+class Sync{{m.camel.title}}(SyncStep):
+    # The model synchronized
+    provides=[{{m.class_name.title}}]
+    # How often do you want this to run. 0 means immediately following changes to the model
+    requested_interval=0
+    # The model in which changes trigger this module
+    observes={{m.class_name}}
+    # The Ansible recipe that does the bulk of the synchronization (hopefully)
+    playbook='sync_{{m.class_name}}.yaml'
+
+    # 1. Populate a data structure to pass into your ansible recipe, based on the data model. 
+    def map_sync_inputs(self, {{ m.class_name }}): 
+        fields = {
+            {% for f in m.fields %}'{{f.name}}': {{f.name}},
+            {% endfor %}
+        }
+        return fields
+        
+    # 2. Store the result of the operation in the model
+    def map_sync_outputs(self, {{ m.class_name }}, res):
+        // Store the output of res in the {{m.class_name}} object 
+        return
+    
+    # 3. Populate the data structure that identifies objects to delete
+    def map_delete_inputs(self, {{ m.class_name }}, res):
+        fields = {
+            {% for f in m.fields %}'{{f.name}}': {{f.name}},
+            {% endfor %}
+               'delete':True
+        }
+        return fields
+
+
++++ Sync{{ m.class_name }}.py
+---
+- hosts: 127.0.0.1
+  connection: local
+  tasks:
+  - task_1:
+      {% for f in m.fields %}
+      {{f.name}}: {% templatetag openbrace %}{% templatetag openbrace %}{{f.name}}{% templatetag closebrace %}{% templatetag closebrace %}
+      {% endfor %}
+      {% verbatim %}
+      {% if delete %}
+      state: absent
+      {% else %}
+      state: present
+      {% endif %}
+      {% endif %}
+      {% endverbatim %}
++++ Sync{{ m.class_name }}.yaml
+{% endfor %}
+{% endfor %}
+{% endfor %}
diff --git a/xos/tools/apigen/type_map b/xos/tools/apigen/type_map
new file mode 100644
index 0000000..ac4a8a8
--- /dev/null
+++ b/xos/tools/apigen/type_map
@@ -0,0 +1,10 @@
+{
+    "AutoField":"int64",
+    "ForeignKey":"InstanceIdentifier",
+    "CharField":"string",
+    "TextField":"string",
+    "FloatField":"float64",
+    "BooleanField":"boolean",
+    "StrippedCharField":"string",
+    "DateTimeField":"date-and-time"
+}
diff --git a/xos/tools/apigen/yang.template.txt b/xos/tools/apigen/yang.template.txt
new file mode 100644
index 0000000..ad4b1b1
--- /dev/null
+++ b/xos/tools/apigen/yang.template.txt
@@ -0,0 +1,21 @@
+{% for app,files in generator.apps.items %}
+{% for file,m in files.items %}
+module xos-{{ app }}-{{ file }} {
+    namespace "urn:xos:{{app}}.{{ file }}";
+    prefix xos-cs;
+
+    import complex-types {prefix ct;}
+    revision 2016-2-24 {
+        description "Initial";
+    }   
+
+    complex-type {{ m.class_name }} {
+      {% for f in m.fields %}
+
+      leaf {{ f.name }} { type {{ f.type }}{% ifequal f.type "ForeignKey" %} { ct:instance-type {{f.related.model.class_name}};{% if f.null%}{%else%}require-instance true{% endif %}{% endifequal %};{% if f.max_length %} { length {{ f.max_length }};{% endif %}{% if None %}default "{{ f.default }}";{% endif %}}
+      {% endfor %}
+    }
+}
++++ {{ app }}-{{ file}}.yang
+{% endfor %}
+{% endfor %}
diff --git a/xos/tosca/README.md b/xos/tosca/README.md
new file mode 100644
index 0000000..98f0aaf
--- /dev/null
+++ b/xos/tosca/README.md
@@ -0,0 +1,13 @@
+## TOSCA Interface Definition
+
+This directory implements a TOSCA interface for XOS,
+which can be extended to include specifications for
+service models added to XOS. The directory is organized
+as follows:
+
+ * custom_types -- Defines schema for XOS-specific models.
+   * `.m4` files are source.
+   * `.yaml` files are generated.
+ * definitions -- Defines schema for TOSCA's base models.
+ * resources -- Translates TOSCA specification to Django API.
+ * sample -- Example TOSCA models.