Initial prototype of synchronizer

Change-Id: I45de04debd89e7007d7985728d51aa5d03178675
diff --git a/xos/synchronizer/__init__.py b/xos/synchronizer/__init__.py
new file mode 100644
index 0000000..e56cd39
--- /dev/null
+++ b/xos/synchronizer/__init__.py
@@ -0,0 +1,36 @@
+from xos.config import Config
+
+try:
+    observer_disabled = Config().observer_disabled
+except:
+    observer_disabled = False
+
+def EnableObserver(x):
+    """ used for manage.py --noobserver """
+    global observer_disabled
+    observer_disabled = not x
+
+print_once = True
+
+def notify_observer(model=None, delete=False, pk=None, model_dict={}):
+    if (observer_disabled):
+        global print_once
+        if (print_once):
+            print "The observer is disabled"
+            print_once = False
+        return
+
+    try:
+        from .event_manager import EventSender
+        if (model and delete):
+            if hasattr(model,"__name__"):
+                modelName = model.__name__
+            else:
+                modelName = model.__class__.__name__
+            EventSender().fire(delete_flag = delete, model = modelName, pk = pk, model_dict=model_dict)
+        else:
+            EventSender().fire()
+    except Exception,e:
+        print "Exception in Observer. This should not disrupt the front end. %s"%str(e)
+
+
diff --git a/xos/synchronizer/globalxos-synchronizer.py b/xos/synchronizer/globalxos-synchronizer.py
new file mode 100644
index 0000000..90d2c98
--- /dev/null
+++ b/xos/synchronizer/globalxos-synchronizer.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+# Runs the standard XOS synchronizer
+
+import importlib
+import os
+import sys
+
+synchronizer_path = os.path.join(os.path.dirname(
+    os.path.realpath(__file__)), "../../synchronizers/base")
+sys.path.append(synchronizer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
+
diff --git a/xos/synchronizer/globalxos_synchronizer_config b/xos/synchronizer/globalxos_synchronizer_config
new file mode 100644
index 0000000..f06031e
--- /dev/null
+++ b/xos/synchronizer/globalxos_synchronizer_config
@@ -0,0 +1,29 @@
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+nova_enabled=True
+
+[nova]
+ca_ssl_cert=/etc/ssl/certs/ca-certificates.crt
+
+[observer]
+name=globalxos
+dependency_graph=/opt/xos/model-deps
+steps_dir=/opt/xos/synchronizers/globalxos/steps
+sys_dir=/opt/xos/synchronizers/globalxos/sys
+model_policies_dir=/opt/xos/synchronizers/globalxos/model_policies
+logfile=/var/log/xos_backend.log
+save_ansible_output=True
+backoff_disabled=True
+pretend=False
+images_directory=/opt/xos/images
+
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/synchronizer/manifest b/xos/synchronizer/manifest
new file mode 100644
index 0000000..051a93b
--- /dev/null
+++ b/xos/synchronizer/manifest
@@ -0,0 +1,26 @@
+__init__.py
+globalxos-synchronizer.py
+globalxos_synchronizer_config
+manifest
+model_policies/__init__.py
+model_policies/model_policy_Controller.py
+model_policies/model_policy_ControllerSlice.py
+model_policies/model_policy_ControllerUser.py
+model_policies/model_policy_Slice.py
+model_policies/model_policy_SlicePrivilege.py
+model_policies/model_policy_Sliver.py
+model_policies/model_policy_User.py
+run.sh
+steps/__init__.py
+steps/sync_controller_slice_privileges.py
+steps/sync_controller_slice_privileges.yaml
+steps/sync_controller_slices.py
+steps/sync_controller_slices.yaml
+steps/sync_controller_users.py
+steps/sync_controller_users.yaml
+steps/sync_images.py
+steps/sync_roles.py
+steps/sync_sites.py
+templates/slice.yaml.j2
+templates/slice_privileges.yaml.j2
+templates/user.yaml.j2
diff --git a/xos/synchronizer/model_policies/__init__.py b/xos/synchronizer/model_policies/__init__.py
new file mode 100644
index 0000000..36c6e25
--- /dev/null
+++ b/xos/synchronizer/model_policies/__init__.py
@@ -0,0 +1,12 @@
+from .model_policy_Slice import *
+from .model_policy_Instance import *
+from .model_policy_User import *
+from .model_policy_Network import *
+from .model_policy_Site import *
+from .model_policy_SitePrivilege import *
+from .model_policy_SlicePrivilege import *
+from .model_policy_ControllerSlice import *
+from .model_policy_ControllerSite import *
+from .model_policy_ControllerUser import *
+from .model_policy_Controller import *
+from .model_policy_Image import *
diff --git a/xos/synchronizer/model_policies/model_policy_Controller.py b/xos/synchronizer/model_policies/model_policy_Controller.py
new file mode 100644
index 0000000..c62b612
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_Controller.py
@@ -0,0 +1,62 @@
+
+def handle(controller):
+    from core.models import Controller, Site, ControllerSite, Slice, ControllerSlice, User, ControllerUser, ControllerImages, ControllerNetwork, Image, Network
+    from collections import defaultdict
+
+    # relations for all sites
+    ctrls_by_site = defaultdict(list)
+    ctrl_sites = ControllerSite.objects.all()
+    for ctrl_site in ctrl_sites:
+        ctrls_by_site[ctrl_site.site].append(ctrl_site.controller)
+    sites = Site.objects.all()
+    for site in sites:
+        if site not in ctrls_by_site or \
+            controller not in ctrls_by_site[site]:
+            controller_site = ControllerSite(controller=controller, site=site)
+            controller_site.save()
+    # relations for all slices
+    ctrls_by_slice = defaultdict(list)
+    ctrl_slices = ControllerSlice.objects.all()
+    for ctrl_slice in ctrl_slices:
+        ctrls_by_slice[ctrl_slice.slice].append(ctrl_slice.controller)
+    slices = Slice.objects.all()
+    for slice in slices:
+        if slice not in ctrls_by_slice or \
+            controller not in ctrls_by_slice[slice]:
+            controller_slice = ControllerSlice(controller=controller, slice=slice)
+            controller_slice.save()
+    # relations for all users
+    ctrls_by_user = defaultdict(list)
+    ctrl_users = ControllerUser.objects.all()
+    for ctrl_user in ctrl_users:
+        ctrls_by_user[ctrl_user.user].append(ctrl_user.controller)
+    users = User.objects.all()
+    for user in users:
+        if user not in ctrls_by_user or \
+            controller not in ctrls_by_user[user]:
+            controller_user = ControllerUser(controller=controller, user=user)
+            controller_user.save()
+    # relations for all networks
+    ctrls_by_network = defaultdict(list)
+    ctrl_networks = ControllerNetwork.objects.all()
+    for ctrl_network in ctrl_networks:
+        ctrls_by_network[ctrl_network.network].append(ctrl_network.controller)
+    networks = Network.objects.all()
+    for network in networks:
+        if network not in ctrls_by_network or \
+            controller not in ctrls_by_network[network]:
+            controller_network = ControllerNetwork(controller=controller, network=network)
+            if network.subnet and network.subnet.strip():
+                controller_network.subnet = network.subnet.strip()
+            controller_network.save()
+    # relations for all images
+    ctrls_by_image = defaultdict(list)
+    ctrl_images = ControllerImages.objects.all()
+    for ctrl_image in ctrl_images:
+        ctrls_by_image[ctrl_image.image].append(ctrl_image.controller)
+    images = Image.objects.all()
+    for image in images:
+        if image not in ctrls_by_image or \
+            controller not in ctrls_by_image[image]:
+            controller_image = ControllerImages(controller=controller, image=image)
+            controller_image.save()
diff --git a/xos/synchronizer/model_policies/model_policy_ControllerSlice.py b/xos/synchronizer/model_policies/model_policy_ControllerSlice.py
new file mode 100644
index 0000000..bfe7995
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_ControllerSlice.py
@@ -0,0 +1,25 @@
+def handle(controller_slice):
+    from core.models import ControllerSlice, Slice
+   
+    try:
+        my_status_code = int(controller_slice.backend_status[0])
+        try:
+            his_status_code = int(controller_slice.slice.backend_status[0])
+        except:
+            his_status_code = 0
+ 
+        fields = []
+        if (my_status_code not in [0,his_status_code]):
+            controller_slice.slice.backend_status = controller_slice.backend_status
+            fields+=['backend_status']
+
+        if (controller_slice.backend_register != controller_slice.slice.backend_register):
+            controller_slice.slice.backend_register = controller_slice.backend_register
+            fields+=['backend_register']
+
+        controller_slice.slice.save(update_fields = fields)
+
+        
+    except Exception,e:
+        print str(e)	
+        pass
diff --git a/xos/synchronizer/model_policies/model_policy_ControllerUser.py b/xos/synchronizer/model_policies/model_policy_ControllerUser.py
new file mode 100644
index 0000000..b69c9b8
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_ControllerUser.py
@@ -0,0 +1,16 @@
+def handle(controller_user):
+    from core.models import ControllerUser, User
+   
+    try:
+        my_status_code = int(controller_user.backend_status[0])
+        try:
+            his_status_code = int(controller_user.user.backend_status[0])
+        except:
+            his_status_code = 0
+ 
+        if (my_status_code not in [0,his_status_code]):
+            controller_user.user.backend_status = controller_user.backend_status
+            controller_user.user.save(update_fields = ['backend_status'])
+    except Exception,e:
+        print str(e)	
+        pass
diff --git a/xos/synchronizer/model_policies/model_policy_Slice.py b/xos/synchronizer/model_policies/model_policy_Slice.py
new file mode 100644
index 0000000..088d583
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_Slice.py
@@ -0,0 +1,102 @@
+from xos.config import Config
+
+def handle_delete(slice):
+    from core.models import Controller, ControllerSlice, SiteDeployment, Network, NetworkSlice,NetworkTemplate, Slice
+    from collections import defaultdict
+
+    public_nets = []
+    private_net = None
+    networks = Network.objects.filter(owner=slice)
+
+    for n in networks:
+        n.delete()	
+    
+    # Note that sliceprivileges and slicecontrollers are autodeleted, through the dependency graph
+
+def handle(slice):
+    from core.models import Controller, ControllerSlice, SiteDeployment, Network, NetworkSlice,NetworkTemplate, Slice
+    from collections import defaultdict
+
+    # only create nat_net if not using VTN
+    support_nat_net = not getattr(Config(), "networking_use_vtn", False)
+
+    print "MODEL POLICY: slice", slice
+
+    # slice = Slice.get(slice_id)
+
+    controller_slices = ControllerSlice.objects.filter(slice=slice)
+    existing_controllers = [cs.controller for cs in controller_slices] 
+        
+    print "MODEL POLICY: slice existing_controllers=", existing_controllers
+
+    all_controllers = Controller.objects.all()
+    for controller in all_controllers:
+        if controller not in existing_controllers:
+            print "MODEL POLICY: slice adding controller", controller
+            sd = ControllerSlice(slice=slice, controller=controller)
+            sd.save()
+
+    if slice.network in ["host", "bridged"]:
+        # Host and Bridged docker containers need no networks and they will
+        # only get in the way.
+        print "MODEL POLICY: Skipping network creation"
+    elif slice.network in ["noauto"]:
+        # do nothing
+        pass
+    else:
+        # make sure slice has at least 1 public and 1 private networkd
+        public_nets = []
+        private_nets = []
+        networks = Network.objects.filter(owner=slice)
+        for network in networks:
+            if not network.autoconnect:
+                continue
+            if network.template.name == 'Public dedicated IPv4':
+                public_nets.append(network)
+            elif network.template.name == 'Public shared IPv4':
+                public_nets.append(network)
+            elif network.template.name == 'Private':
+                private_nets.append(network)
+        if support_nat_net and (not public_nets):
+            # ensure there is at least one public network, and default it to dedicated
+            nat_net = Network(
+                    name = slice.name+'-nat',
+                        template = NetworkTemplate.objects.get(name='Public shared IPv4'),
+                    owner = slice
+                    )
+            if slice.exposed_ports:
+                nat_net.ports = slice.exposed_ports
+            nat_net.save()
+            public_nets.append(nat_net)
+            print "MODEL POLICY: slice", slice, "made nat-net"
+
+        if not private_nets:
+            private_net = Network(
+                name = slice.name+'-private',
+                template = NetworkTemplate.objects.get(name='Private'),
+                owner = slice
+            )
+            private_net.save()
+            print "MODEL POLICY: slice", slice, "made private net"
+            private_nets = [private_net]
+        # create slice networks
+        public_net_slice = None
+        private_net_slice = None
+        net_slices = NetworkSlice.objects.filter(slice=slice, network__in=private_nets+public_nets)
+        for net_slice in net_slices:
+            if net_slice.network in public_nets:
+                public_net_slice = net_slice
+            elif net_slice.network in private_nets:
+                private_net_slice = net_slice
+        if support_nat_net and (not public_net_slice):
+            public_net_slice = NetworkSlice(slice=slice, network=public_nets[0])
+            public_net_slice.save()
+            print "MODEL POLICY: slice", slice, "made public_net_slice"
+        if not private_net_slice:
+            private_net_slice = NetworkSlice(slice=slice, network=private_nets[0])
+            private_net_slice.save()
+            print "MODEL POLICY: slice", slice, "made private_net_slice"
+
+    print "MODEL POLICY: slice", slice, "DONE"
+
+
diff --git a/xos/synchronizer/model_policies/model_policy_SlicePrivilege.py b/xos/synchronizer/model_policies/model_policy_SlicePrivilege.py
new file mode 100644
index 0000000..bca7f22
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_SlicePrivilege.py
@@ -0,0 +1,15 @@
+def handle(slice_privilege):
+    from core.models import Controller, SlicePrivilege, ControllerSlicePrivilege
+   
+    # slice_privilege = SlicePrivilege.get(slice_privilege_id) 
+    # apply slice privilage at all controllers
+    controller_slice_privileges = ControllerSlicePrivilege.objects.filter(
+        slice_privilege = slice_privilege,
+        )
+    existing_controllers = [sp.controller for sp in controller_slice_privileges]
+    all_controllers = Controller.objects.all()
+    for controller in all_controllers:
+        if controller not in existing_controllers:
+            ctrl_slice_priv = ControllerSlicePrivilege(controller=controller, slice_privilege=slice_privilege)
+            ctrl_slice_priv.save()  
+
diff --git a/xos/synchronizer/model_policies/model_policy_Sliver.py b/xos/synchronizer/model_policies/model_policy_Sliver.py
new file mode 100644
index 0000000..a13428d
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_Sliver.py
@@ -0,0 +1,13 @@
+
+def handle(instance):
+    from core.models import Controller, ControllerSlice, ControllerNetwork, NetworkSlice
+
+    networks = [ns.network for ns in NetworkSlice.objects.filter(slice=instance.slice)]
+    controller_networks = ControllerNetwork.objects.filter(network__in=networks,
+                                                                controller=instance.node.site_deployment.controller)
+
+    for cn in controller_networks:
+        if (cn.lazy_blocked):	
+		cn.lazy_blocked=False
+		cn.backend_register = '{}'
+		cn.save()
diff --git a/xos/synchronizer/model_policies/model_policy_User.py b/xos/synchronizer/model_policies/model_policy_User.py
new file mode 100644
index 0000000..8d14244
--- /dev/null
+++ b/xos/synchronizer/model_policies/model_policy_User.py
@@ -0,0 +1,14 @@
+def handle(user):
+    from core.models import Controller, ControllerSite, ControllerUser, User
+    from collections import defaultdict
+
+    # user = User.get(user_id)
+    
+    controller_users = ControllerUser.objects.filter(user=user)
+    existing_controllers = [cu.controller for cu in controller_users]
+    all_controllers = Controller.objects.all()
+    for controller in all_controllers:
+        if controller not in existing_controllers:
+            ctrl_user = ControllerUser(controller=controller, user=user)
+            ctrl_user.save()  
+
diff --git a/xos/synchronizer/run.sh b/xos/synchronizer/run.sh
new file mode 100644
index 0000000..62bdacc
--- /dev/null
+++ b/xos/synchronizer/run.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+export XOS_DIR=/opt/xos
+python $XOS_DIR/synchronizers/globalxos/globalxos-synchronizer.py -C $XOS_DIR/synchronizers/globalxos/globalxos_synchronizer_config
diff --git a/xos/synchronizer/steps/__init__.py b/xos/synchronizer/steps/__init__.py
new file mode 100644
index 0000000..c70b0c0
--- /dev/null
+++ b/xos/synchronizer/steps/__init__.py
@@ -0,0 +1,6 @@
+#from .sync_controller_sites import SyncControllerSites
+#from .sync_controller_slices import SyncControllerSlices
+#from .sync_controller_users import SyncControllerUsers
+#from .sync_controller_site_privileges import SyncControllerSitePrivileges
+#from .sync_controller_slice_privileges import SyncControllerSlicePrivileges
+#from .sync_controller_networks import SyncControllerNetworks
diff --git a/xos/synchronizer/steps/sync_controller_slice_privileges.py b/xos/synchronizer/steps/sync_controller_slice_privileges.py
new file mode 100644
index 0000000..c6b9cb3
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_slice_privileges.py
@@ -0,0 +1,47 @@
+import os
+import base64
+from collections import defaultdict
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import *
+from core.models.slice import Controller, SlicePrivilege
+from core.models.user import User
+from core.models.controlleruser import ControllerUser, ControllerSlicePrivilege
+from synchronizers.base.ansible import *
+from xos.logger import observer_logger as logger
+import json
+
+class SyncControllerSlicePrivileges(SyncStep):
+    provides=[SlicePrivilege]
+    requested_interval=0
+    observes=ControllerSlicePrivilege
+    playbook = 'sync_controller_slice_privileges.yaml'
+
+    def map_sync_inputs(self, controller_slice_privilege):
+        if not controller_slice_privilege.controller.admin_user:
+            logger.info("controller %r has no admin_user, skipping" % controller_slice_privilege.controller)
+            return
+
+        template = os_template_env.get_template('sync_controller_users.yaml')
+        role = controller_slice_privilege.slice_privilege.role.role
+	# setup user home slice roles at controller
+        if not controller_slice_privilege.slice_privilege.user.site:
+            raise Exception('Sliceless user %s'%controller_slice_privilege.slice_privilege.user.email)
+        user_fields = {
+           'endpoint':controller_slice_privilege.controller.auth_url,
+		   'user_name': controller_slice_privilege.slice_privilege.user.email,
+           'admin_user': controller_slice_privilege.controller.admin_user,
+		   'admin_password': controller_slice_privilege.controller.admin_password,
+           'ansible_tag':'%s@%s@%s'%(controller_slice_privilege.slice_privilege.user.email.replace('@','-at-'),controller_slice_privilege.slice_privilege.slice.name,controller_slice_privilege.controller.name),
+		   'role':role,
+		   'slice_name':controller_slice_privilege.slice_privilege.slice.name}
+        return user_fields
+
+    def map_sync_outputs(self, controller_slice_privilege, res):
+        controller_slice_privilege.role_id = res[0]['id']
+        controller_slice_privilege.save()
+
+    def delete_record(self, controller_slice_privilege):
+        controller_register = json.loads(controller_slice_privilege.controller.backend_register)
+        if (controller_register.get('disabled',False)):
+            raise InnocuousException('Controller %s is disabled'%controller_slice_privilege.controller.name)
diff --git a/xos/synchronizer/steps/sync_controller_slice_privileges.yaml b/xos/synchronizer/steps/sync_controller_slice_privileges.yaml
new file mode 100644
index 0000000..4e0d767
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_slice_privileges.yaml
@@ -0,0 +1,76 @@
+---
+- hosts: 127.0.0.1
+  connection: local
+  # These variables are expanded by the Synchronizer framework
+  # and used to create the TOSCA recipe from a template
+  vars:
+    slice_name: "{{ slice_name }}"
+    user_name: "{{ user_name }}"
+    role: "{{ role }}"
+  tasks:
+  {% if delete -%}
+  {% else -%}
+  - name: Lookup local name of remote site
+    uri:
+      url: "{{ endpoint }}/api/core/sites/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: sites
+
+  - name: Save site name in local_site variable
+    set_fact:
+      local_site: "{{ '{{' }} sites.json[0]['name'] {{ '}}' }}"
+
+  - name: Save local name of slice in local_slice_name variable
+    set_fact:
+      local_slice_name: "{{ '{{' }} local_site {{ '}}' }}_{{ slice_name }}"
+
+  - name: Get list of users
+    uri:
+      url: "{{ endpoint }}/api/core/users/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: users
+
+  - fail: msg="User {{ user_name }} is not present at {{ endpoint }}"
+    when: not ((users.json | selectattr('username', 'equalto', user_name)) | list)
+
+  - name: Get list of slices
+    uri:
+      url: "{{ endpoint }}/api/core/slices/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: slices
+
+  - fail: msg="Slice {{ slice_name }} is not present at {{ endpoint }}"
+    when: not ((slices.json | selectattr('name', 'equalto', local_slice_name)) | list)
+
+  - name: Ensure TOSCA directory exists
+    file:
+      path=/opt/xos/synchronizers/globalxos/tosca/slice_privileges/
+      state=directory
+
+  - name: Create TOSCA recipe from the template
+    template:
+      src=/opt/xos/synchronizers/globalxos/templates/slice_privileges.yaml.j2
+      dest=/opt/xos/synchronizers/globalxos/tosca/slice_privileges/{{ ansible_tag }}.yml
+
+  - name: Create slice privilege for "{{ slice }}"
+    uri:
+      url: "{{ endpoint }}/api/utility/tosca/run/"
+      method: POST
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      body: { "recipe": "{{ '{{' }} lookup('file', '/opt/xos/synchronizers/globalxos/tosca/slice_privileges/{{ ansible_tag }}.yml') {{ '}}' }}" }
+      force_basic_auth: yes
+      body_format: json
+  {% endif %}
diff --git a/xos/synchronizer/steps/sync_controller_slices.py b/xos/synchronizer/steps/sync_controller_slices.py
new file mode 100644
index 0000000..58a0896
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_slices.py
@@ -0,0 +1,76 @@
+import os
+import base64
+from collections import defaultdict
+from netaddr import IPAddress, IPNetwork
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import *
+from core.models import *
+from synchronizers.base.ansible import *
+from xos.logger import observer_logger as logger
+import json
+
+class SyncControllerSlices(SyncStep):
+    provides=[Slice]
+    requested_interval=0
+    observes=ControllerSlice
+    playbook='sync_controller_slices.yaml'
+
+    def map_sync_inputs(self, controller_slice):
+        logger.info("sync'ing slice controller %s" % controller_slice)
+
+        if not controller_slice.controller.admin_user:
+            logger.info("controller %r has no admin_user, skipping" % controller_slice.controller)
+            return
+
+        controller_users = ControllerUser.objects.filter(user=controller_slice.slice.creator,
+                                                             controller=controller_slice.controller)
+        if not controller_users:
+            raise Exception("slice creator %s has not account at controller %s" % (controller_slice.slice.creator, controller_slice.controller.name))
+        else:
+            controller_user = controller_users[0]
+
+        max_instances=int(controller_slice.slice.max_instances)
+        slice_fields = {
+            'endpoint': controller_slice.controller.auth_url,
+            'admin_user': controller_slice.controller.admin_user,
+            'admin_password': controller_slice.controller.admin_password,
+            'slice_name': controller_slice.slice.name,
+            'slice_description': controller_slice.slice.description,
+            'name': controller_user.user.email,
+            'ansible_tag': '%s@%s'%(controller_slice.slice.name,controller_slice.controller.name),
+            'image': controller_slice.slice.default_image.name,
+            'addresses': '10.168.2.0/24',          # FIXME
+            'gateway_ip': '10.168.2.1',            # FIXME
+            'gateway_mac': '02:42:0a:a8:02:01',    # FIXME
+            'max_instances': max_instances
+        }
+
+        return slice_fields
+
+    def map_sync_outputs(self, controller_slice, res):
+        if (not controller_slice.tenant_id):
+            controller_slice.tenant_id = "Not implemented"
+            controller_slice.backend_status = '1 - OK'
+            controller_slice.save()
+
+
+    def map_delete_inputs(self, controller_slice):
+        controller_users = ControllerUser.objects.filter(user=controller_slice.slice.creator,
+                                                              controller=controller_slice.controller)
+        if not controller_users:
+            raise Exception("slice creator %s has not account at controller %s" % (controller_slice.slice.creator, controller_slice.controller.name))
+        else:
+            controller_user = controller_users[0]
+
+        slice_fields = {
+            'endpoint': controller_slice.controller.auth_url,
+            'admin_user': controller_slice.controller.admin_user,
+            'admin_password': controller_slice.controller.admin_password,
+            'slice': controller_slice.slice.name,
+            'slice_description': controller_slice.slice.description,
+            'name': controller_user.user.email,
+            'ansible_tag': '%s@%s'%(controller_slice.slice.name,controller_slice.controller.name),
+            'delete': True
+        }
+	return slice_fields
diff --git a/xos/synchronizer/steps/sync_controller_slices.yaml b/xos/synchronizer/steps/sync_controller_slices.yaml
new file mode 100644
index 0000000..7c85872
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_slices.yaml
@@ -0,0 +1,79 @@
+---
+- hosts: 127.0.0.1
+  connection: local
+  # These variables are expanded by the Synchronizer framework
+  # and used to create the TOSCA recipe from a template
+  vars:
+    slice_name: "{{ slice_name }}"
+    slice_description: "{{ slice_description }}"
+    image: "{{ image }}"
+    addresses: "{{ addresses }}"
+    gateway_ip: "{{ gateway_ip }}"
+    gateway_mac: "{{ gateway_mac }}"
+    max_instances: "{{ max_instances }}"
+  tasks:
+  {% if delete -%}
+  {% else -%}
+  - name: Lookup local name of remote site
+    uri:
+      url: "{{ endpoint }}/api/core/sites/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: sites
+
+  - name: Save site name in local_site variable
+    set_fact:
+      local_site: "{{ '{{' }} sites.json[0]['name'] {{ '}}' }}"
+
+  - name: Get list of images
+    uri:
+      url: "{{ endpoint }}/api/core/images/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: images
+
+  - fail: msg="Image {{ image }} is not present at {{ endpoint }}"
+    when: not ((images.json | selectattr('name', 'equalto', image)) | list)
+
+  - name: Get list of networks
+    uri:
+      url: "{{ endpoint }}/api/core/networks/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: networks
+
+  - fail: msg="Network {{ '{{' }} item {{ '}}' }} is not present at {{ endpoint }}"
+    when: not ((networks.json | selectattr('name', 'equalto', item)) | list)
+    with_items:
+      - "management"
+      - "public"
+
+  - name: Ensure TOSCA directory exists
+    file:
+      path=/opt/xos/synchronizers/globalxos/tosca/slices/
+      state=directory
+
+  - name: Create TOSCA recipe from the template
+    template:
+      src=/opt/xos/synchronizers/globalxos/templates/slice.yaml.j2
+      dest=/opt/xos/synchronizers/globalxos/tosca/slices/{{ ansible_tag }}.yml
+
+  - name: Create slice "{{ slice }}"
+    uri:
+      url: "{{ endpoint }}/api/utility/tosca/run/"
+      method: POST
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      body: { "recipe": "{{ '{{' }} lookup('file', '/opt/xos/synchronizers/globalxos/tosca/slices/{{ ansible_tag }}.yml') {{ '}}' }}" }
+      force_basic_auth: yes
+      body_format: json
+  {% endif %}
diff --git a/xos/synchronizer/steps/sync_controller_users.py b/xos/synchronizer/steps/sync_controller_users.py
new file mode 100644
index 0000000..7382edd
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_users.py
@@ -0,0 +1,63 @@
+import os
+import base64
+from collections import defaultdict
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import *
+from core.models.site import Controller, SiteDeployment, SiteDeployment
+from core.models.user import User
+from core.models.controlleruser import ControllerUser
+from synchronizers.base.ansible import *
+from xos.logger import observer_logger as logger
+import json
+
+class SyncControllerUsers(SyncStep):
+    provides=[User]
+    requested_interval=0
+    observes=ControllerUser
+    playbook='sync_controller_users.yaml'
+
+    def map_sync_inputs(self, controller_user):
+        if not controller_user.controller.admin_user:
+            logger.info("controller %r has no admin_user, skipping" % controller_user.controller)
+            return
+
+        if not controller_user.user.site:
+            raise Exception('Siteless user %s'%controller_user.user.email)
+
+        if controller_user.user.email == controller_user.controller.admin_user:
+            logger.info("user %s is the admin_user at controller %r, skipping" % (controller_user.user.email, controller_user.controller))
+            return
+
+        user_fields = {
+            'endpoint':controller_user.controller.auth_url,
+            'name': controller_user.user.email,
+            'firstname': controller_user.user.firstname,
+            'lastname': controller_user.user.lastname,
+            'phone': controller_user.user.phone,
+            'user_url': controller_user.user.user_url,
+            'public_key': controller_user.user.public_key,
+            'is_active': controller_user.user.is_active,
+            'is_admin': controller_user.user.is_admin,
+            'is_readonly': controller_user.user.is_readonly,
+            'is_appuser': controller_user.user.is_appuser,
+            'password': controller_user.user.remote_password,
+            'admin_user': controller_user.controller.admin_user,
+            'admin_password': controller_user.controller.admin_password,
+            'ansible_tag':'%s@%s'%(controller_user.user.email.replace('@','-at-'),controller_user.controller.name),
+        }
+	return user_fields
+
+    def map_sync_outputs(self, controller_user, res):
+        #controller_user.kuser_id = res[0]['user']['id']
+        controller_user.kuser_id = 'not implemented'
+        controller_user.backend_status = '1 - OK'
+        controller_user.save()
+
+    def delete_record(self, controller_user):
+        """
+        if controller_user.kuser_id:
+            driver = self.driver.admin_driver(controller=controller_user.controller)
+            driver.delete_user(controller_user.kuser_id)
+        """
+        pass
diff --git a/xos/synchronizer/steps/sync_controller_users.yaml b/xos/synchronizer/steps/sync_controller_users.yaml
new file mode 100644
index 0000000..2d4cc00
--- /dev/null
+++ b/xos/synchronizer/steps/sync_controller_users.yaml
@@ -0,0 +1,53 @@
+---
+- hosts: 127.0.0.1
+  connection: local
+  # These variables are expanded by the Synchronizer framework
+  # and used to create the TOSCA recipe from a template
+  vars:
+    name: "{{ name }}"
+    email: "{{ email }}"
+    password: "{{ password }}"
+    firstname: "{{ firstname }}"
+    lastname: "{{ lastname }}"
+    phone: "{{ phone }}"
+    user_url: "{{ user_url }}"
+    public_key: "{{ public_key }}"
+    is_active: "{{ is_active }}"
+    is_admin: "{{ is_admin }}"
+    is_readonly: "{{ is_readonly }}"
+    is_appuser: "{{ is_appuser }}"
+  tasks:
+
+  - name: Lookup local name of remote site
+    uri:
+      url: "{{ endpoint }}/api/core/sites/"
+      method: GET
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      return_content: yes
+      force_basic_auth: yes
+    register: sites
+
+  - name: Save site name in local_site variable
+    set_fact:
+      local_site: "{{ '{{' }} sites.json[0]['name'] {{ '}}' }}"
+
+  - name: Ensure TOSCA directory exists
+    file:
+      path=/opt/xos/synchronizers/globalxos/tosca/users/
+      state=directory
+
+  - name: Create TOSCA recipe from the template
+    template:
+      src=/opt/xos/synchronizers/globalxos/templates/user.yaml.j2
+      dest=/opt/xos/synchronizers/globalxos/tosca/users/{{ ansible_tag }}.yml
+
+  - name: Create user account for "{{ name }}"
+    uri:
+      url: "{{ endpoint }}/api/utility/tosca/run/"
+      method: POST
+      user: "{{ admin_user }}"
+      password: "{{ admin_password }}"
+      body: { "recipe": "{{ '{{' }} lookup('file', '/opt/xos/synchronizers/globalxos/tosca/users/{{ ansible_tag }}.yml') {{ '}}' }}" }
+      force_basic_auth: yes
+      body_format: json
diff --git a/xos/synchronizer/steps/sync_images.py b/xos/synchronizer/steps/sync_images.py
new file mode 100644
index 0000000..5b8e227
--- /dev/null
+++ b/xos/synchronizer/steps/sync_images.py
@@ -0,0 +1,18 @@
+import os
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from core.models.image import Image
+from xos.logger import observer_logger as logger
+from synchronizers.base.syncstep import *
+
+class SyncImages(SyncStep):
+    provides=[Image]
+    requested_interval=0
+    observes=Image
+
+    # We're not sync'ing sites with L-XOS
+    # Just mark it as sync'ed
+    def sync_record(self, image):
+        image.backend_status = "0 - not sync'ed by globalxos"
+        image.save()
diff --git a/xos/synchronizer/steps/sync_roles.py b/xos/synchronizer/steps/sync_roles.py
new file mode 100644
index 0000000..e19532e
--- /dev/null
+++ b/xos/synchronizer/steps/sync_roles.py
@@ -0,0 +1,26 @@
+import os
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from core.models.role import Role
+from core.models.site import SiteRole, Controller, ControllerRole
+from core.models.slice import SliceRole
+from xos.logger import observer_logger as logger
+from synchronizers.base.ansible import *
+from synchronizers.base.syncstep import *
+
+class SyncRoles(SyncStep):
+    provides=[Role]
+    requested_interval=0
+    observes=[SiteRole,SliceRole,ControllerRole]
+
+    def sync_record(self, role):
+        if not role.enacted:
+            controllers = Controller.objects.all()
+            """
+       	    for controller in controllers:
+                driver = self.driver.admin_driver(controller=controller)
+                driver.create_role(role.role)
+            """
+            role.save()
+    
diff --git a/xos/synchronizer/steps/sync_sites.py b/xos/synchronizer/steps/sync_sites.py
new file mode 100644
index 0000000..82e42d2
--- /dev/null
+++ b/xos/synchronizer/steps/sync_sites.py
@@ -0,0 +1,19 @@
+import os
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from core.models.site import Site
+from xos.logger import observer_logger as logger
+from synchronizers.base.ansible import *
+from synchronizers.base.syncstep import *
+
+class SyncSites(SyncStep):
+    provides=[Site]
+    requested_interval=0
+    observes=[Site]
+
+    # We're not sync'ing sites with L-XOS
+    # Just mark it as sync'ed
+    def sync_record(self, site):
+        site.backend_status = "0 - not sync'ed by globalxos"
+        site.save()
diff --git a/xos/synchronizer/templates/slice.yaml.j2 b/xos/synchronizer/templates/slice.yaml.j2
new file mode 100644
index 0000000..6f22b48
--- /dev/null
+++ b/xos/synchronizer/templates/slice.yaml.j2
@@ -0,0 +1,48 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup a generic slice on the pod
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+
+    management:
+      type: tosca.nodes.network.Network.XOS
+      properties:
+          no-create: true
+          no-delete: true
+          no-update: true
+
+    public:
+      type: tosca.nodes.network.Network.XOS
+      properties:
+          no-create: true
+          no-delete: true
+          no-update: true
+
+    {{ local_site }}:
+      type: tosca.nodes.Site
+
+    {{ image }}:
+      type: tosca.nodes.Image
+
+    {{ local_site }}_{{ slice_name }}:
+      description: {{ slice_description }}
+      type: tosca.nodes.Slice
+      properties:
+          network: noauto
+      requirements:
+          - site:
+              node: {{ local_site }}
+              relationship: tosca.relationships.MemberOfSite
+          - management:
+              node: management
+              relationship: tosca.relationships.ConnectsToNetwork
+          - public:
+              node: public
+              relationship: tosca.relationships.ConnectsToNetwork
+          - image:
+              node: {{ image }}
+              relationship: tosca.relationships.DefaultImage
diff --git a/xos/synchronizer/templates/slice_privileges.yaml.j2 b/xos/synchronizer/templates/slice_privileges.yaml.j2
new file mode 100644
index 0000000..ac3a573
--- /dev/null
+++ b/xos/synchronizer/templates/slice_privileges.yaml.j2
@@ -0,0 +1,29 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup a slice privilege on the pod
+{% set relationship = {"admin": "tosca.relationships.AdminPrivilege", "access": "tosca.relationships.AccessPrivilege"}[role] | default("ERROR") -%}
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+
+    {{ local_site }}:
+      type: tosca.nodes.Site
+
+    {{ user_name }}:
+      type: tosca.nodes.User
+
+    {{ local_site }}_{{ slice_name }}:
+      type: tosca.nodes.Slice
+      properties:
+          no-create: true
+          no-delete: true
+      requirements:
+          - privilege:
+              node: {{ user_name }}
+              relationship: {{ relationship }}
+          - site:
+              node: {{ local_site }}
+              relationship: tosca.relationships.MemberOfSite
diff --git a/xos/synchronizer/templates/user.yaml.j2 b/xos/synchronizer/templates/user.yaml.j2
new file mode 100644
index 0000000..956051d
--- /dev/null
+++ b/xos/synchronizer/templates/user.yaml.j2
@@ -0,0 +1,29 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Add a user
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    {{ local_site }}:
+      type: tosca.nodes.Site
+
+    {{ name }}:
+      type: tosca.nodes.User
+      properties:
+          password: "{{ password }}"
+          firstname: "{{ firstname }}"
+          lastname: "{{ lastname }}"
+          phone: "{{ phone }}"
+          user_url: "{{ user_url }}"
+          public_key: "{{ public_key }}"
+          is_active: "{{ is_active }}"
+          is_admin: "{{ is_admin }}"
+          is_readonly: "{{ is_readonly }}"
+          is_appuser: "{{ is_appuser }}"
+      requirements:
+          - site:
+              node: {{ local_site }}
+              relationship: tosca.relationships.MemberOfSite