Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/services/cord/admin.py b/xos/services/cord/admin.py
index 56e46ae..4195aaa 100644
--- a/xos/services/cord/admin.py
+++ b/xos/services/cord/admin.py
@@ -351,6 +351,7 @@
     downlink_speed = forms.CharField(required = False)
     status = forms.ChoiceField(choices=CordSubscriberRoot.status_choices, required=True)
     enable_uverse = forms.BooleanField(required=False)
+    cdn_enable = forms.BooleanField(required=False)
 
     def __init__(self,*args,**kwargs):
         super (CordSubscriberRootForm,self ).__init__(*args,**kwargs)
@@ -361,6 +362,7 @@
             self.fields['downlink_speed'].initial = self.instance.downlink_speed
             self.fields['status'].initial = self.instance.status
             self.fields['enable_uverse'].initial = self.instance.enable_uverse
+            self.fields['cdn_enable'].initial = self.instance.cdn_enable
         if (not self.instance) or (not self.instance.pk):
             # default fields for an 'add' form
             self.fields['kind'].initial = CORD_SUBSCRIBER_KIND
@@ -368,6 +370,7 @@
             self.fields['downlink_speed'].initial = CordSubscriberRoot.get_default_attribute("downlink_speed")
             self.fields['status'].initial = CordSubscriberRoot.get_default_attribute("status")
             self.fields['enable_uverse'].initial = CordSubscriberRoot.get_default_attribute("enable_uverse")
+            self.fields['cdn_enable'].initial = CordSubscriberRoot.get_default_attribute("cdn_enable")
 
     def save(self, commit=True):
         self.instance.url_filter_level = self.cleaned_data.get("url_filter_level")
@@ -375,6 +378,7 @@
         self.instance.downlink_speed = self.cleaned_data.get("downlink_speed")
         self.instance.status = self.cleaned_data.get("status")
         self.instance.enable_uverse = self.cleaned_data.get("enable_uverse")
+        self.instance.cdn_enable = self.cleaned_data.get("cdn_enable")
         return super(CordSubscriberRootForm, self).save(commit=commit)
 
     class Meta:
@@ -384,7 +388,7 @@
     list_display = ('backend_status_icon', 'id',  'name', )
     list_display_links = ('backend_status_icon', 'id', 'name', )
     fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'name', 'service_specific_id', # 'service_specific_attribute',
-                                     'url_filter_level', "uplink_speed", "downlink_speed", "status", "enable_uverse"],
+                                     'url_filter_level', "uplink_speed", "downlink_speed", "status", "enable_uverse", "cdn_enable"],
                           'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', 'service_specific_attribute',)
     form = CordSubscriberRootForm
diff --git a/xos/services/exampleservice/admin.py b/xos/services/exampleservice/admin.py
index d4f6248..f679e4e 100644
--- a/xos/services/exampleservice/admin.py
+++ b/xos/services/exampleservice/admin.py
@@ -9,25 +9,40 @@
 
 from services.exampleservice.models import *
 
+class ExampleServiceForm(forms.ModelForm):
+
+    class Meta:
+        model = ExampleService
+
+    def __init__(self, *args, **kwargs):
+        super(ExampleServiceForm, self).__init__(*args, **kwargs)
+
+        if self.instance:
+            self.fields['service_message'].initial = self.instance.service_message
+
+    def save(self, commit=True):
+        self.instance.service_message = self.cleaned_data.get('service_message')
+        return super(ExampleServiceForm, self).save(commit=commit)
+
 class ExampleServiceAdmin(ReadOnlyAwareAdmin):
 
     model = ExampleService
     verbose_name = SERVICE_NAME_VERBOSE
     verbose_name_plural = SERVICE_NAME_VERBOSE_PLURAL
+    form = ExampleServiceForm
+    inlines = [SliceInline]
 
-    list_display = ('backend_status_icon', 'name', 'enabled',)
-    list_display_links = ('backend_status_icon', 'name', )
+    list_display = ('backend_status_icon', 'name', 'service_message', 'enabled')
+    list_display_links = ('backend_status_icon', 'name', 'service_message' )
 
     fieldsets = [(None, {
-        'fields': ['backend_status_text', 'name', 'enabled', 'versionNumber', 'description',],
+        'fields': ['backend_status_text', 'name', 'enabled', 'versionNumber', 'service_message', 'description',],
         'classes':['suit-tab suit-tab-general',],
         })]
 
     readonly_fields = ('backend_status_text', )
     user_readonly_fields = ['name', 'enabled', 'versionNumber', 'description',]
 
-    inlines = [SliceInline]
-
     extracontext_registered_admins = True
 
     suit_form_tabs = (
diff --git a/xos/services/exampleservice/models.py b/xos/services/exampleservice/models.py
index 6305b54..5d3e258 100644
--- a/xos/services/exampleservice/models.py
+++ b/xos/services/exampleservice/models.py
@@ -16,7 +16,8 @@
     class Meta:
         app_label = SERVICE_NAME
         verbose_name = SERVICE_NAME_VERBOSE
-        proxy = True
+
+    service_message = models.CharField(max_length=254, help_text="Service Message to Display")
 
 class ExampleTenant(TenantWithContainer):
 
diff --git a/xos/synchronizers/base/ansible.py b/xos/synchronizers/base/ansible.py
index d92835a..b6f1ca2 100644
--- a/xos/synchronizers/base/ansible.py
+++ b/xos/synchronizers/base/ansible.py
@@ -69,7 +69,7 @@
             total_unreachable += unreachable
             total_failed += failed
     return {'unreachable':total_unreachable,'failed':total_failed}
-	
+
 
 def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
     return ''.join(random.choice(chars) for _ in range(size))
@@ -86,8 +86,17 @@
 
     objname = opts["ansible_tag"]
 
-    os.system('mkdir -p %s' % os.path.join(sys_dir, path))
-    return (opts, os.path.join(sys_dir,path,objname))
+    pathed_sys_dir = os.path.join(sys_dir, path)
+    if not os.path.isdir(pathed_sys_dir):
+        os.makedirs(pathed_sys_dir)
+
+    # symlink steps/roles into sys/roles so that playbooks can access roles
+    roledir = os.path.join(step_dir,"roles")
+    rolelink = os.path.join(pathed_sys_dir, "roles")
+    if os.path.isdir(roledir) and not os.path.islink(rolelink):
+        os.symlink(roledir,rolelink)
+
+    return (opts, os.path.join(pathed_sys_dir,objname))
 
 def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None):
     template = os_template_env.get_template(name)
diff --git a/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml b/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
index 9da06f5..89e4617 100644
--- a/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
+++ b/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
@@ -6,13 +6,11 @@
   user: ubuntu
   sudo: yes
   gather_facts: no
+  vars:
+    - tenant_message: "{{ tenant_message }}"
+    - service_message: "{{ service_message }}"
 
-  tasks:
-  - name: install apache
-    apt:
-      name=apache2
-      update_cache=yes
-
-  - name: write message
-    shell: echo "{{ tenant_message }}" > /var/www/html/index.html
+  roles:
+    - install_apache
+    - create_index
 
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml b/xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml
new file mode 100644
index 0000000..91c6029
--- /dev/null
+++ b/xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+
+- name: Write index.html file to apache document root
+  template:
+    src=index.html.j2
+    dest=/var/www/html/index.html
+
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2 b/xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2
new file mode 100644
index 0000000..9cec084
--- /dev/null
+++ b/xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2
@@ -0,0 +1,4 @@
+ExampleService
+ Service Message: "{{ service_message }}"
+ Tenant Message: "{{ tenant_message }}"
+
diff --git a/xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml b/xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml
new file mode 100644
index 0000000..d9a155c
--- /dev/null
+++ b/xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+
+- name: Install apache using apt
+  apt:
+    name=apache2
+    update_cache=yes
+
diff --git a/xos/synchronizers/exampleservice/steps/sync_exampletenant.py b/xos/synchronizers/exampleservice/steps/sync_exampletenant.py
index bc4169b..fbde96f 100644
--- a/xos/synchronizers/exampleservice/steps/sync_exampletenant.py
+++ b/xos/synchronizers/exampleservice/steps/sync_exampletenant.py
@@ -33,8 +33,23 @@
 
         return objs
 
+    def get_exampleservice(self, o):
+        if not o.provider_service:
+            return None
+
+        exampleservice = ExampleService.get_service_objects().filter(id=o.provider_service.id)
+
+        if not exampleservice:
+            return None
+
+        return exampleservice[0]
+
     # Gets the attributes that are used by the Ansible template but are not
     # part of the set of default attributes.
     def get_extra_attributes(self, o):
-        return {"tenant_message": o.tenant_message}
+        fields = {}
+        fields['tenant_message'] = o.tenant_message
+        exampleservice = self.get_exampleservice(o)
+        fields['service_message'] = exampleservice.service_message
+        return fields
 
diff --git a/xos/synchronizers/vcpe/steps/sync_vcpetenant.py b/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
index 2e8e0c6..a8c6b4f 100644
--- a/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
+++ b/xos/synchronizers/vcpe/steps/sync_vcpetenant.py
@@ -66,38 +66,52 @@
         vcpe_service = self.get_vcpe_service(o)
 
         dnsdemux_ip = None
-        if vcpe_service.backend_network_label:
-            # Connect to dnsdemux using the network specified by
-            #     vcpe_service.backend_network_label
-            for service in HpcService.objects.all():
-                for slice in service.slices.all():
-                    if "dnsdemux" in slice.name:
-                        for instance in slice.instances.all():
-                            for ns in instance.ports.all():
-                                if ns.ip and ns.network.labels and (vcpe_service.backend_network_label in ns.network.labels):
-                                    dnsdemux_ip = ns.ip
-            if not dnsdemux_ip:
-                logger.info("failed to find a dnsdemux on network %s" % vcpe_service.backend_network_label)
+        cdn_prefixes = []
+
+        cdn_config_fn = "/opt/xos/synchronizers/vcpe/cdn_config"
+        if os.path.exists(cdn_config_fn):
+            # manual CDN configuration
+            #   the first line is the address of dnsredir
+            #   the remaining lines are domain names, one per line
+            lines = file(cdn_config_fn).readlines()
+            if len(lines)>=2:
+                dnsdemux_ip = lines[0].strip()
+                cdn_prefixes = [x.strip() for x in lines[1:] if x.strip()]
         else:
-            # Connect to dnsdemux using the instance's public address
-            for service in HpcService.objects.all():
-                for slice in service.slices.all():
-                    if "dnsdemux" in slice.name:
-                        for instance in slice.instances.all():
-                            if dnsdemux_ip=="none":
-                                try:
-                                    dnsdemux_ip = socket.gethostbyname(instance.node.name)
-                                except:
-                                    pass
-            if not dnsdemux_ip:
-                logger.info("failed to find a dnsdemux with a public address")
+            # automatic CDN configuiration
+            #    it learns everything from CDN objects in XOS
+            #    not tested on pod.
+            if vcpe_service.backend_network_label:
+                # Connect to dnsdemux using the network specified by
+                #     vcpe_service.backend_network_label
+                for service in HpcService.objects.all():
+                    for slice in service.slices.all():
+                        if "dnsdemux" in slice.name:
+                            for instance in slice.instances.all():
+                                for ns in instance.ports.all():
+                                    if ns.ip and ns.network.labels and (vcpe_service.backend_network_label in ns.network.labels):
+                                        dnsdemux_ip = ns.ip
+                if not dnsdemux_ip:
+                    logger.info("failed to find a dnsdemux on network %s" % vcpe_service.backend_network_label)
+            else:
+                # Connect to dnsdemux using the instance's public address
+                for service in HpcService.objects.all():
+                    for slice in service.slices.all():
+                        if "dnsdemux" in slice.name:
+                            for instance in slice.instances.all():
+                                if dnsdemux_ip=="none":
+                                    try:
+                                        dnsdemux_ip = socket.gethostbyname(instance.node.name)
+                                    except:
+                                        pass
+                if not dnsdemux_ip:
+                    logger.info("failed to find a dnsdemux with a public address")
+
+            for prefix in CDNPrefix.objects.all():
+                cdn_prefixes.append(prefix.prefix)
 
         dnsdemux_ip = dnsdemux_ip or "none"
 
-        cdn_prefixes = []
-        for prefix in CDNPrefix.objects.all():
-            cdn_prefixes.append(prefix.prefix)
-
         # Broadbandshield can either be set up internally, using vcpe_service.bbs_slice,
         # or it can be setup externally using vcpe_service.bbs_server.
 
diff --git a/xos/synchronizers/vcpe/templates/dnsmasq_safe_servers.j2 b/xos/synchronizers/vcpe/templates/dnsmasq_safe_servers.j2
index 2f93777..0b3c807 100644
--- a/xos/synchronizers/vcpe/templates/dnsmasq_safe_servers.j2
+++ b/xos/synchronizers/vcpe/templates/dnsmasq_safe_servers.j2
@@ -3,11 +3,13 @@
 no-resolv
 
 {% if cdn_enable %}
+{% if cdn_prefixes %}
 # CDN
 {% for prefix in cdn_prefixes %}
 server=/{{ prefix }}/{{ dnsdemux_ip }}
 {% endfor %}
 {% endif %}
+{% endif %}
 
 # use OpenDNS service
 server=208.67.222.123
diff --git a/xos/synchronizers/vcpe/templates/dnsmasq_servers.j2 b/xos/synchronizers/vcpe/templates/dnsmasq_servers.j2
index 3682cdf..004576f 100644
--- a/xos/synchronizers/vcpe/templates/dnsmasq_servers.j2
+++ b/xos/synchronizers/vcpe/templates/dnsmasq_servers.j2
@@ -3,11 +3,13 @@
 no-resolv
 
 {% if cdn_enable %}
+{% if cdn_prefixes %}
 # CDN
 {% for prefix in cdn_prefixes %}
 server=/{{ prefix }}/{{ dnsdemux_ip }}
 {% endfor %}
 {% endif %}
+{% endif %}
 
 # use google's DNS service
 {% for dns_server in dns_servers %}