Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/configurations/cord-pod/Makefile b/xos/configurations/cord-pod/Makefile
new file mode 100644
index 0000000..9930c52
--- /dev/null
+++ b/xos/configurations/cord-pod/Makefile
@@ -0,0 +1,22 @@
+.PHONY: xos
+xos: nodes.yaml images.yaml vtn_network_cfg_json
+	sudo docker-compose up -d
+	../common/wait_for_xos_port.sh 80
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/setup.yaml
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/images.yaml
+	sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/vtn-external.yaml
+
+nodes.yaml:
+	export SETUPDIR=.; bash ../common/make-nodes-yaml.sh
+
+images.yaml:
+	export SETUPDIR=.; bash ../common/make-images-yaml.sh
+
+vtn_network_cfg_json:
+	export SETUPDIR=.; bash ./make-vtn-networkconfig-json.sh
+
+.PHONY: local_containers
+local_containers:
+	cd ../../../containers/xos; make devel
+	cd ../../../containers/synchronizer; make
diff --git a/xos/configurations/cord-pod/NOTES.txt b/xos/configurations/cord-pod/NOTES.txt
new file mode 100644
index 0000000..d0ecf36
--- /dev/null
+++ b/xos/configurations/cord-pod/NOTES.txt
@@ -0,0 +1,38 @@
+Notes on setup
+
+Requirements:
+* admin-openrc.sh: Admin credentials for your OpenStack cloud
+* id_rsa[.pub]: Keypair for use by the various services
+* node_key: Private key that allows root login to the compute nodes
+
+Steps for bringing up the POD:
+
+OpenStack
+* Configure management net
+  - mgmtbr on head nodes
+  - dnsmasq on head1 using cord config file
+* Install OpenStack using the openstack-cluster-install repo
+  TO ADD:
+  - Create XOS VM
+  - Install git, make, OpenStack clients
+  - Check out XOS inside VM
+  - Copy admin-openrc.sh and id_rsa* to correct location
+* Add trusty-server-multi-nic image to OpenStack
+
+VTN
+* Install Docker on the head node
+* Edit /etc/default/docker so that it points to mgmtbr
+* Create onos-cord Docker container
+  # docker ...
+
+XOS
+* Create xos Docker containers attached to mgmtbr
+  # docker ...
+* Bring up XOS cord-pod configuration
+
+
+
+
+
+Test VTN
+* Try to create a network: neutron net-create testnet
diff --git a/xos/configurations/cord-pod/README.md b/xos/configurations/cord-pod/README.md
new file mode 100644
index 0000000..0fcdb13
--- /dev/null
+++ b/xos/configurations/cord-pod/README.md
@@ -0,0 +1,77 @@
+# XOS Docker Images
+
+## Introduction
+
+ XOS is comprised of 3 core services:
+
+  * A database backend (postgres)
+  * A webserver front end (django)
+  * A synchronizer daemon that interacts with the openstack backend.
+
+We have created separate dockerfiles for each of these services, making it
+easier to build the services independently and also deploy and run them in
+isolated environments.
+
+#### Database Container
+
+To build the database container:
+
+```
+$ cd postgresql; make build
+```
+
+#### XOS Container
+
+To build the XOS webserver container:
+
+```
+$ cd xos; make build
+```
+
+#### Synchronizer Container
+
+The Synchronizer shares many of the same dependencies as the XOS container. The
+synchronizer container takes advantage of this by building itself on top of the
+XOS image. This means you must build the XOS image before building the
+synchronizer image.  Assuming you have already built the XOS container,
+executing the following will build the Synchronizer container:
+
+```
+$ cd synchronizer; make build
+```
+
+#### Solution Compose File
+
+[Docker Compose](https://docs.docker.com/compose/) is a tool for defining and
+running multi-container Docker applications. With Compose, you use a Compose
+file to configure your application’s services. Then, using a single command, you
+create, start, scale, and manage all the services from your configuration.
+
+Included is a compose file in *YAML* format with content defined by the [Docker
+Compose Format](https://docs.docker.com/compose/compose-file/). With the compose
+file a complete XOS solution based on Docker containers can be instantiated
+using a single command. To start the instance you can use the command:
+
+```
+$ docker-compose up -d
+```
+
+You should now be able to access the login page by visiting
+`http://localhost:8000` and log in using the default `padmin@vicci.org` account
+with password `letmein`.
+
+#### Configuring XOS for OpenStack
+
+If you have your own OpenStack cluster, and you would like to configure XOS to
+control it, copy the `admin-openrc.sh` credentials file for your cluster to
+this directory.  Make sure that OpenStack commands work from the local machine
+using the credentials, e.g., `source ./admin-openrc.sh; nova list`.  Then run:
+
+```
+$ make
+```
+
+XOS will be launched (the Makefile will run the `docker-compose up -d` command
+for you) and configured with the nodes and images available in your
+OpenStack cloud.  You can then log in to XOS as described above and start creating
+slices and instances.
diff --git a/xos/configurations/cord-pod/admin-openrc.sh b/xos/configurations/cord-pod/admin-openrc.sh
new file mode 100644
index 0000000..f27fdac
--- /dev/null
+++ b/xos/configurations/cord-pod/admin-openrc.sh
@@ -0,0 +1,6 @@
+# Replace with the OpenStack admin credentials for your cluster
+export OS_TENANT_NAME=admin
+export OS_USERNAME=admin
+export OS_PASSWORD=admin
+export OS_AUTH_URL=http://localhost:35357/v2.0
+
diff --git a/xos/configurations/cord-pod/docker-compose.yml b/xos/configurations/cord-pod/docker-compose.yml
new file mode 100644
index 0000000..0116a1b
--- /dev/null
+++ b/xos/configurations/cord-pod/docker-compose.yml
@@ -0,0 +1,49 @@
+xos_db:
+    image: xosproject/xos-postgres
+    expose:
+        - "5432"
+
+xos_synchronizer_openstack:
+    command: bash -c "sleep 120; python /opt/xos/synchronizers/openstack/xos-synchronizer.py"
+    image: xosproject/xos-synchronizer-openstack
+    labels:
+        org.xosproject.kind: synchronizer
+        org.xosproject.target: openstack
+    links:
+        - xos_db
+    volumes:
+        - .:/root/setup:ro
+        - ../vtn/files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
+
+xos_synchronizer_onos:
+    image: xosproject/xos-synchronizer-openstack
+    command: bash -c "python /opt/xos/synchronizers/onos/onos-synchronizer.py -C /opt/xos/synchronizers/onos/onos_synchronizer_config"
+    labels:
+        org.xosproject.kind: synchronizer
+        org.xosproject.target: onos
+    links:
+        - xos_db
+    volumes:
+        - .:/root/setup:ro
+        - ./id_rsa:/opt/xos/synchronizers/onos/onos_key:ro  # private key
+
+# FUTURE
+#xos_swarm_synchronizer:
+#    image: xosproject/xos-swarm-synchronizer
+#    labels:
+#        org.xosproject.kind: synchronizer
+#        org.xosproject.target: swarm
+
+xos:
+    command: python /opt/xos/manage.py runserver 0.0.0.0:80 --insecure --makemigrations
+    image: xosproject/xos
+    links:
+        - xos_db
+    ports:
+        - "80:80"
+    volumes:
+        - .:/root/setup:ro
+        - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
+        - ../cord/xos_cord_config:/opt/xos/xos_configuration/xos_cord_config:ro
+        - ../vtn/files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
+        - ./id_rsa.pub:/opt/xos/synchronizers/onos/onos_key.pub:ro
diff --git a/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh b/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh
new file mode 100755
index 0000000..e7aac2e
--- /dev/null
+++ b/xos/configurations/cord-pod/make-vtn-networkconfig-json.sh
@@ -0,0 +1,71 @@
+FN=$SETUPDIR/vtn-network-cfg.json
+
+echo "Writing to $FN"
+
+rm -f $FN
+
+cat >> $FN <<EOF
+{
+    "apps" : {
+        "org.onosproject.cordvtn" : {
+            "cordvtn" : {
+                "gatewayMac" : "00:00:00:00:00:01",
+                "nodes" : [
+EOF
+
+NODES=$( sudo bash -c "source $SETUPDIR/admin-openrc.sh ; nova hypervisor-list" |grep -v ID|grep -v +|awk '{print $4}' )
+
+# also configure ONOS to manage the nm node
+#NM="neutron-gateway"
+#NODES="$NODES $NM"
+
+NODECOUNT=0
+for NODE in $NODES; do
+    ((NODECOUNT++))
+done
+
+I=0
+for NODE in $NODES; do
+    echo $NODE
+    NODEIP=`getent hosts $NODE | awk '{ print $1 }'`
+
+    PHYPORT=mlx0
+    LOCALIP=$NODEIP
+
+    ((I++))
+    cat >> $FN <<EOF
+                    {
+                      "hostname": "$NODE",
+                      "ovsdbIp": "$NODEIP",
+                      "ovsdbPort": "6641",
+                      "bridgeId": "of:000000000000000$I",
+                      "phyPortName": "$PHYPORT",
+                      "localIp": "$LOCALIP"
+EOF
+    if [[ "$I" -lt "$NODECOUNT" ]]; then
+        echo "                    }," >> $FN
+    else
+        echo "                    }" >> $FN
+    fi
+done
+
+# get the openstack admin password and username
+source $SETUPDIR/admin-openrc.sh
+NEUTRON_URL=`keystone endpoint-get --service network|grep publicURL|awk '{print $4}'`
+
+cat >> $FN <<EOF
+                ]
+            }
+        },
+        "org.onosproject.openstackswitching" : {
+            "openstackswitching" : {
+                 "do_not_push_flows" : "true",
+                 "neutron_server" : "$NEUTRON_URL/v2.0/",
+                 "keystone_server" : "$OS_AUTH_URL",
+                 "user_name" : "$OS_USERNAME",
+                 "password" : "$OS_PASSWORD"
+             }
+        }
+    }
+}
+EOF
diff --git a/xos/configurations/cord-pod/setup.yaml b/xos/configurations/cord-pod/setup.yaml
new file mode 100644
index 0000000..c13f0eb
--- /dev/null
+++ b/xos/configurations/cord-pod/setup.yaml
@@ -0,0 +1,61 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: >
+    * Adds OpenCloud Sites, Deployments, and Controllers.
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+
+    MyDeployment:
+      type: tosca.nodes.Deployment
+      properties:
+          flavors: m1.large, m1.medium, m1.small
+
+    MyOpenStack:
+      type: tosca.nodes.Controller
+      requirements:
+          - deployment:
+              node: MyDeployment
+              relationship: tosca.relationships.ControllerDeployment
+      properties:
+          backend_type: OpenStack
+          version: Kilo
+          auth_url: { get_script_env: [ SELF, adminrc, OS_AUTH_URL, LOCAL_FILE] }
+          admin_user: { get_script_env: [ SELF, adminrc, OS_USERNAME, LOCAL_FILE] }
+          admin_password: { get_script_env: [ SELF, adminrc, OS_PASSWORD, LOCAL_FILE] }
+          admin_tenant: { get_script_env: [ SELF, adminrc, OS_TENANT_NAME, LOCAL_FILE] }
+          domain: Default
+      artifacts:
+          adminrc: /root/setup/admin-openrc.sh
+
+    mysite:
+      type: tosca.nodes.Site
+      properties:
+          display_name: MySite
+          site_url: http://xosproject.org/
+      requirements:
+          - deployment:
+               node: MyDeployment
+               relationship: tosca.relationships.SiteDeployment
+               requirements:
+                   - controller:
+                       node: MyOpenStack
+                       relationship: tosca.relationships.UsesController
+
+    # This user already exists in XOS with this password
+    # It's an example of how to create new users
+    padmin@vicci.org:
+      type: tosca.nodes.User
+      requirements:
+          - site:
+              node: mysite
+              relationship: tosca.relationships.MemberOfSite
+      properties:
+          is_admin: true
+          is_active: true
+          firstname: XOS
+          lastname: admin
+          password: letmein
diff --git a/xos/configurations/cord-pod/vtn-external.yaml b/xos/configurations/cord-pod/vtn-external.yaml
new file mode 100644
index 0000000..ee41ac8
--- /dev/null
+++ b/xos/configurations/cord-pod/vtn-external.yaml
@@ -0,0 +1,31 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup CORD-related services -- vOLT, vCPE, vBNG.
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    service_ONOS_VTN:
+      type: tosca.nodes.ONOSService
+      requirements:
+      properties:
+          kind: onos
+          view_url: /admin/onos/onosservice/$id$/
+          no_container: true
+          rest_hostname: ctl.smbaker-xos-neu.xos-pg0.clemson.cloudlab.us
+
+    VTN_ONOS_app:
+      type: tosca.nodes.ONOSVTNApp
+      requirements:
+          - onos_tenant:
+              node: service_ONOS_VTN
+              relationship: tosca.relationships.TenantOfService
+      properties:
+          dependencies: org.onosproject.drivers, org.onosproject.drivers.ovsdb, org.onosproject.lldpprovider, org.onosproject.openflow-base, org.onosproject.ovsdb-base, org.onosproject.dhcp, org.onosproject.openstackswitching, org.onosproject.cordvtn
+          rest_onos/v1/network/configuration/: { get_artifact: [ SELF, vtn_network_cfg_json, LOCAL_FILE ] }
+      artifacts:
+          vtn_network_cfg_json: /root/setup/vtn-network-cfg.json
+
+
diff --git a/xos/configurations/opencloud/cdn-content.yaml b/xos/configurations/opencloud/cdn-content.yaml
index d4d1445..ebf6b82 100644
--- a/xos/configurations/opencloud/cdn-content.yaml
+++ b/xos/configurations/opencloud/cdn-content.yaml
@@ -111,37 +111,113 @@
                   node: main_service_provider
                   relationship: tosca.relationships.MemberOfServiceProvider
 
-    downloads.onosproject.org:
-        type: tosca.nodes.CDNPrefix
-        requirements:
-             - content_provider:
-                   node: on_lab_content
+    # Create CDN prefix onlab.vicci.org
+    onlab.vicci.org:

+        type: tosca.nodes.CDNPrefix

+        requirements:

+             - content_provider:

+                   node: on_lab_content

                    relationship: tosca.relationships.MemberOfContentProvider
-             - default_origin_server:
-                   node: http_downloads.onosproject.org
+
+    http_onos-videos.s3.amazonaws.com:
+        type: tosca.nodes.OriginServer

+        requirements:

+             - content_provider:

+                   node: on_lab_content

+                   relationship: tosca.relationships.MemberOfContentProvider
+
+    # Create origin server s3-us-west-1.amazonaws.com
+    http_s3-us-west-1.amazonaws.com:

+        type: tosca.nodes.OriginServer

+        requirements:

+             - content_provider:

+                   node: on_lab_content

+                   relationship: tosca.relationships.MemberOfContentProvider

+

+    # Create origin server s3.amazonaws.com

+    http_s3.amazonaws.com:

+        type: tosca.nodes.OriginServer

+        requirements:

+             - content_provider:

+                   node: on_lab_content

+                   relationship: tosca.relationships.MemberOfContentProvider
+
+    # Test Content Provider
+
+    testcp2:
+        type: tosca.nodes.ContentProvider

+        requirements:

+            - service_provider:

+                  node: main_service_provider

+                  relationship: tosca.relationships.MemberOfServiceProvider
+
+    http_www.cs.arizona.edu:
+        type: tosca.nodes.OriginServer

+        requirements:

+             - content_provider:

+                   node: testcp2

+                   relationship: tosca.relationships.MemberOfContentProvider
+
+    test-cdn.opencloud.us:
+        type: tosca.nodes.CDNPrefix

+        requirements:

+             - content_provider:

+                   node: testcp2

+                   relationship: tosca.relationships.MemberOfContentProvider

+

+             - default_origin_server:

+                   node: http_www.cs.arizona.edu

                    relationship: tosca.relationships.DefaultOriginServer
 
-    onlab.vicci.org:
-        type: tosca.nodes.CDNPrefix
-        requirements:
-             - content_provider:
-                   node: on_lab_content
-                   relationship: tosca.relationships.MemberOfContentProvider
-             - default_origin_server:
-                   node: http_onlab.vicci.org
-                   relationship: tosca.relationships.DefaultOriginServer
+    # Health Checks
 
-    http_downloads.onosproject.org:
-        type: tosca.nodes.OriginServer
+    healthcheck_dns_onlab.vicci.org:
+        type: tosca.nodes.HpcHealthCheck
         requirements:
-             - content_provider:
-                   node: on_lab_content
-                   relationship: tosca.relationships.MemberOfContentProvider
+           - hpc_service:
+                 node: HyperCache
+                 relationship: tosca.relationships.MemberOfService
+        properties:
+           kind: dns
+           resource_name: onlab.vicci.org
 
-    http_onlab.vicci.org:
-        type: tosca.nodes.OriginServer
+    healthcheck_dns_test-cdn.opencloud.us:
+        type: tosca.nodes.HpcHealthCheck
         requirements:
-             - content_provider:
-                   node: on_lab_content
-                   relationship: tosca.relationships.MemberOfContentProvider
+           - hpc_service:
+                 node: HyperCache
+                 relationship: tosca.relationships.MemberOfService
+        properties:
+           kind: dns
+           resource_name: test-cdn.opencloud.us
 
+    healthcheck_http_test-cdn-index:
+        type: tosca.nodes.HpcHealthCheck
+        requirements:
+           - hpc_service:
+                 node: HyperCache
+                 relationship: tosca.relationships.MemberOfService
+        properties:
+           kind: http
+           resource_name: test-cdn.opencloud.us:/
+           result_contains: Lowenthal
+
+    healthcheck_http_onlab_onos_image:
+        type: tosca.nodes.HpcHealthCheck
+        requirements:
+           - hpc_service:
+                 node: HyperCache
+                 relationship: tosca.relationships.MemberOfService
+        properties:
+           kind: http
+           resource_name: onlab.vicci.org:/onos/vm/onos-tutorial-1.1.0r220-ovf.zip
+
+    healthcheck_http_onlab_mininet_image:
+        type: tosca.nodes.HpcHealthCheck
+        requirements:
+           - hpc_service:
+                 node: HyperCache
+                 relationship: tosca.relationships.MemberOfService
+        properties:
+           kind: http
+           resource_name: onlab.vicci.org:/mininet-vm/mininet-2.1.0-130919-ubuntu-13.04-server-amd64-ovf.zip
diff --git a/xos/configurations/opencloud/cdn-opencloud.yaml b/xos/configurations/opencloud/cdn-opencloud.yaml
index 4a13233..f66e0f2 100644
--- a/xos/configurations/opencloud/cdn-opencloud.yaml
+++ b/xos/configurations/opencloud/cdn-opencloud.yaml
@@ -40,7 +40,7 @@
       description: HyperCache Slice
       type: tosca.nodes.Slice
       properties:
-          exposed_ports: tcp 2120:2128, tcp 3200:3209, tcp 8006, tcp 8009, tcp 8015
+          exposed_ports: tcp 2120:2128, tcp 3200:3209, tcp 8006, tcp 8009, tcp 8015, tcp 80
       requirements:
           - cdn_service:
               node: HyperCache
diff --git a/xos/configurations/opencloud/docker-compose.yml b/xos/configurations/opencloud/docker-compose.yml
index 828175e..3813dee 100644
--- a/xos/configurations/opencloud/docker-compose.yml
+++ b/xos/configurations/opencloud/docker-compose.yml
@@ -5,8 +5,8 @@
 
 xos_synchronizer_openstack:
     image: xosproject/xos-synchronizer-openstack
-    #command: bash -c "update-ca-certificates; sleep 120; python /opt/xos/synchronizers/openstack/xos-synchronizer.py"
-    command: sleep 86400
+    command: bash -c "update-ca-certificates; sleep 120; python /opt/xos/synchronizers/openstack/xos-synchronizer.py"
+    #command: sleep 86400
     labels:
         org.xosproject.kind: synchronizer
         org.xosproject.target: openstack
@@ -15,6 +15,35 @@
     volumes:
         - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
         - /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro
+        - ./files/xos_opencloud_config:/opt/xos/xos_configuration/xos_opencloud_config:ro
+
+xos_synchronizer_hpc:
+    image: xosproject/xos-synchronizer-openstack
+    command: bash -c "sleep 120; python /opt/xos/synchronizers/hpc/hpc-synchronizer.py -C /opt/xos/synchronizers/hpc/hpc_synchronizer_config"
+    #command: sleep 86400
+    labels:
+        org.xosproject.kind: synchronizer
+        org.xosproject.target: hpc
+    links:
+        - xos_db
+    volumes:
+        - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
+        - /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro
+        - ./files/xos_opencloud_config:/opt/xos/xos_configuration/xos_opencloud_config:ro
+
+xos_watcher_hpc:
+    image: xosproject/xos-synchronizer-openstack
+    command: bash -c "sleep 120; python /opt/xos/synchronizers/hpc/hpc_watcher.py"
+    #command: sleep 86400
+    labels:
+        org.xosproject.kind: watcher
+        org.xosproject.target: hpc
+    links:
+        - xos_db
+    volumes:
+        - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
+        - /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro
+        - ./files/xos_opencloud_config:/opt/xos/xos_configuration/xos_opencloud_config:ro
 
 # FUTURE
 #xos_swarm_synchronizer:
@@ -32,3 +61,4 @@
         - xos_db
     volumes:
       - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
+      - ./files/xos_opencloud_config:/opt/xos/xos_configuration/xos_opencloud_config:ro
diff --git a/xos/configurations/opencloud/files/xos_opencloud_config b/xos/configurations/opencloud/files/xos_opencloud_config
new file mode 100644
index 0000000..62291b6
--- /dev/null
+++ b/xos/configurations/opencloud/files/xos_opencloud_config
@@ -0,0 +1,3 @@
+[server]
+restapi_hostname=portal.opencloud.us
+restapi_port=80
diff --git a/xos/core/admin.py b/xos/core/admin.py
index f0d402e..904d64e 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1840,7 +1840,7 @@
     verbose_name_plural = "Controller Networks"
     verbose_name = "Controller Network"
     suit_classes = 'suit-tab suit-tab-admin-only'
-    fields = ['backend_status_icon', 'controller','net_id','subnet_id']
+    fields = ['backend_status_icon', 'controller','net_id','subnet_id','subnet']
     readonly_fields = ('backend_status_icon', )
 
 class NetworkForm(forms.ModelForm):
diff --git a/xos/core/models/instance.py b/xos/core/models/instance.py
index 62a86c4..7f13eb8 100644
--- a/xos/core/models/instance.py
+++ b/xos/core/models/instance.py
@@ -101,6 +101,9 @@
     volumes = models.TextField(null=True, blank=True, help_text="Comma-separated list of directories to expose to parent context")
     parent = models.ForeignKey("Instance", null=True, blank=True, help_text="Parent Instance for containers nested inside of VMs")
 
+    def get_controller (self):
+        return self.node.site_deployment.controller
+
     def __unicode__(self):
         if self.name and Slice.objects.filter(id=self.slice_id) and (self.name != self.slice.name):
             # NOTE: The weird check on self.slice_id was due to a problem when
@@ -183,6 +186,9 @@
 
     # return an address that the synchronizer can use to SSH to the instance
     def get_ssh_ip(self):
+        management=self.get_network_ip("management")
+        if management:
+            return management
         return self.get_network_ip("nat")
 
     @staticmethod
diff --git a/xos/core/models/plcorebase.py b/xos/core/models/plcorebase.py
index 0822bf5..99acc15 100644
--- a/xos/core/models/plcorebase.py
+++ b/xos/core/models/plcorebase.py
@@ -224,6 +224,9 @@
         self._initial = self._dict # for PlModelMixIn
         self.silent = False
 
+    def get_controller(self):
+        return self.controller
+
     def can_update(self, user):
         return user.can_update_root()
 
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/core/models/site.py b/xos/core/models/site.py
index 1bdef36..b98c40a 100644
--- a/xos/core/models/site.py
+++ b/xos/core/models/site.py
@@ -310,6 +310,11 @@
     site = models.ForeignKey(Site,related_name='controllersite')
     controller = models.ForeignKey(Controller, null=True, blank=True, related_name='controllersite')
     tenant_id = StrippedCharField(null=True, blank=True, max_length=200, db_index=True, help_text="Keystone tenant id")
+
+    def delete(self, *args, **kwds):
+        pdb.set_trace()
+        super(ControllerSite, self).delete(*args, **kwds)
+
     
     class Meta:
         unique_together = ('site', 'controller') 
diff --git a/xos/core/views/legacyapi.py b/xos/core/views/legacyapi.py
index 4657116..b5592c0 100644
--- a/xos/core/views/legacyapi.py
+++ b/xos/core/views/legacyapi.py
@@ -121,6 +121,13 @@
         for ps_node in ps_site.nodes.all():
             node_ids.append(ps_id_to_pl_id(ps_node.id))
 
+        if ps_site.location:
+            longitude = ps_site.location.longitude
+            latitude = ps_site.location.latitude
+        else:
+            longitude = 0
+            latitude = 0
+
         site = {"site_id": ps_id_to_pl_id(ps_site.id),
                 "node_ids": node_ids,
                 "pcu_ids": [],
@@ -134,8 +141,8 @@
                 "url": None,
                 "site_tag_ids": [],
                 "enabled": True,
-                "longitude": float(ps_site.location.longitude),
-                "latitude": float(ps_site.location.latitude),
+                "longitude": float(longitude),
+                "latitude": float(latitude),
                 "slice_ids": slice_ids,
                 "login_base": ps_site.login_base,
                 "peer_id": None}
@@ -285,7 +292,7 @@
             'hostipmap':hostipmap,
             'hostnatmap':hostnatmap,
             'hostprivmap':hostprivmap,
-            'instances': instances,
+            'slivers': instances,
             'interfaces': allinterfaces,
             'sites': sites,
             'nodes': nodes}
diff --git a/xos/core/xoslib/methods/ceilometerview.py b/xos/core/xoslib/methods/ceilometerview.py
index f26d84a..c2ecb15 100644
--- a/xos/core/xoslib/methods/ceilometerview.py
+++ b/xos/core/xoslib/methods/ceilometerview.py
@@ -3,6 +3,7 @@
 import urllib2
 import pytz
 import datetime
+import time
 from rest_framework.decorators import api_view
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
@@ -29,17 +30,23 @@
     if not monitoring_channel:
         raise XOSMissingField("Monitoring channel is missing for this tenant...Create one and invoke this REST API")
     #TODO: Wait until URL is completely UP
+    MAX_ATTEMPTS = 5
+    attempts = 0
     while True:
         try:
-            response = urllib2.urlopen(monitoring_channel.ceilometer_url,timeout=1)
+            response = urllib2.urlopen(monitoring_channel.ceilometer_url)
             break
         except urllib2.HTTPError, e:
-            logger.info('SRIKANTH: HTTP error %(reason)s' % {'reason':e.reason})
+            logger.info('HTTP error %(reason)s' % {'reason':e.reason})
             break
         except urllib2.URLError, e:
-            logger.info('SRIKANTH: URL error %(reason)s' % {'reason':e.reason})
+            attempts += 1
+            if attempts >= MAX_ATTEMPTS:
+                raise XOSServiceUnavailable("Ceilometer channel is not ready yet...Try again later")
+            logger.info('URL error %(reason)s' % {'reason':e.reason})
+            time.sleep(1)
             pass
-    logger.info("SRIKANTH: Ceilometer proxy URL for user %(user)s is %(url)s" % {'user':user.username,'url':monitoring_channel.ceilometer_url})
+    logger.info("Ceilometer proxy URL for user %(user)s is %(url)s" % {'user':user.username,'url':monitoring_channel.ceilometer_url})
     return monitoring_channel.ceilometer_url
 
 def getTenantControllerTenantMap(user, slice=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..4e5807e 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
@@ -80,6 +94,7 @@
                        "hostname": instance.node.name,
                        "instance_id": instance.instance_id,
                        "username": "ubuntu",
+                       "ssh_ip": instance.get_ssh_ip(),
                      }
             key_name = self.service_key_name
         elif (instance.isolation == "container"):
@@ -91,6 +106,7 @@
                        "instance_name": "rootcontext",
                        "username": "root",
                        "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
+                       # ssh_ip is not used for container-on-metal
                      }
             key_name = self.get_node_key(node)
         else:
@@ -107,7 +123,7 @@
                        "instance_name": instance.parent.name,
                        "instance_id": instance.parent.instance_id,
                        "username": "ubuntu",
-                       "nat_ip": instance.parent.get_ssh_ip(),
+                       "ssh_ip": instance.parent.get_ssh_ip(),
                        "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
                          }
             key_name = instance.parent.slice.service.private_key_fn
@@ -142,30 +158,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/base/ansible.py b/xos/synchronizers/base/ansible.py
index d2dca3b..d92835a 100644
--- a/xos/synchronizers/base/ansible.py
+++ b/xos/synchronizers/base/ansible.py
@@ -34,11 +34,20 @@
         if (l.startswith(magic_str)):
             w = len(magic_str)
             str = l[w:]
+
+            # handle ok: [127.0.0.1] => (item=org.onosproject.driver) => {...
+            if str.startswith("(") and (" => {" in str):
+                str = str.split("=> ",1)[1]
+
             d = json.loads(str)
             results.append(d)
         elif (l.startswith(magic_str2)):
             w = len(magic_str2)
             str = l[w:]
+
+            if str.startswith("(") and (" => {" in str):
+                str = str.split("=> ",1)[1]
+
             d = json.loads(str)
             results.append(d)
 
@@ -149,12 +158,12 @@
     private_key = opts["private_key"]
     baremetal_ssh = opts.get("baremetal_ssh",False)
     if baremetal_ssh:
-        # no instance_id or nat_ip for baremetal
+        # no instance_id or ssh_ip for baremetal
         # we never proxy to baremetal
         proxy_ssh = False
     else:
         instance_id = opts["instance_id"]
-        nat_ip = opts["nat_ip"]
+        ssh_ip = opts["ssh_ip"]
         try:
             proxy_ssh = Config().observer_proxy_ssh
         except:
@@ -172,7 +181,15 @@
     f = open(config_pathname, "w")
     f.write("[ssh_connection]\n")
     if proxy_ssh:
-        proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname)
+        proxy_ssh_key = getattr(Config(), "observer_proxy_ssh_key", None)
+        proxy_ssh_user = getattr(Config(), "observer_proxy_ssh_user", "root")
+        if proxy_ssh_key:
+            # If proxy_ssh_key is known, then we can proxy into the compute
+            # node without needing to have the OpenCloud sshd machinery in
+            # place.
+            proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s nc %s 22" % (proxy_ssh_key, proxy_ssh_user, hostname, ssh_ip)
+        else:
+            proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname)
         f.write('ssh_args = -o "%s"\n' % proxy_command)
     f.write('scp_if_ssh = True\n')
     f.write('pipelining = True\n')
@@ -186,7 +203,7 @@
         f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname))
     else:
         # acb: Login user is hardcoded, this is not great
-        f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (nat_ip, private_key_pathname))
+        f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (ssh_ip, private_key_pathname))
     f.close()
 
     # SSH will complain if private key is world or group readable
diff --git a/xos/synchronizers/base/syncstep.py b/xos/synchronizers/base/syncstep.py
index bdab8f3..54c4b89 100644
--- a/xos/synchronizers/base/syncstep.py
+++ b/xos/synchronizers/base/syncstep.py
@@ -142,10 +142,10 @@
     def sync_record(self, o):
         try:
             controller = o.get_controller()
-            controller_register = json.loads(o.node.site_deployment.controller.backend_register)
+            controller_register = json.loads(controller.backend_register)
 
             if (controller_register.get('disabled',False)):
-                raise InnocuousException('Controller %s is disabled'%sliver.node.site_deployment.controller.name)
+                raise InnocuousException('Controller %s is disabled'%controller.name)
         except AttributeError:
             pass
 
@@ -249,7 +249,7 @@
                     except:
                         error = '%s'%str_e
 
-                    if isinstance(e, InnocuousException) and not force_error:
+                    if isinstance(e, InnocuousException):
                         o.backend_status = '1 - %s'%error
                     else:
                         o.backend_status = '2 - %s'%error
diff --git a/xos/synchronizers/hpc/run.sh b/xos/synchronizers/hpc/run.sh
index f77d751..9d22047 100755
--- a/xos/synchronizers/hpc/run.sh
+++ b/xos/synchronizers/hpc/run.sh
@@ -1,6 +1,2 @@
-#if [[ ! -e ./hpc-backend.py ]]; then
-#    ln -s ../xos-observer.py hpc-backend.py
-#fi
-
 export XOS_DIR=/opt/xos
-python hpc-observer.py  -C $XOS_DIR/observers/hpc/hpc_observer_config
+python hpc-synchronizer.py  -C $XOS_DIR/synchronizers/hpc/hpc_synchronizer_config
diff --git a/xos/synchronizers/hpc/start.sh b/xos/synchronizers/hpc/start.sh
index 305c07f..3153a7d 100755
--- a/xos/synchronizers/hpc/start.sh
+++ b/xos/synchronizers/hpc/start.sh
@@ -1,6 +1,2 @@
-#if [[ ! -e ./hpc-backend.py ]]; then
-#    ln -s ../xos-observer.py hpc-backend.py
-#fi
-
 export XOS_DIR=/opt/xos
-nohup python hpc-observer.py  -C $XOS_DIR/observers/hpc/hpc_observer_config > /dev/null 2>&1 &
+nohup python hpc-synchronizer.py  -C $XOS_DIR/synchronizers/hpc/hpc_synchronizer_config > /dev/null 2>&1 &
diff --git a/xos/synchronizers/hpc/stop.sh b/xos/synchronizers/hpc/stop.sh
index a0b4a8e..780e25c 100755
--- a/xos/synchronizers/hpc/stop.sh
+++ b/xos/synchronizers/hpc/stop.sh
@@ -1 +1 @@
-pkill -9 -f hpc-observer.py
+pkill -9 -f hpc-synchronizer.py
diff --git a/xos/synchronizers/monitoring_channel/steps/sync_sflowservice.py b/xos/synchronizers/monitoring_channel/steps/sync_sflowservice.py
index 5e5cd83..154c5ab 100644
--- a/xos/synchronizers/monitoring_channel/steps/sync_sflowservice.py
+++ b/xos/synchronizers/monitoring_channel/steps/sync_sflowservice.py
@@ -55,7 +55,6 @@
         fields["instance_hostname"] = self.get_instance(o).instance_name.replace("_","-")
         fields["sflow_port"] = o.sflow_port
         fields["sflow_api_port"] = o.sflow_api_port
-        fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
         fields["sflow_container"] = "sflowpubsub"
         return fields
 
diff --git a/xos/synchronizers/monitoring_channel/steps/sync_sflowtenant.py b/xos/synchronizers/monitoring_channel/steps/sync_sflowtenant.py
index 6de0374..a15fa54 100644
--- a/xos/synchronizers/monitoring_channel/steps/sync_sflowtenant.py
+++ b/xos/synchronizers/monitoring_channel/steps/sync_sflowtenant.py
@@ -64,7 +64,6 @@
         instance = self.get_instance(o)
 
         fields={}
-        fields["nat_ip"] = instance.get_ssh_ip()
         fields["sflow_api_base_url"] = self.get_sflow_service(o).sflow_api_url
         fields["sflow_api_port"] = self.get_sflow_service(o).sflow_api_port
         fields["listening_endpoint"] = o.listening_endpoint
diff --git a/xos/synchronizers/onos/onos-ext-notifier-1.0-SNAPSHOT.oar b/xos/synchronizers/onos/onos-ext-notifier-1.0-SNAPSHOT.oar
index 893c01a..3d44818 100644
--- a/xos/synchronizers/onos/onos-ext-notifier-1.0-SNAPSHOT.oar
+++ b/xos/synchronizers/onos/onos-ext-notifier-1.0-SNAPSHOT.oar
Binary files differ
diff --git a/xos/synchronizers/onos/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar b/xos/synchronizers/onos/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
index 7a32268..e9a4593 100644
--- a/xos/synchronizers/onos/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
+++ b/xos/synchronizers/onos/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
Binary files differ
diff --git a/xos/synchronizers/onos/steps/sync_onosapp.py b/xos/synchronizers/onos/steps/sync_onosapp.py
index 8942e59..281df10 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,36 @@
     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["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 +220,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_onosapp_nocontainer.yaml b/xos/synchronizers/onos/steps/sync_onosapp_nocontainer.yaml
new file mode 100644
index 0000000..5aad569
--- /dev/null
+++ b/xos/synchronizers/onos/steps/sync_onosapp_nocontainer.yaml
@@ -0,0 +1,57 @@
+---
+- hosts: 127.0.0.1
+  connection: local
+  vars:
+    appname: {{ appname }}
+    dependencies: {{ dependencies }}
+{% if component_configs %}
+    component_configs:
+{% for component_config in component_configs %}
+       - component: {{ component_config.component }}
+         config_params: {{  component_config.config_params }}
+{% endfor %}
+{% endif %}
+{% if rest_configs %}
+    rest_configs:
+{% for rest_config in rest_configs %}
+       - endpoint: {{ rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+{% if early_rest_configs %}
+    early_rest_configs:
+{% for early_rest_config in early_rest_configs %}
+       - endpoint: {{ early_rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ early_rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+    rest_hostname: {{ rest_hostname }}
+    rest_port: {{ rest_port }}
+
+  tasks:
+{% if dependencies %}
+  - name: Add dependencies to ONOS
+    uri:
+      url: http://{{ '{{' }} rest_hostname {{ '}}' }}:{{ '{{' }} rest_port {{ '}}' }}/onos/v1/applications/{{ '{{' }} item {{ '}}' }}/active
+      method: POST
+      user: karaf
+      password: karaf
+    with_items:
+        {% for dependency in dependencies %}
+        - {{ dependency }}
+        {% endfor %}
+{% endif %}
+
+{% if rest_configs %}
+# Do this after services have been activated, or it will cause an exception.
+# vOLT will re-read its net config; vbng may not.
+  - name: Add ONOS configuration values
+    uri:
+      url: http://{{ '{{' }} rest_hostname {{ '}}' }}:{{ '{{' }} rest_port {{ '}}' }}/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
+      body: "{{ '{{' }} item.body {{ '}}' }}"
+      body_format: raw
+      method: POST
+      user: karaf
+      password: karaf
+    with_items: "rest_configs"
+{% endif %}
diff --git a/xos/synchronizers/onos/steps/sync_onosservice.py b/xos/synchronizers/onos/steps/sync_onosservice.py
index e70be0c..e1f62c7 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():
@@ -57,10 +54,17 @@
         fields={}
         fields["instance_hostname"] = self.get_instance(o).instance_name.replace("_","-")
         fields["appname"] = o.name
-        fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
+        fields["ssh_ip"] = self.get_instance(o).get_ssh_ip()
         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)
diff --git a/xos/synchronizers/openstack/event_loop.py b/xos/synchronizers/openstack/event_loop.py
index 6cfc9f6..3a14511 100644
--- a/xos/synchronizers/openstack/event_loop.py
+++ b/xos/synchronizers/openstack/event_loop.py
@@ -23,8 +23,8 @@
 #from timeout import timeout
 from xos.config import Config, XOS_DIR
 from synchronizers.base.steps import *
-from syncstep import SyncStep
-from toposort import toposort
+from synchronizers.base.syncstep import SyncStep
+from synchronizers.base.toposort import toposort
 from synchronizers.base.error_mapper import *
 from synchronizers.openstack.openstacksyncstep import OpenStackSyncStep
 from synchronizers.base.steps.sync_object import SyncObject
diff --git a/xos/synchronizers/openstack/model_policies/model_policy_Network.py b/xos/synchronizers/openstack/model_policies/model_policy_Network.py
index 38d0377..9f9e5fd 100644
--- a/xos/synchronizers/openstack/model_policies/model_policy_Network.py
+++ b/xos/synchronizers/openstack/model_policies/model_policy_Network.py
@@ -22,4 +22,9 @@
 		if network not in network_deploy_lookup or \
 		  expected_controller not in network_deploy_lookup[network]:
 			nd = ControllerNetwork(network=network, controller=expected_controller, lazy_blocked=True)
+                        if network.subnet:
+                            # XXX: Possibly unpredictable behavior if there is
+                            # more than one ControllerNetwork and the subnet
+                            # is specified.
+                            nd.subnet = network.subnet
 			nd.save()
diff --git a/xos/synchronizers/openstack/openstacksyncstep.py b/xos/synchronizers/openstack/openstacksyncstep.py
index cc568f8..46056cf 100644
--- a/xos/synchronizers/openstack/openstacksyncstep.py
+++ b/xos/synchronizers/openstack/openstacksyncstep.py
@@ -1,6 +1,6 @@
 import os
 import base64
-from syncstep import SyncStep
+from synchronizers.base.syncstep import SyncStep
 
 class OpenStackSyncStep(SyncStep):
     """ XOS Sync step for copying data to OpenStack 
diff --git a/xos/synchronizers/openstack/steps/sync_controller_networks.py b/xos/synchronizers/openstack/steps/sync_controller_networks.py
index 0b876fa..f8b2292 100644
--- a/xos/synchronizers/openstack/steps/sync_controller_networks.py
+++ b/xos/synchronizers/openstack/steps/sync_controller_networks.py
@@ -34,22 +34,21 @@
         cidr = '%d.%d.%d.%d/24'%(a,b,c,d)
         return cidr
 
-    def alloc_gateway(self, uuid):
-        # 16 bits only
-        uuid_masked = uuid & 0xffff
-        a = 10
-        b = uuid_masked >> 8
-        c = uuid_masked & 0xff
-        d = 1
-
-        gateway = '%d.%d.%d.%d'%(a,b,c,d)
-        return gateway
-
+    def alloc_gateway(self, subnet):
+        parts = subnet.split(".")
+        if len(parts)!=4:
+            raise Exception("Invalid subnet %s" % subnet)
+        return ".".join(parts[:3]) + ".1"
 
     def save_controller_network(self, controller_network):
         network_name = controller_network.network.name
         subnet_name = '%s-%d'%(network_name,controller_network.pk)
-        cidr = self.alloc_subnet(controller_network.pk)
+        if controller_network.subnet and controller_network.subnet.strip():
+            # If a subnet is already specified (pass in by the creator), then
+            # use that rather than auto-generating one.
+            cidr = controller_network.subnet.strip()
+        else:
+            cidr = self.alloc_subnet(controller_network.pk)
         self.cidr=cidr
         slice = controller_network.network.owner
 
@@ -63,9 +62,9 @@
                     'subnet_name':subnet_name,
                     'ansible_tag':'%s-%s@%s'%(network_name,slice.slicename,controller_network.controller.name),
                     'cidr':cidr,
-                    'gateway':self.alloc_gateway(controller_network.pk),
+                    'gateway':self.alloc_gateway(cidr),
                     'use_vtn':getattr(Config(), "networking_use_vtn", False),
-                    'delete':False	
+                    'delete':False
                     }
         return network_fields
 
diff --git a/xos/synchronizers/openstack/syncstep.py b/xos/synchronizers/openstack/syncstep.py
index bdab8f3..d1639b4 100644
--- a/xos/synchronizers/openstack/syncstep.py
+++ b/xos/synchronizers/openstack/syncstep.py
@@ -142,10 +142,10 @@
     def sync_record(self, o):
         try:
             controller = o.get_controller()
-            controller_register = json.loads(o.node.site_deployment.controller.backend_register)
+            controller_register = json.loads(controller.backend_register)
 
             if (controller_register.get('disabled',False)):
-                raise InnocuousException('Controller %s is disabled'%sliver.node.site_deployment.controller.name)
+                raise InnocuousException('Controller %s is disabled'%controller.name)
         except AttributeError:
             pass
 
diff --git a/xos/tosca/custom_types/cdn.m4 b/xos/tosca/custom_types/cdn.m4
index 59d4ee6..0d33715 100644
--- a/xos/tosca/custom_types/cdn.m4
+++ b/xos/tosca/custom_types/cdn.m4
@@ -33,6 +33,26 @@
             user:
                 type: tosca.capabilities.xos.CDNPrefix
 
+    tosca.nodes.HpcHealthCheck:
+        derived_from: tosca.nodes.Root
+
+        properties:
+            kind:
+                type: string
+                required: true
+                description: dns | http | nameserver
+            resource_name:
+                type: string
+                required: true
+                description: name of resource to query
+            result_contains:
+                type: string
+                required: false
+                description: soemthing to look for inside the result
+        capabilities:
+            healthcheck:
+                type: tosca.capabilities.xos.HpcHealthCheck
+
     tosca.relationships.MemberOfServiceProvider:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.ServiceProvider ]
@@ -57,4 +77,7 @@
     tosca.capabilities.xos.OriginServer:
         derived_from: tosca.capabilities.Root
 
+    tosca.capabilities.xos.HpcHealthCheck:
+        derived_from: tosca.capabilities.Root
+
 
diff --git a/xos/tosca/custom_types/cdn.yaml b/xos/tosca/custom_types/cdn.yaml
index 59d4ee6..0d33715 100644
--- a/xos/tosca/custom_types/cdn.yaml
+++ b/xos/tosca/custom_types/cdn.yaml
@@ -33,6 +33,26 @@
             user:
                 type: tosca.capabilities.xos.CDNPrefix
 
+    tosca.nodes.HpcHealthCheck:
+        derived_from: tosca.nodes.Root
+
+        properties:
+            kind:
+                type: string
+                required: true
+                description: dns | http | nameserver
+            resource_name:
+                type: string
+                required: true
+                description: name of resource to query
+            result_contains:
+                type: string
+                required: false
+                description: soemthing to look for inside the result
+        capabilities:
+            healthcheck:
+                type: tosca.capabilities.xos.HpcHealthCheck
+
     tosca.relationships.MemberOfServiceProvider:
         derived_from: tosca.relationships.Root
         valid_target_types: [ tosca.capabilities.xos.ServiceProvider ]
@@ -57,4 +77,7 @@
     tosca.capabilities.xos.OriginServer:
         derived_from: tosca.capabilities.Root
 
+    tosca.capabilities.xos.HpcHealthCheck:
+        derived_from: tosca.capabilities.Root
+
 
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 20a537a..760e3c9 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -121,6 +121,15 @@
             rest_onos/v1/network/configuration/:
                 type: string
                 required: false
+            rest_hostname:
+                type: string
+                required: false
+            rest_port:
+                type: string
+                required: false
+            no_container:
+                type: boolean
+                default: false
 
 
     tosca.nodes.ONOSApp:
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 1b5db39..8135b7f 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -151,6 +151,15 @@
             rest_onos/v1/network/configuration/:
                 type: string
                 required: false
+            rest_hostname:
+                type: string
+                required: false
+            rest_port:
+                type: string
+                required: false
+            no_container:
+                type: boolean
+                default: false
 
 
     tosca.nodes.ONOSApp:
diff --git a/xos/tosca/resources/hpchealthcheck.py b/xos/tosca/resources/hpchealthcheck.py
new file mode 100644
index 0000000..93a0912
--- /dev/null
+++ b/xos/tosca/resources/hpchealthcheck.py
@@ -0,0 +1,39 @@
+import importlib
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from services.hpc.models import HpcHealthCheck, HpcService
+
+from xosresource import XOSResource
+
+class XOSHpcHealthCheck(XOSResource):
+    provides = "tosca.nodes.HpcHealthCheck"
+    xos_model = HpcHealthCheck
+    name_field = None
+    copyin_props = ("kind", "resource_name", "result_contains")
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSHpcHealthCheck, self).get_xos_args()
+
+        service_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
+        if service_name:
+            args["hpcService"] = self.get_xos_object(HpcService, throw_exception=throw_exception, name=service_name)
+
+        return args
+
+    def get_existing_objs(self):
+        args = self.get_xos_args(throw_exception=True)
+
+        return list( HpcHealthCheck.objects.filter(hpcService=args["hpcService"], kind=args["kind"], resource_name=args["resource_name"]) )
+
+    def postprocess(self, obj):
+        pass
+
+    def can_delete(self, obj):
+        return super(XOSTenant, self).can_delete(obj)
+
diff --git a/xos/tosca/resources/network.py b/xos/tosca/resources/network.py
index f483b6c..7b513c3 100644
--- a/xos/tosca/resources/network.py
+++ b/xos/tosca/resources/network.py
@@ -39,6 +39,10 @@
             # we have to manually fill in some defaults.
             args["permit_all_slices"] = True
 
+        cidr = self.get_property_default("cidr", None)
+        if cidr:
+            args["subnet"] = cidr
+
         return args
 
     def postprocess(self, obj):
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
index 5947400..321600d 100644
--- a/xos/tosca/resources/onosapp.py
+++ b/xos/tosca/resources/onosapp.py
@@ -57,7 +57,7 @@
             v = d.value
             if k.startswith("config_"):
                 self.set_tenant_attr(obj, k, v)
-            elif k.startswith("rest_"):
+            elif k.startswith("rest_") and (k!="rest_hostname") and (k!="rest_port"):
                 self.set_tenant_attr(obj, k, v)
             elif k.startswith("component_config"):
                 self.set_tenant_attr(obj, k, v)
diff --git a/xos/tosca/resources/onosservice.py b/xos/tosca/resources/onosservice.py
index b742ebb..c836a6c 100644
--- a/xos/tosca/resources/onosservice.py
+++ b/xos/tosca/resources/onosservice.py
@@ -13,7 +13,7 @@
 class XOSONOSService(XOSService):
     provides = "tosca.nodes.ONOSService"
     xos_model = ONOSService
-    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber"]
+    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber", "rest_hostname", "rest_port", "no_container"]
 
     def set_service_attr(self, obj, prop_name, value):
         value = self.try_intrinsic_function(value)
@@ -36,6 +36,6 @@
             v = d.value
             if k.startswith("config_"):
                 self.set_service_attr(obj, k, v)
-            elif k.startswith("rest_"):
+            elif k.startswith("rest_")  and (k!="rest_hostname") and (k!="rest_port"):
                 self.set_service_attr(obj, k, v)
 
diff --git a/xos/tosca/samples/two_slices_two_networks.yaml b/xos/tosca/samples/two_slices_two_networks.yaml
new file mode 100644
index 0000000..080a6f0
--- /dev/null
+++ b/xos/tosca/samples/two_slices_two_networks.yaml
@@ -0,0 +1,69 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Template for deploying a single server with predefined properties.
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
+    Private:
+      type: tosca.nodes.NetworkTemplate
+
+    # this one lets XOS auto-allocate a subnet
+    producer_private_network:
+      type: tosca.nodes.network.Network
+      properties:
+          ip_version: 4
+      requirements:
+          - network_template:
+              node: Private
+              relationship: tosca.relationships.UsesNetworkTemplate
+          - slice:
+              node: mysite_producer
+              relationship: tosca.relationships.MemberOfSlice
+          - slice:
+              node: mysite_producer
+              relationship: tosca.relationships.ConnectsToSlice
+
+    # this one specifies the subnet to use
+    producer_private_network2:
+      type: tosca.nodes.network.Network
+      properties:
+          ip_version: 4
+          cidr: 123.123.0.0/16
+      requirements:
+          - network_template:
+              node: Private
+              relationship: tosca.relationships.UsesNetworkTemplate
+          - slice:
+              node: mysite_producer
+              relationship: tosca.relationships.MemberOfSlice
+          - slice:
+              node: mysite_producer
+              relationship: tosca.relationships.ConnectsToSlice
+
+    mysite_producer:
+      type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: mysite
+                relationship: tosca.relationships.MemberOfSite
+
+    mysite_consumer:
+      type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: mysite
+                relationship: tosca.relationships.MemberOfSite
+          - network:
+                node: producer_private_network
+                relationship: tosca.relationships.ConnectsToNetwork
+          - network2:
+                node: producer_private_network2
+                relationship: tosca.relationships.ConnectsToNetwork
+
+
diff --git a/xos/tosca/samples/vtn-external.yaml b/xos/tosca/samples/vtn-external.yaml
new file mode 100644
index 0000000..ee41ac8
--- /dev/null
+++ b/xos/tosca/samples/vtn-external.yaml
@@ -0,0 +1,31 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup CORD-related services -- vOLT, vCPE, vBNG.
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    service_ONOS_VTN:
+      type: tosca.nodes.ONOSService
+      requirements:
+      properties:
+          kind: onos
+          view_url: /admin/onos/onosservice/$id$/
+          no_container: true
+          rest_hostname: ctl.smbaker-xos-neu.xos-pg0.clemson.cloudlab.us
+
+    VTN_ONOS_app:
+      type: tosca.nodes.ONOSVTNApp
+      requirements:
+          - onos_tenant:
+              node: service_ONOS_VTN
+              relationship: tosca.relationships.TenantOfService
+      properties:
+          dependencies: org.onosproject.drivers, org.onosproject.drivers.ovsdb, org.onosproject.lldpprovider, org.onosproject.openflow-base, org.onosproject.ovsdb-base, org.onosproject.dhcp, org.onosproject.openstackswitching, org.onosproject.cordvtn
+          rest_onos/v1/network/configuration/: { get_artifact: [ SELF, vtn_network_cfg_json, LOCAL_FILE ] }
+      artifacts:
+          vtn_network_cfg_json: /root/setup/vtn-network-cfg.json
+
+
diff --git a/xos/xos/exceptions.py b/xos/xos/exceptions.py
index 52badf8..9ab2605 100644
--- a/xos/xos/exceptions.py
+++ b/xos/xos/exceptions.py
@@ -62,3 +62,9 @@
                             "specific_error": why,
                             "fields": fields})
 
+class XOSServiceUnavailable(APIException):
+    status_code=503
+    def __init__(self, why="Service temporarily unavailable, try again later", fields={}):
+        APIException.__init__(self, {"error": "XOSServiceUnavailable",
+                            "specific_error": why,
+                            "fields": fields})
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index a6313cf..c8b0b07 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -224,8 +224,8 @@
     }
 }
 
-RESTAPI_HOSTNAME = getattr(config, "server_restapihostname", getattr(config, "server_hostname", socket.gethostname()))
-RESTAPI_PORT = int(getattr(config, "server_port", "8000"))
+RESTAPI_HOSTNAME = getattr(config, "server_restapi_hostname", getattr(config, "server_hostname", socket.gethostname()))
+RESTAPI_PORT = int(getattr(config, "server_restapi_port", getattr(config, "server_port", "8000")))
 
 BIGQUERY_TABLE = getattr(config, "bigquery_table", "demoevents")