Merged xos/master from open-cloud
diff --git a/containers/README.md b/containers/README.md
new file mode 100644
index 0000000..0bdc74b
--- /dev/null
+++ b/containers/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 and run the database container:
+
+```
+$ cd postgres; make build && make run
+```
+
+#### XOS Container
+
+To build and run the xos webserver container:
+
+```
+$ cd xos; make build && make run
+```
+
+You should now be able to access the login page by visiting
+`http://localhost:80` and log in using the default `paadmin@vicci.org` account
+with password `letmein`. It may be helpful to bootstrap xos with some sample
+data; deployment, controllers, sites, slices, etc. You can get started by
+loading tosca configuration for the opencloud demo dataset:
+
+```
+$ cd xos; make runtosca
+```
+
+Or you can create you own tosca configuraton file and customize the dataset
+however you want. You can all load your own tosca configuration by setting the
+`TOSCA_CONFIG_PATH` environment variable before executing the make command:
+
+```
+$ cd xos; TOSCA_CONFIG_PATH=/path/to/tosca/config.yaml make runtosca
+```
+
+#### 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. The XOS and synchronizer containers can run on separate
+hosts, but you must build the xos image on the host that you plan to run the
+synchronizer container. Assuming you have already built the xos container,
+executing the following will build and run the synchronizer container:
+
+```
+$ cd synchronizer; make build && make run
+```
+
+#### Solution Compose File ![](https://img.shields.io/badge/compose-beta-red.svg)
+
+[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 -f xos-compose.yml up -d
+```
diff --git a/containers/postgresql/Makefile b/containers/postgresql/Makefile
index 327f661..38f159c 100644
--- a/containers/postgresql/Makefile
+++ b/containers/postgresql/Makefile
@@ -1,21 +1,25 @@
+IMAGE_NAME:=xosproject/xos-postgress
+CONTAINER_NAME:=xos-db-postgress
+NO_DOCKER_CACHE?=false
+
 .PHONY: build
-build: ; docker build --rm -t postgres .
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
 
 .PHONY: run
-run: ; docker run -d -p 5432:5432 --name postgres-server postgres
+run: ; docker run -d -p 5432:5432 --name ${CONTAINER_NAME} ${IMAGE_NAME}
 
 .PHONY: stop
-stop: ; docker stop postgres-server
+stop: ; docker stop ${CONTAINER_NAME}
 
 .PHONY: rm
-rm: ; docker rm postgres-server
+rm: ; docker rm ${CONTAINER_NAME}
 
 .PHONE: rmi
-rmi: ; docker rmi postgres
+rmi: ; docker rmi ${IMAGE_NAME}
 
 .PHONY: backup
-backupvol: ; docker run --volumes-from postgres-server -v /backup:/backup postgres tar cvf /backup/backup-postgres.tar /var/lib/postgresql
+backupvol: ; docker run --volumes-from ${CONTAINER_NAME} -v /backup:/backup postgres tar cvf /backup/backup-postgres.tar /var/lib/postgresql
 
 .PHONY: restore
-restorevol: ; docker run --volumes-from postgres-server -v /backup:/backup postgres cd /var/lib/postgresql && tar xvf /backup/backup-postgres.tar
+restorevol: ; docker run --volumes-from ${CONTAINER_NAME} -v /backup:/backup postgres cd /var/lib/postgresql && tar xvf /backup/backup-postgres.tar
 
diff --git a/containers/synchronizer/Dockerfile b/containers/synchronizer/Dockerfile
index 44b058e..f9d79ae 100644
--- a/containers/synchronizer/Dockerfile
+++ b/containers/synchronizer/Dockerfile
@@ -1,4 +1,4 @@
-FROM       xos
+FROM       xosproject/xos
 
 # Install custom Ansible
 RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
diff --git a/containers/synchronizer/Makefile b/containers/synchronizer/Makefile
index 14520d9..8620438 100644
--- a/containers/synchronizer/Makefile
+++ b/containers/synchronizer/Makefile
@@ -1,10 +1,12 @@
-CONTAINER_NAME:=synchronizer-server
+IMAGE_NAME:=xosproject/xos-synchronizer-openstack
+CONTAINER_NAME:=xos-synchronizer
+NO_DOCKER_CACHE?=false
 
 .PHONY: build
-build: ; docker build --rm -t synchronizer .
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
 
 .PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} synchronizer
+run: ; docker run -d --name ${CONTAINER_NAME} ${IMAGE_NAME}
 
 .PHONY: stop
 stop: ; docker stop ${CONTAINER_NAME}
diff --git a/containers/xos-compose.yml b/containers/xos-compose.yml
new file mode 100644
index 0000000..27a3b5a
--- /dev/null
+++ b/containers/xos-compose.yml
@@ -0,0 +1,25 @@
+xos_db:
+    image: xosproject/xos-postgress
+    expose:
+        - "5432"
+
+xos_synchronizer_openstack:
+    image: xosproject/xos-synchronizer-openstack
+    labels:
+        org.xosproject.kind: synchronizer
+        org.xosproject.target: openstack
+
+# FUTURE
+#xos_swarm_synchronizer:
+#    image: xosproject/xos-swarm-synchronizer
+#    labels:
+#        org.xosproject.kind: synchronizer
+#        org.xosproject.target: swarm
+
+xos:
+    image: xosproject/xos
+    command: python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure --makemigrations
+    ports:
+        - "8000:8000"
+    links:
+        - xos_db
diff --git a/containers/xos/Dockerfile.templ b/containers/xos/Dockerfile.templ
new file mode 100644
index 0000000..e669692
--- /dev/null
+++ b/containers/xos/Dockerfile.templ
@@ -0,0 +1,88 @@
+FROM       ubuntu:14.04.3
+
+# XXX Workaround for docker bug:
+# https://github.com/docker/docker/issues/6345
+# Kernel 3.15 breaks docker, uss the line below as a workaround
+# until there is a fix
+RUN ln -s -f /bin/true /usr/bin/chfn
+# XXX End workaround
+
+# Install.
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
+    curl \
+    gcc \
+    geoip-database \
+    git \
+    graphviz \
+    graphviz-dev \
+    libgeoip1 \
+    libxslt1.1 \
+    libxslt1-dev \
+    libyaml-dev \
+    m4 \
+    pkg-config \
+    python-dev \
+    python-httplib2 \
+    python-pip \
+    python-psycopg2 \
+    python-pycurl \
+    python-setuptools \
+    tar \
+    wget \
+##### observer dependencies  
+    python-keystoneclient \
+    python-novaclient \
+    python-neutronclient \
+    python-glanceclient \
+    python-ceilometerclient
+
+RUN pip install -U \
+    django==1.7 \
+    django-bitfield \
+    django-crispy-forms \
+    django-encrypted-fields \
+    django_evolution \
+    django-extensions \
+    django-filter \
+    django-geoposition \
+    django-ipware \
+    django_rest_swagger \
+    django-suit \
+    django-timezones \
+    djangorestframework==2.4.4 \
+    dnslib \
+    google_api_python_client \
+    httplib2 \
+    httplib2.ca_certs_locater \
+    lxml \  
+    markdown \
+    netaddr \
+    python-dateutil \
+    python_gflags \
+    python-keyczar \
+    pygraphviz \
+    pytz \
+    pyyaml \
+    requests
+
+ADD http://code.jquery.com/jquery-1.9.1.min.js /usr/local/lib/python2.7/dist-packages/suit/static/suit/js/
+
+# Install XOS
+RUN git clone XOS_GIT_REPO -b XOS_GIT_BRANCH /tmp/xos && \
+    mv /tmp/xos/xos /opt/ && \
+    chmod +x /opt/xos/scripts/opencloud && \
+    /opt/xos/scripts/opencloud genkeys
+
+# install Tosca engine
+RUN bash /opt/xos/tosca/install_tosca.sh
+
+EXPOSE 8000
+
+# Set environment variables.
+ENV HOME /root
+
+# Define working directory.
+WORKDIR /root
+
+# Define default command.
+CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure
diff --git a/containers/xos/Makefile b/containers/xos/Makefile
index c71873e..367f0ec 100644
--- a/containers/xos/Makefile
+++ b/containers/xos/Makefile
@@ -1,11 +1,18 @@
 CONTAINER_NAME:=xos-server
+IMAGE_NAME:=xosproject/xos
 TOSCA_CONFIG_PATH:=/opt/xos/configurations/opencloud/opencloud.yaml
+XOS_GIT_REPO?=git://github.com/open-cloud/xos.git
+XOS_GIT_BRANCH?=master
+NO_DOCKER_CACHE?=false
 
 .PHONY: build
-build: ; docker build --rm -t xos . && ./initdb
+build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} . 
+
+.PHONY: custom
+custom: ; cat Dockerfile.templ | sed -e "s|XOS_GIT_REPO|${XOS_GIT_REPO}|g" -e "s|XOS_GIT_BRANCH|${XOS_GIT_BRANCH}|g" | docker build --no-cache=${NO_DOCKER_CACHE} -
 
 .PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 xos
+run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 ${IMAGE_NAME}
 
 .PHONY: runtosca
 runtosca: ; docker exec -it ${CONTAINER_NAME} /usr/bin/python /opt/xos/tosca/run.py padmin@vicci.org ${TOSCA_CONFIG_PATH}
diff --git a/containers/xos/initdb b/containers/xos/initdb
index bd020c5..41e0a9a 100755
--- a/containers/xos/initdb
+++ b/containers/xos/initdb
@@ -1,16 +1,17 @@
 #!/bin/bash
 
-IMAGE_NAME=xos
+IMAGE_NAME=xosproject/xos
+CONTAINER_NAME=xos_build_helper_$$
 DB_HOST=$(wget http://ipinfo.io/ip -qO -)
 
 # configure db host
-docker run -it $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_config
-CONTAINER_ID=$(docker ps -a | grep $IMAGE_NAME | head -1 |  awk '{print $1}')
-echo $CONTAINER_ID $IMAGE_NAME
-docker commit $CONTAINER_ID $IMAGE_NAME
+docker run -it --name=$CONTAINER_NAME $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_config
+docker commit $CONTAINER_NAME $IMAGE_NAME
+docker rm $CONTAINER_NAME
 
 # init db schema
-docker run -it $IMAGE_NAME /opt/xos/scripts/opencloud makemigrations
-CONTAINER_ID=$(docker ps -a | grep $IMAGE_NAME | head -1 | awk '{print $1}')
+docker run -it --name=$CONTAINER_NAME $IMAGE_NAME /opt/xos/scripts/opencloud makemigrations
 # run overrides the CMD specifed in the Dockerfile, so we re-set the CMD in the final commit"
-docker commit --change "CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_ID $IMAGE_NAME
+echo docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
+docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
+docker rm $CONTAINER_NAME
diff --git a/xos/configurations/cord/Dockerfile.cord b/xos/configurations/cord/Dockerfile.cord
index f7fdb65..8734eef 100644
--- a/xos/configurations/cord/Dockerfile.cord
+++ b/xos/configurations/cord/Dockerfile.cord
@@ -19,6 +19,7 @@
 ADD xos/observers/monitoring_channel/supervisor/monitoring_channel_observer.conf /etc/supervisor/conf.d/
 RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/vcpe/vcpe_observer_config
 RUN sed -i 's/proxy_ssh=True/proxy_ssh=False/' /opt/xos/observers/monitoring_channel/monitoring_channel_observer_config
+ADD xos/configurations/cord/virtualbng.json /root/setup/
 
 CMD /usr/bin/make -C /opt/xos/configurations/cord -f Makefile.inside; /bin/bash
 
diff --git a/xos/configurations/cord/Makefile b/xos/configurations/cord/Makefile
index 86b4ca1..701e92f 100644
--- a/xos/configurations/cord/Makefile
+++ b/xos/configurations/cord/Makefile
@@ -2,7 +2,7 @@
 RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
 LAST_CONTAINER=$(shell sudo docker ps -l -q)
 
-cord: common_cloudlab ceilometer_dashboard
+cord: common_cloudlab ceilometer_dashboard virtualbng_json
 	echo "# Autogenerated -- do not edit" > Dockerfile
 	cat ../common/Dockerfile.common Dockerfile.cord >> Dockerfile
 	cd ../../..; sudo docker build -t xos -f xos/configurations/cord/Dockerfile .
@@ -16,7 +16,10 @@
 	#NOTE: The below dashboard install scripts assume 
 	#clouldlab openstack environment created using "OpenStack" profile
 	bash install_xos_ceilometer_dashboard.sh
-	bash install_ceilometer_vcpe_notification_listener.sh
+	bash install_ceilometer_patch.sh
+
+virtualbng_json:
+	bash make-virtualbng-json.sh
 
 stop:
 	sudo docker stop $(RUNNING_CONTAINER)
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 9929a84..02137ec 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -97,20 +97,9 @@
                 }
               }
             }
-          config_virtualbng.json: >
-            {
-                "localPublicIpPrefixes" : [
-                    "10.254.0.128/25"
-                ],
-                "nextHopIpAddress" : "10.254.0.1",
-                "publicFacingMac" : "00:00:00:00:00:66",
-                "xosIpAddress" : "10.11.10.1",
-                "xosRestPort" : "9999",
-                "hosts" : {
-                    "cp-1.devel.xos-pg0.clemson.cloudlab.us" : "of:0000000000000001/1",
-                    "cp-2.devel.xos-pg0.clemson.cloudlab.us" : "of:0000000000000001/1"
-                }
-            }
+          config_virtualbng.json: { get_artifact: [ SELF, virtualbng_json, LOCAL_FILE] }
+      artifacts:
+          virtualbng_json: /root/setup/virtualbng.json
 
     service_ONOS_vOLT:
       type: tosca.nodes.ONOSService
@@ -133,7 +122,7 @@
               node: service_volt
               relationship: tosca.relationships.UsedByService
       properties:
-          dependencies: org.onosproject.openflow, org.onosproject.olt
+          dependencies: org.onosproject.openflow-base, org.onosproject.olt
           config_network-cfg.json: >
             {
               "devices" : {
diff --git a/xos/configurations/cord/dataplane/dataplane.yaml b/xos/configurations/cord/dataplane/dataplane.yaml
index f43e4d7..489609a 100644
--- a/xos/configurations/cord/dataplane/dataplane.yaml
+++ b/xos/configurations/cord/dataplane/dataplane.yaml
@@ -158,6 +158,14 @@
     shell: ifconfig br-lan inet6 del {{ ipv6.stdout }}
     when: ipv6.stdout != ""
 
+  - name: Check if veth1 has an IPv6 address
+    shell: ip addr show veth1|grep inet6|awk '{print $2}'
+    register: ipv6
+
+  - name: Remove veth1 IPv6 address if present
+    shell: ifconfig veth1 inet6 del {{ ipv6.stdout }}
+    when: ipv6.stdout != ""
+
   - name: Run the datapath
     command: /usr/local/bin/ofdatapath -i veth1,br-lan punix:/tmp/s1 -d 000000000001 --no-slicing -D -P
       creates=/usr/local/var/run/ofdatapath.pid
diff --git a/xos/configurations/cord/make-virtualbng-json.sh b/xos/configurations/cord/make-virtualbng-json.sh
new file mode 100644
index 0000000..0e1d350
--- /dev/null
+++ b/xos/configurations/cord/make-virtualbng-json.sh
@@ -0,0 +1,39 @@
+#FN=/opt/xos/configurations/common/cloudlab-nodes.yaml
+FN=virtualbng.json
+
+rm -f $FN
+
+cat >> $FN <<EOF
+{
+    "localPublicIpPrefixes" : [
+        "10.254.0.128/25"
+    ],
+    "nextHopIpAddress" : "10.254.0.1",
+    "publicFacingMac" : "00:00:00:00:00:66",
+    "xosIpAddress" : "10.11.10.1",
+    "xosRestPort" : "9999",
+    "hosts" : {
+EOF
+
+NODES=$( sudo bash -c "source /root/setup/admin-openrc.sh ; nova hypervisor-list" |grep cloudlab|awk '{print $4}' )
+
+NODECOUNT=0
+for NODE in $NODES; do
+    ((NODECOUNT++))
+done
+
+I=0
+for NODE in $NODES; do
+    echo $NODE
+    ((I++))
+    if [[ "$I" -lt "$NODECOUNT" ]]; then
+        echo "      \"$NODE\" : \"of:0000000000000001/1\"," >> $FN
+    else
+        echo "      \"$NODE\" : \"of:0000000000000001/1\"" >> $FN
+    fi
+done
+
+cat >> $FN <<EOF
+    }
+}
+EOF
diff --git a/xos/configurations/devel/Dockerfile.devel b/xos/configurations/devel/Dockerfile.devel
index 115b731..60c0143 100644
--- a/xos/configurations/devel/Dockerfile.devel
+++ b/xos/configurations/devel/Dockerfile.devel
@@ -4,6 +4,7 @@
 ADD xos/configurations/common/flat_net_name /root/setup/
 ADD xos/configurations/common/cloudlab-nodes.yaml /opt/xos/configurations/commmon/
 ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
+ADD xos/configurations/common/id_rsa /opt/xos/observers/helloworldservice_complete/helloworldservice_private_key
 ADD xos/configurations/common/node_key.pub /root/setup/node_key.pub
 ADD xos/configurations/common/node_key /root/setup/node_key
 ADD xos/configurations/common/ceilometer_url /root/setup/ceilometer_url
diff --git a/xos/helloworldservice_complete/__init__.py b/xos/helloworldservice_complete/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/helloworldservice_complete/__init__.py
diff --git a/xos/helloworldservice_complete/admin.py b/xos/helloworldservice_complete/admin.py
new file mode 100644
index 0000000..9a3e4f7
--- /dev/null
+++ b/xos/helloworldservice_complete/admin.py
@@ -0,0 +1,141 @@
+
+from core.admin import ReadOnlyAwareAdmin, SliceInline
+from core.middleware import get_request
+from core.models import User
+from django import forms
+from django.contrib import admin
+from helloworldservice_complete.models import HelloWorldServiceComplete, HelloWorldTenantComplete, HELLO_WORLD_KIND
+
+# The class to provide an admin interface on the web for the service.
+# We do only configuration here and don't change any logic because the logic
+# is taken care of for us by ReadOnlyAwareAdmin
+class HelloWorldServiceCompleteAdmin(ReadOnlyAwareAdmin):
+    # We must set the model so that the admin knows what fields to use
+    model = HelloWorldServiceComplete
+    verbose_name = "Hello World Service"
+    verbose_name_plural = "Hello World Services"
+
+    # Setting list_display creates columns on the admin page, each value here
+    # is a column, the column is populated for every instance of the model.
+    list_display = ("backend_status_icon", "name", "enabled")
+
+    # Used to indicate which values in the columns of the admin form are links.
+    list_display_links = ('backend_status_icon', 'name', )
+
+    # Denotes the sections of the form, the fields in the section, and the
+    # CSS classes used to style them. We represent this as a set of tuples, each
+    # tuple as a name (or None) and a set of fields and classes.
+    # Here the first section does not have a name so we use none. That first
+    # section has several fields indicated in the 'fields' attribute, and styled
+    # by the classes indicated in the 'classes' attribute. The classes given
+    # here are important for rendering the tabs on the form. To give the tabs
+    # we must assign the classes suit-tab and suit-tab-<name> where
+    # where <name> will be used later.
+    fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled',
+                                    'versionNumber', 'description', "view_url"],
+                         'classes':['suit-tab suit-tab-general']})]
+
+    # Denotes the fields that are readonly and cannot be changed.
+    readonly_fields = ('backend_status_text', )
+
+    # Inlines are used to denote other models that can be edited on the same
+    # form as this one. In this case the service form also allows changes
+    # to slices.
+    inlines = [SliceInline]
+
+    extracontext_registered_admins = True
+
+    # Denotes the fields that can be changed by an admin but not be all users
+    user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+    # Associates fieldsets from this form and from the inlines.
+    # The format here are tuples, of (<name>, tab title). <name> comes from the
+    # <name> in the fieldsets.
+    suit_form_tabs = (('general', 'Hello World Service Details'),
+                      ('administration', 'Tenants'),
+                      ('slices', 'Slices'),)
+
+    # Used to include a template for a tab. Here we include the
+    # helloworldserviceadmin template in the top position for the administration
+    # tab.
+    suit_form_includes = (('helloworldserviceadmin.html',
+                           'top',
+                           'administration'),)
+
+    # Used to get the objects for this model that are associated with the
+    # requesting user.
+    def queryset(self, request):
+        return HelloWorldServiceComplete.get_service_objects_by_user(request.user)
+
+# Class to represent the form to add and edit tenants.
+# We need to define this instead of just using an admin like we did for the
+# service because tenants vary more than services and there isn't a common form.
+# This allows us to change the python behavior for the admin form to save extra
+# fields and control defaults.
+class HelloWorldTenantCompleteForm(forms.ModelForm):
+    # Defines a field for the creator of this service. It is a dropdown which
+    # is populated with all of the users.
+    creator = forms.ModelChoiceField(queryset=User.objects.all())
+    # Defines a text field for the display message, it is not required.
+    display_message = forms.CharField(required=False)
+
+    def __init__(self, *args, **kwargs):
+        super(HelloWorldTenantCompleteForm, self).__init__(*args, **kwargs)
+        # Set the kind field to readonly
+        self.fields['kind'].widget.attrs['readonly'] = True
+        # Define the logic for obtaining the objects for the provider_service
+        # dropdown of the tenant form.
+        self.fields[
+            'provider_service'].queryset = HelloWorldServiceComplete.get_service_objects().all()
+        # Set the initial kind to HELLO_WORLD_KIND for this tenant.
+        self.fields['kind'].initial = HELLO_WORLD_KIND
+        # If there is an instance of this model then we can set the initial
+        # form values to the existing values.
+        if self.instance:
+            self.fields['creator'].initial = self.instance.creator
+            self.fields[
+                'display_message'].initial = self.instance.display_message
+
+        # If there is not an instance then we need to set initial values.
+        if (not self.instance) or (not self.instance.pk):
+            self.fields['creator'].initial = get_request().user
+            if HelloWorldServiceComplete.get_service_objects().exists():
+                self.fields["provider_service"].initial = HelloWorldServiceComplete.get_service_objects().all()[0]
+
+    # This function describes what happens when the save button is pressed on
+    # the tenant form. In this case we set the values for the instance that were
+    # entered.
+    def save(self, commit=True):
+        self.instance.creator = self.cleaned_data.get("creator")
+        self.instance.display_message = self.cleaned_data.get(
+            "display_message")
+        return super(HelloWorldTenantCompleteForm, self).save(commit=commit)
+
+    class Meta:
+        model = HelloWorldTenantComplete
+
+# Define the admin form for the tenant. This uses a similar structure as the
+# service but uses HelloWorldTenantCompleteForm to change the python behavior.
+
+
+class HelloWorldTenantCompleteAdmin(ReadOnlyAwareAdmin):
+    verbose_name = "Hello World Tenant"
+    verbose_name_plural = "Hello World Tenants"
+    list_display = ('id', 'backend_status_icon', 'instance', 'display_message')
+    list_display_links = ('backend_status_icon', 'instance', 'display_message',
+                          'id')
+    fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
+                                    'provider_service', 'instance', 'creator',
+                                    'display_message'],
+                         'classes': ['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text', 'instance',)
+    form = HelloWorldTenantCompleteForm
+
+    suit_form_tabs = (('general', 'Details'),)
+
+    def queryset(self, request):
+        return HelloWorldTenantComplete.get_tenant_objects_by_user(request.user)
+
+# Associate the admin forms with the models.
+admin.site.register(HelloWorldServiceComplete, HelloWorldServiceCompleteAdmin)
+admin.site.register(HelloWorldTenantComplete, HelloWorldTenantCompleteAdmin)
diff --git a/xos/helloworldservice_complete/models.py b/xos/helloworldservice_complete/models.py
new file mode 100644
index 0000000..8a4ce59
--- /dev/null
+++ b/xos/helloworldservice_complete/models.py
@@ -0,0 +1,113 @@
+from core.models import Service, TenantWithContainer
+from django.db import transaction
+
+HELLO_WORLD_KIND = "helloworldservice_complete"
+
+# The class to represent the service. Most of the service logic is given for us
+# in the Service class but, we have some configuration that is specific for
+# this example.
+class HelloWorldServiceComplete(Service):
+    KIND = HELLO_WORLD_KIND
+
+    class Meta:
+        # When the proxy field is set to True the model is represented as
+        # it's superclass in the database, but we can still change the python
+        # behavior. In this case HelloWorldServiceComplete is a Service in the
+        # database.
+        proxy = True
+        # The name used to find this service, all directories are named this
+        app_label = "helloworldservice_complete"
+        verbose_name = "Hello World Service"
+
+# This is the class to represent the tenant. Most of the logic is given to use
+# in TenantWithContainer, however there is some configuration and logic that
+# we need to define for this example.
+class HelloWorldTenantComplete(TenantWithContainer):
+
+    class Meta:
+        # Same as a above, HelloWorldTenantComplete is represented as a
+        # TenantWithContainer, but we change the python behavior.
+        proxy = True
+        verbose_name = "Hello World Tenant"
+
+    # The kind of the service is used on forms to differentiate this service
+    # from the other services.
+    KIND = HELLO_WORLD_KIND
+
+    # Ansible requires that the sync_attributes field contain nat_ip and nat_mac
+    # these will be used to determine where to SSH to for ansible.
+    # Getters must be defined for every attribute specified here.
+    sync_attributes = ("nat_ip", "nat_mac",)
+
+    # default_attributes is used cleanly indicate what the default values for
+    # the fields are.
+    default_attributes = {'display_message': 'Hello World!'}
+
+    def __init__(self, *args, **kwargs):
+        helloworld_services = HelloWorldServiceComplete.get_service_objects().all()
+        # When the tenant is created the default service in the form is set
+        # to be the first created HelloWorldServiceComplete
+        if helloworld_services:
+            self._meta.get_field(
+                "provider_service").default = helloworld_services[0].id
+        super(HelloWorldTenantComplete, self).__init__(*args, **kwargs)
+
+    def save(self, *args, **kwargs):
+        super(HelloWorldTenantComplete, self).save(*args, **kwargs)
+        # This call needs to happen so that an instance is created for this
+        # tenant is created in the slice. One instance is created per tenant.
+        model_policy_helloworld_tenant(self.pk)
+
+    def delete(self, *args, **kwargs):
+        # Delete the instance that was created for this tenant
+        self.cleanup_container()
+        super(HelloWorldTenantComplete, self).delete(*args, **kwargs)
+
+    # Getter for the message that will appear on the webpage
+    # By default it is "Hello World!"
+    @property
+    def display_message(self):
+        return self.get_attribute(
+            "display_message",
+            self.default_attributes['display_message'])
+
+    # Setter for the message that will appear on the webpage
+    @display_message.setter
+    def display_message(self, value):
+        self.set_attribute("display_message", value)
+
+    @property
+    def addresses(self):
+        if (not self.id) or (not self.instance):
+            return {}
+
+        addresses = {}
+        # The ports field refers to networks for the instance.
+        # This loop stores the details for the NAT network that will be
+        # necessary for ansible.
+        for ns in self.instance.ports.all():
+            if "nat" in ns.network.name.lower():
+                addresses["nat"] = (ns.ip, ns.mac)
+        return addresses
+
+    # This getter is necessary because nat_ip is a sync_attribute
+    @property
+    def nat_ip(self):
+        return self.addresses.get("nat", (None, None))[0]
+
+    # This getter is necessary because nat_mac is a sync_attribute
+    @property
+    def nat_mac(self):
+        return self.addresses.get("nat", (None, None))[1]
+
+
+def model_policy_helloworld_tenant(pk):
+    # This section of code is atomic to prevent race conditions
+    with transaction.atomic():
+        # We find all of the tenants that are waiting to update
+        tenant = HelloWorldTenantComplete.objects.select_for_update().filter(pk=pk)
+        if not tenant:
+            return
+        # Since this code is atomic it is safe to always use the first tenant
+        tenant = tenant[0]
+        tenant.manage_container()
diff --git a/xos/helloworldservice_complete/templates/helloworldserviceadmin.html b/xos/helloworldservice_complete/templates/helloworldserviceadmin.html
new file mode 100644
index 0000000..ba418ee
--- /dev/null
+++ b/xos/helloworldservice_complete/templates/helloworldserviceadmin.html
@@ -0,0 +1,10 @@
+<!-- Template used to for the button leading to the HelloWorldTenantComplete form. -->
+<div class = "left-nav">
+  <ul>
+    <li>
+      <a href="/admin/helloworldservice_complete/helloworldtenantcomplete/">
+        Hello World Tenants
+      </a>
+    </li>
+  </ul>
+</div>
diff --git a/xos/manage.py b/xos/manage.py
index 0ddd014..5d09794 100644
--- a/xos/manage.py
+++ b/xos/manage.py
@@ -7,6 +7,10 @@
 
     from django.core.management import execute_from_command_line
 
+    if "--makemigrations" in sys.argv:
+        os.system("/opt/xos/scripts/opencloud makemigrations")
+        sys.argv.remove("--makemigrations")
+
     if "--nomodelpolicy" in sys.argv:
         import model_policy
         model_policy.EnableModelPolicy(False)
diff --git a/xos/observers/helloworldservice_complete/helloworldservice-observer.py b/xos/observers/helloworldservice_complete/helloworldservice-observer.py
new file mode 100755
index 0000000..75dcc46
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/helloworldservice-observer.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+# Runs the standard XOS observer
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(
+    os.path.realpath(__file__)), "../..")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-observer")
+mod.main()
diff --git a/xos/observers/helloworldservice_complete/helloworldservice_config b/xos/observers/helloworldservice_complete/helloworldservice_config
new file mode 100644
index 0000000..716e3a0
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/helloworldservice_config
@@ -0,0 +1,36 @@
+# Required by XOS
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+# Required by XOS
+[api]
+nova_enabled=True
+
+# Sets options for the observer
+[observer]
+# Optional name
+name=helloworldservice
+# This is the location to the dependency graph you generate
+dependency_graph=/opt/xos/observers/helloworldservice_complete/model-deps
+# The location of your SyncSteps
+steps_dir=/opt/xos/observers/helloworldservice_complete/steps
+# A temporary directory that will be used by ansible
+sys_dir=/opt/xos/observers/helloworldservice_complete/sys
+# Location of the file to save logging messages to the backend log is often used
+logfile=/var/log/xos_backend.log
+# If this option is true, then nothing will change, we simply pretend to run
+pretend=False
+# If this is False then XOS will use an exponential backoff when the observer
+# fails, since we will be waiting for an instance, we don't want this.
+backoff_disabled=True
+# We want the output from ansible to be logged
+save_ansible_output=True
+# This determines how we SSH to a client, if this is set to True then we try
+# to ssh using the instance name as a proxy, if this is disabled we ssh using
+# the NAT IP of the instance. On CloudLab the first option will fail so we must
+# set this to False
+proxy_ssh=False
diff --git a/xos/observers/helloworldservice_complete/model-deps b/xos/observers/helloworldservice_complete/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/observers/helloworldservice_complete/run.sh b/xos/observers/helloworldservice_complete/run.sh
new file mode 100755
index 0000000..6bce079
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/run.sh
@@ -0,0 +1,3 @@
+# Runs the XOS observer using helloworldservice_config
+export XOS_DIR=/opt/xos
+python helloworldservice-observer.py  -C $XOS_DIR/observers/helloworldservice_complete/helloworldservice_config
diff --git a/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py
new file mode 100644
index 0000000..a4b5435
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.py
@@ -0,0 +1,48 @@
+import os
+import sys
+from django.db.models import Q, F
+from helloworldservice_complete.models import HelloWorldServiceComplete, HelloWorldTenantComplete
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+# Class to define how we sync a tenant. Using SyncInstanceUsingAnsible we
+# indicate where the find the YAML for ansible, where to find the SSH key,
+# and the logic for determining what tenant needs updating, what additional
+# attributes are needed, and how to delete an instance.
+class SyncHelloWorldTenantComplete(SyncInstanceUsingAnsible):
+    # Indicates the position in the data model, this will run when XOS needs to
+    # enact a HelloWorldTenantComplete
+    provides = [HelloWorldTenantComplete]
+    # The actual model being enacted, usually the same as provides.
+    observes = HelloWorldTenantComplete
+    # Number of milliseconds between interruptions of the observer
+    requested_interval = 0
+    # The ansible template to run
+    template_name = "sync_helloworldtenant.yaml"
+    # The location of the SSH private key to use when ansible connects to
+    # instances.
+    service_key_name = "/opt/xos/observers/helloworldservice_complete/helloworldservice_private_key"
+
+    def __init__(self, *args, **kwargs):
+        super(SyncHelloWorldTenantComplete, self).__init__(*args, **kwargs)
+
+    # Defines the logic for determining what HelloWorldTenantCompletes need to be
+    # enacted.
+    def fetch_pending(self, deleted):
+        # If the update is not a deletion, then we get all of the instnaces that
+        # have been updated or have not been enacted.
+        if (not deleted):
+            objs = HelloWorldTenantComplete.get_tenant_objects().filter(
+                Q(enacted__lt=F('updated')) | Q(enacted=None), Q(lazy_blocked=False))
+        else:
+            # If this is a deletion we get all of the deleted tenants..
+            objs = HelloWorldTenantComplete.get_deleted_tenant_objects()
+
+        return objs
+
+    # 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 {"display_message": o.display_message}
diff --git a/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml
new file mode 100644
index 0000000..719c75f
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/steps/sync_helloworldtenant.yaml
@@ -0,0 +1,18 @@
+---
+- hosts: {{ instance_name }}
+  gather_facts: False
+  connection: ssh
+  user: ubuntu
+  sudo: yes
+  tasks:
+  - name: install apache
+    apt: name=apache2 state=present update_cache=yes
+
+  - name: write message
+    shell: echo "{{ display_message }}" > /var/www/html/index.html
+
+  - name: stop apache
+    service: name=apache2 state=stopped
+
+  - name: start apache
+    service: name=apache2 state=started
diff --git a/xos/observers/helloworldservice_complete/stop.sh b/xos/observers/helloworldservice_complete/stop.sh
new file mode 100755
index 0000000..76e68d9
--- /dev/null
+++ b/xos/observers/helloworldservice_complete/stop.sh
@@ -0,0 +1,2 @@
+# Kill the observer
+pkill -9 -f helloworldservice-observer.py
diff --git a/xos/observers/onos/steps/sync_onosapp.py b/xos/observers/onos/steps/sync_onosapp.py
index 8c97391..9b32298 100644
--- a/xos/observers/onos/steps/sync_onosapp.py
+++ b/xos/observers/onos/steps/sync_onosapp.py
@@ -75,6 +75,7 @@
 
     def write_configs(self, o):
         o.config_fns = []
+        o.rest_configs = []
         o.files_dir = self.get_files_dir(o)
 
         if not os.path.exists(o.files_dir):
@@ -85,6 +86,16 @@
                 fn = attr.name[7:] # .replace("_json",".json")
                 o.config_fns.append(fn)
                 file(os.path.join(o.files_dir, fn),"w").write(attr.value)
+            if attr.name.startswith("rest_"):
+                fn = attr.name[5:].replace("/","_")
+                endpoint = attr.name[5:]
+                # Ansible goes out of it's way to make our life difficult. If
+                # 'lookup' sees a file that it thinks contains json, then it'll
+                # insist on parsing and return a json object. We just want
+                # a string, so prepend a space and then strip the space off
+                # later.
+                file(os.path.join(o.files_dir, fn),"w").write(" " +attr.value)
+                o.rest_configs.append( {"endpoint": endpoint, "fn": fn} )
 
     def prepare_record(self, o):
         self.write_configs(o)
@@ -95,6 +106,7 @@
         fields["appname"] = o.name
         fields["nat_ip"] = self.get_instance(o).get_ssh_ip()
         fields["config_fns"] = o.config_fns
+        fields["rest_configs"] = o.rest_configs
         fields["dependencies"] = [x.strip() for x in o.dependencies.split(",")]
         fields["ONOS_container"] = "ONOS"
         return fields
diff --git a/xos/observers/onos/steps/sync_onosapp.yaml b/xos/observers/onos/steps/sync_onosapp.yaml
index ad3718c..9ee2513 100644
--- a/xos/observers/onos/steps/sync_onosapp.yaml
+++ b/xos/observers/onos/steps/sync_onosapp.yaml
@@ -7,6 +7,13 @@
   vars:
     appname: {{ appname }}
     dependencies: {{ dependencies }}
+{% 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 %}
 
   tasks:
 
@@ -38,6 +45,18 @@
         {% endfor %}
 {% endif %}
 
+{% if rest_configs %}
+  - name: Add ONOS configuration values
+    uri:
+      url: http://localhost:8181/{{ '{{' }} 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 %}
+
   # Don't know how to check for this condition, just wait
   - name: Wait for ONOS to install the apps
     wait_for: timeout=15
diff --git a/xos/scripts/opencloud b/xos/scripts/opencloud
index 189a673..6b3e737 100755
--- a/xos/scripts/opencloud
+++ b/xos/scripts/opencloud
@@ -144,6 +144,7 @@
     python ./manage.py makemigrations syndicate_storage
     python ./manage.py makemigrations cord
     python ./manage.py makemigrations ceilometer
+    python ./manage.py makemigrations helloworldservice_complete
     python ./manage.py makemigrations onos
     #python ./manage.py makemigrations servcomp
 }
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 4f726fc..d4e96ce 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -143,6 +143,9 @@
             config_network-cfg.json:
                 type: string
                 required: false
+            rest_onos/v1/network/configuration/:
+                type: string
+                required: false
 
     tosca.nodes.VCPEService:
         description: >
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 246c922..0c20211 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -165,6 +165,9 @@
             config_network-cfg.json:
                 type: string
                 required: false
+            rest_onos/v1/network/configuration/:
+                type: string
+                required: false
 
     tosca.nodes.VCPEService:
         description: >
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
index 648bb09..7ed47d7 100644
--- a/xos/tosca/resources/onosapp.py
+++ b/xos/tosca/resources/onosapp.py
@@ -57,6 +57,8 @@
             v = d.value
             if k.startswith("config_"):
                 self.set_tenant_attr(obj, k, v)
+            elif k.startswith("rest_"):
+                self.set_tenant_attr(obj, k, v)
 
     def can_delete(self, obj):
         return super(XOSONOSApp, self).can_delete(obj)
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 5942d2c..e276954 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -1,15 +1,36 @@
 from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
 from django import VERSION as DJANGO_VERSION
 import socket
+import os
+from urlparse import urlparse
 
 # Django settings for XOS.
 from config import Config
+from config import set_override
 config = Config()
 
+# Override the config from the environment. This is used leverage the LINK
+# capability of docker. It would be far better to use DNS and that can be
+# done in environments like kubernetes. Look for environment variables that
+# match the link pattern and set the appropriate overeides. It is expected
+# that the set of overrides will be expanded with need
+def overrideDbSettings(v):
+    parsed = urlparse(v)
+    config.db_host = parsed.hostname
+    config.db_port = parsed.port
+
+env_to_config_dict = {
+    "XOS_DB_PORT" : overrideDbSettings
+}
+
+for key, ofunc in env_to_config_dict.items():
+    if key in os.environ:
+        ofunc(os.environ[key])
+
 GEOIP_PATH = "/usr/share/GeoIP"
 XOS_DIR = "/opt/xos"
- 
-DEBUG = False 
+
+DEBUG = False
 TEMPLATE_DEBUG = DEBUG
 
 ADMINS = (
@@ -29,7 +50,7 @@
         'USER': config.db_user,
         'PASSWORD': config.db_password,
         'HOST': config.db_host,                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
-        'PORT': '',                      # Set to empty string for default.
+        'PORT': config.db_port,                      # Set to empty string for default.
     }
 }
 
@@ -132,7 +153,7 @@
     # Always use forward slashes, even on Windows.
     # Don't forget to use absolute paths, not relative paths.
     XOS_DIR + "/templates",
-    XOS_DIR + "/core/xoslib/templates",    
+    XOS_DIR + "/core/xoslib/templates",
 )
 
 INSTALLED_APPS = (
@@ -154,6 +175,7 @@
     'core',
     'hpc',
     'cord',
+    'helloworldservice_complete',
     'services.onos',
     'ceilometer',
     'requestrouter',
@@ -169,7 +191,7 @@
     INSTALLED_APPS = list(INSTALLED_APPS)
     INSTALLED_APPS[INSTALLED_APPS.index('django.contrib.admin')] = 'django.contrib.admin.apps.SimpleAdminConfig'
 
-# Added for django-suit form 
+# Added for django-suit form
 TEMPLATE_CONTEXT_PROCESSORS = TCP + (
     'django.core.context_processors.request',
     'core.context_processors.xos',
@@ -221,4 +243,3 @@
 
 # prevents warnings on django 1.7
 TEST_RUNNER = 'django.test.runner.DiscoverRunner'
-