Merge branch 'master' of https://github.com/open-cloud/xos
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index b5ba737..1a1b6ef 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -554,6 +554,22 @@
         self.set_attribute("instance_id", value)
 
     @property
+    def external_hostname(self):
+        return self.get_attribute("external_hostname", "")
+
+    @external_hostname.setter
+    def external_hostname(self, value):
+        self.set_attribute("external_hostname", value)
+
+    @property
+    def external_container(self):
+        return self.get_attribute("external_container", "")
+
+    @external_container.setter
+    def external_container(self, value):
+        self.set_attribute("external_container", value)
+
+    @property
     def creator(self):
         from core.models import User
         if getattr(self, "cached_creator", None):
diff --git a/xos/services/onos/admin.py b/xos/services/onos/admin.py
index 3f9f96c..fb0f1d7 100644
--- a/xos/services/onos/admin.py
+++ b/xos/services/onos/admin.py
@@ -19,16 +19,28 @@
 from django.contrib.admin.utils import quote
 
 class ONOSServiceForm(forms.ModelForm):
-    use_external_host = forms.CharField(required=False)
+    rest_hostname = forms.CharField(required=False)
+    rest_port = forms.CharField(required=False)
+    no_container = forms.BooleanField(required=False)
+#    external_hostname = forms.CharField(required=False)
+#    external_container = forms.CharField(required=False)
 
     def __init__(self,*args,**kwargs):
         super (ONOSServiceForm,self ).__init__(*args,**kwargs)
         if self.instance:
             # fields for the attributes
-            self.fields['use_external_host'].initial = self.instance.use_external_host
+            self.fields['rest_hostname'].initial = self.instance.rest_hostname
+            self.fields['rest_port'].initial = self.instance.rest_port
+            self.fields['no_container'].initial = self.instance.no_container
+#            self.fields['external_hostname'].initial = self.instance.external_hostname
+#            self.fields['external_container'].initial = self.instance.external_hostname
 
     def save(self, commit=True):
-        self.instance.use_external_host = self.cleaned_data.get("use_external_host")
+        self.instance.rest_hostname = self.cleaned_data.get("rest_hostname")
+        self.instance.rest_port = self.cleaned_data.get("rest_port")
+        self.instance.no_container = self.cleaned_data.get("no_container")
+#        self.instance.external_hostname = self.cleaned_data.get("external_hostname")
+#        self.instance.external_container = self.cleaned_data.get("external_container")
         return super(ONOSServiceForm, self).save(commit=commit)
 
     class Meta:
@@ -40,7 +52,7 @@
     verbose_name_plural = "ONOS Services"
     list_display = ("backend_status_icon", "name", "enabled")
     list_display_links = ('backend_status_icon', 'name', )
-    fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description',"view_url","icon_url", "use_external_host" ], 'classes':['suit-tab suit-tab-general']})]
+    fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description',"view_url","icon_url", "rest_hostname", "rest_port", "no_container" ], 'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', )
     inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
     form = ONOSServiceForm
diff --git a/xos/services/onos/models.py b/xos/services/onos/models.py
index 1e869d1..4be2c1b 100644
--- a/xos/services/onos/models.py
+++ b/xos/services/onos/models.py
@@ -21,15 +21,34 @@
         verbose_name = "ONOS Service"
         proxy = True
 
-    default_attributes = {"use_external_host": ""}
+    default_attributes = {"rest_hostname": "",
+                          "rest_port": "8181",
+                          "no_container": False}
 
     @property
-    def use_external_host(self):
-        return self.get_attribute("use_external_host", self.default_attributes["use_external_host"])
+    def rest_hostname(self):
+        return self.get_attribute("rest_hostname", self.default_attributes["rest_hostname"])
 
-    @use_external_host.setter
-    def use_external_host(self, value):
-        self.set_attribute("use_external_host", value)
+    @rest_hostname.setter
+    def rest_hostname(self, value):
+        self.set_attribute("rest_hostname", value)
+
+    @property
+    def rest_port(self):
+        return self.get_attribute("rest_port", self.default_attributes["rest_port"])
+
+    @rest_port.setter
+    def rest_port(self, value):
+        self.set_attribute("rest_port", value)
+
+    @property
+    def no_container(self):
+        return self.get_attribute("no_container", self.default_attributes["no_container"])
+
+    @no_container.setter
+    def no_container(self, value):
+        self.set_attribute("no_container", value)
+
 
 class ONOSApp(Tenant):   # aka 'ONOSTenant'
     class Meta:
@@ -93,19 +112,6 @@
     def install_dependencies(self, value):
         self.set_attribute("install_dependencies", value)
 
-    #@property
-    #def instance(self):
-    #    instance_id = self.get_attribute("instance_id", self.default_attributes["instance_id"])
-    #    if instance_id:
-    #        instances = Instance.objects.filter(id=instance_id)
-    #        if instances:
-    #            return instances[0]
-    #    return None
-
-    #@instance.setter
-    #def instance(self, value):
-    #    self.set_attribute("instance_id", value.id)
-
     def save(self, *args, **kwargs):
         if not self.creator:
             if not getattr(self, "caller", None):
diff --git a/xos/synchronizers/base/SyncInstanceUsingAnsible.py b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
index 335932f..95c7b22 100644
--- a/xos/synchronizers/base/SyncInstanceUsingAnsible.py
+++ b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
@@ -26,6 +26,12 @@
     def __init__(self, **args):
         SyncStep.__init__(self, **args)
 
+    def skip_ansible_fields(self, o):
+        # Return True if the instance processing and get_ansible_fields stuff
+        # should be skipped. This hook is primarily for the OnosApp
+        # sync step, so it can do its external REST API sync thing.
+        return False
+
     def defer_sync(self, o, reason):
         logger.info("defer object %s due to %s" % (str(o), reason))
         raise Exception("defer object %s due to %s" % (str(o), reason))
@@ -44,6 +50,14 @@
 
         return o.instance
 
+    def get_external_sync(self, o):
+        hostname = getattr(o, "external_hostname", None)
+        container = getattr(o, "external_container", None)
+        if hostname and container:
+            return (hostname, container)
+        else:
+            return None
+
     def run_playbook(self, o, fields, template_name=None):
         if not template_name:
             template_name = self.template_name
@@ -142,30 +156,43 @@
 
         self.prepare_record(o)
 
-        instance = self.get_instance(o)
-
-        if isinstance(instance, basestring):
-            # sync to some external host
-
-            # XXX - this probably needs more work...
-
-            fields = { "hostname": instance,
-                       "instance_id": "ubuntu",     # this is the username to log into
-                       "private_key": service.key,
-                     }
+        if self.skip_ansible_fields(o):
+            fields = {}
         else:
-            # sync to an XOS instance
-            if not instance:
-                self.defer_sync(o, "waiting on instance")
-                return
+            if self.get_external_sync(o):
+                # sync to some external host
 
-            if not instance.instance_name:
-                self.defer_sync(o, "waiting on instance.instance_name")
-                return
+                # UNTESTED
 
-            fields = self.get_ansible_fields(instance)
+                (hostname, container_name) = self.get_external_sync(o)
+                fields = { "hostname": hostname,
+                           "baremetal_ssh": True,
+                           "instance_name": "rootcontext",
+                           "username": "root",
+                           "container_name": container_name
+                         }
+                key_name = self.get_node_key(node)
+                if not os.path.exists(key_name):
+                    raise Exception("Node key %s does not exist" % key_name)
 
-            fields["ansible_tag"] =  o.__class__.__name__ + "_" + str(o.id)
+                key = file(key_name).read()
+
+                fields["private_key"] = key
+                # TO DO: Ceilometer stuff
+            else:
+                instance = self.get_instance(o)
+                # sync to an XOS instance
+                if not instance:
+                    self.defer_sync(o, "waiting on instance")
+                    return
+
+                if not instance.instance_name:
+                    self.defer_sync(o, "waiting on instance.instance_name")
+                    return
+
+                fields = self.get_ansible_fields(instance)
+
+        fields["ansible_tag"] =  o.__class__.__name__ + "_" + str(o.id)
 
         # If 'o' defines a 'sync_attributes' list, then we'll copy those
         # attributes into the Ansible recipe's field list automatically.
diff --git a/xos/synchronizers/onos/steps/sync_onosapp.py b/xos/synchronizers/onos/steps/sync_onosapp.py
index 8942e59..c55a0d6 100644
--- a/xos/synchronizers/onos/steps/sync_onosapp.py
+++ b/xos/synchronizers/onos/steps/sync_onosapp.py
@@ -9,6 +9,7 @@
 import json
 from django.db.models import F, Q
 from xos.config import Config
+from synchronizers.base.ansible import run_template
 from synchronizers.base.syncstep import SyncStep
 from synchronizers.base.ansible import run_template_ssh
 from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
@@ -46,8 +47,8 @@
 
         serv = self.get_onos_service(o)
 
-        if serv.use_external_host:
-            return serv.use_external_host
+        if serv.no_container:
+            raise Exception("get_instance() was called on a service that was marked no_container")
 
         if serv.slices.exists():
             slice = serv.slices.all()[0]
@@ -66,6 +67,12 @@
 
         return onoses[0]
 
+    def is_no_container(self, o):
+        return self.get_onos_service(o).no_container
+
+    def skip_ansible_fields(self, o):
+        return self.is_no_container(o)
+
     def get_files_dir(self, o):
         if not hasattr(Config(), "observer_steps_dir"):
             # make steps_dir mandatory; there's no valid reason for it to not
@@ -126,7 +133,7 @@
         ordered_attrs = attrs.keys()
 
         o.early_rest_configs=[]
-        if ("cordvtn" in o.dependencies):
+        if ("cordvtn" in o.dependencies) and (not self.is_no_container(o)):
             # For VTN, since it's running in a docker host container, we need
             # to make sure it configures the cluster using the right ip addresses.
             # NOTE: rest_onos/v1/cluster/configuration/ will reboot the cluster and
@@ -172,22 +179,37 @@
     def prepare_record(self, o):
         self.write_configs(o)
 
-    def get_extra_attributes(self, o):
-        instance = self.get_instance(o)
+    def get_extra_attributes_common(self, o):
+        fields = {}
 
-        fields={}
+        # These are attributes that are not dependent on Instance. For example,
+        # REST API stuff.
+
+        onos = self.get_onos_service(o)
+
         fields["files_dir"] = o.files_dir
         fields["appname"] = o.name
-        fields["nat_ip"] = instance.get_ssh_ip()
-        fields["config_fns"] = o.config_fns
         fields["rest_configs"] = o.rest_configs
-        fields["early_rest_configs"] = o.early_rest_configs
-        fields["component_configs"] = o.component_configs
+        fields["rest_hostname"] = onos.rest_hostname
+        fields["rest_port"] = onos.rest_port
+
         if o.dependencies:
             fields["dependencies"] = [x.strip() for x in o.dependencies.split(",")]
         else:
             fields["dependencies"] = []
 
+        return fields
+
+    def get_extra_attributes_full(self, o):
+        instance = self.get_instance(o)
+
+        fields = self.get_extra_attributes_common(o)
+
+        fields["nat_ip"] = instance.get_ssh_ip()
+        fields["config_fns"] = o.config_fns
+        fields["early_rest_configs"] = o.early_rest_configs
+        fields["component_configs"] = o.component_configs
+
         if o.install_dependencies:
             fields["install_dependencies"] = [x.strip() for x in o.install_dependencies.split(",")]
         else:
@@ -199,12 +221,23 @@
             fields["ONOS_container"] = "ONOS"
         return fields
 
+    def get_extra_attributes(self, o):
+        if self.is_no_container(o):
+            return self.get_extra_attributes_common(o)
+        else:
+            return self.get_extra_attributes_full(o)
+
     def sync_fields(self, o, fields):
         # the super causes the playbook to be run
         super(SyncONOSApp, self).sync_fields(o, fields)
 
     def run_playbook(self, o, fields):
-        super(SyncONOSApp, self).run_playbook(o, fields)
+        if self.is_no_container(o):
+            # There is no machine to SSH to, so use the synchronizer's
+            # run_template method directly.
+            run_template("sync_onosapp_nocontainer.yaml", fields)
+        else:
+            super(SyncONOSApp, self).run_playbook(o, fields)
 
     def delete_record(self, m):
         pass
diff --git a/xos/synchronizers/onos/steps/sync_onosservice.py b/xos/synchronizers/onos/steps/sync_onosservice.py
index e70be0c..2e9bb38 100644
--- a/xos/synchronizers/onos/steps/sync_onosservice.py
+++ b/xos/synchronizers/onos/steps/sync_onosservice.py
@@ -43,9 +43,6 @@
 
         serv = o
 
-        if serv.use_external_host:
-            return serv.use_external_host
-
         if serv.slices.exists():
             slice = serv.slices.all()[0]
             if slice.instances.exists():
@@ -61,6 +58,13 @@
         fields["ONOS_container"] = "ONOS"
         return fields
 
+    def sync_record(self, o):
+        if o.no_container:
+            logger.info("no work to do for onos service, because o.no_container is set")
+            o.save()
+        else:
+            super(SyncONOSService, self).sync_record(o)
+
     def sync_fields(self, o, fields):
         # the super causes the playbook to be run
         super(SyncONOSService, self).sync_fields(o, fields)