Merge branch 'master' of https://github.com/open-cloud/xos into AddHelloWorldService
diff --git a/xos/configurations/common/Dockerfile.common b/xos/configurations/common/Dockerfile.common
index 1f42c34..5329142 100644
--- a/xos/configurations/common/Dockerfile.common
+++ b/xos/configurations/common/Dockerfile.common
@@ -4,8 +4,8 @@
 # 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 
+# until there is a fix
+RUN ln -s -f /bin/true /usr/bin/chfn
 # XXX End workaround
 
 # Install.
@@ -23,12 +23,12 @@
     python-httplib2 \
     geoip-database \
     libgeoip1 \
-    wget \ 
-    curl \ 
+    wget \
+    curl \
     python-dev \
-    libyaml-dev \ 
-    pkg-config \ 
-    python-pycurl 
+    libyaml-dev \
+    pkg-config \
+    python-pycurl
 
 RUN pip install django==1.7
 RUN pip install djangorestframework==2.4.4
@@ -64,6 +64,7 @@
 RUN easy_install python_gflags
 RUN easy_install --upgrade httplib2
 RUN easy_install google_api_python_client
+RUN easy_install httplib2.ca_certs_locater
 
 # Install custom Ansible
 RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-crypto
@@ -97,7 +98,7 @@
 RUN DEBIAN_FRONTEND=noninteractive apt-get install -y supervisor
 ADD observer.conf /etc/supervisor/conf.d/
 
-# Get XOS 
+# Get XOS
 ADD xos /opt/xos
 
 # Initscript is broken in Ubuntu
diff --git a/xos/configurations/common/Makefile.cloudlab b/xos/configurations/common/Makefile.cloudlab
index ddf9daf..929bc14 100644
--- a/xos/configurations/common/Makefile.cloudlab
+++ b/xos/configurations/common/Makefile.cloudlab
@@ -1,4 +1,7 @@
-all: admin-openrc flat_name nodes_yaml public_key install_docker
+all: prereqs admin-openrc flat_name nodes_yaml public_key 
+
+prereqs:
+	make -f Makefile.prereqs
 
 admin-openrc:
 	sudo cat /root/setup/admin-openrc.sh > admin-openrc.sh
@@ -15,6 +18,3 @@
 ~/.ssh/id_rsa.pub:
 	cat /dev/zero | ssh-keygen -q -N ""
 
-install_docker:
-	which docker > /dev/null || wget -qO- https://get.docker.com/ | sh
-	sudo usermod -aG docker $(shell whoami)
diff --git a/xos/configurations/common/Makefile.prereqs b/xos/configurations/common/Makefile.prereqs
new file mode 100644
index 0000000..0325224
--- /dev/null
+++ b/xos/configurations/common/Makefile.prereqs
@@ -0,0 +1,34 @@
+UBUNTU:=$(shell which apt > /dev/null 2>&1; echo $$?)
+
+ifeq ($(UBUNTU),0)
+
+# ******************* apt-based distros ***************************
+prereqs: /usr/bin/http docker
+
+/usr/bin/http: 
+	sudo apt-get -y install httpie
+
+docker:
+	which docker > /dev/null || wget -qO- https://get.docker.com/ | sh
+	sudo usermod -aG docker $(shell whoami)
+
+else
+
+# ****************** RPM-based distros ******************
+
+# (untested / work-in-progress)
+
+prereqs: /usr/bin/pip /usr/bin/http docker
+
+/usr/bin/pip:
+	curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
+	sudo python ./get-pip.py     
+
+docker:
+	which docker > /dev/null || wget -qO- https://get.docker.com/ | sh
+	sudo usermod -aG docker $(shell whoami)
+
+/usr/bin/http:
+	sudo pip install httpie
+
+endif
diff --git a/xos/configurations/devel/Makefile b/xos/configurations/devel/Makefile
index e7bf0f4..90dabcc 100644
--- a/xos/configurations/devel/Makefile
+++ b/xos/configurations/devel/Makefile
@@ -1,5 +1,5 @@
 MYIP:=$(shell hostname -i)
-EXISTING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
+RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
 LAST_CONTAINER=$(shell sudo docker ps -l -q)
 
 test: common_cloudlab
@@ -13,10 +13,10 @@
 	make -C ../common -f Makefile.cloudlab
 
 stop:
-	sudo docker stop $(EXISTING_CONTAINER)
+	sudo docker stop $(RUNNING_CONTAINER)
 
 showlogs:
 	sudo docker logs $(LAST_CONTAINER)
 
 enter:
-	sudo docker exec -t -i $(EXISTING_CONTAINER) bash
+	sudo docker exec -t -i $(RUNNING_CONTAINER) bash
diff --git a/xos/configurations/frontend/Makefile b/xos/configurations/frontend/Makefile
index e44062d..d03e1bc 100644
--- a/xos/configurations/frontend/Makefile
+++ b/xos/configurations/frontend/Makefile
@@ -18,4 +18,4 @@
 	sudo docker logs $(LAST_CONTAINER)
 
 enter:
-	sudo docker exec -t -i $(EXISTING_CONTAINER) bash
\ No newline at end of file
+	sudo docker exec -t -i $(RUNNING_CONTAINER) bash
diff --git a/xos/configurations/kilo-install/Dockerfile b/xos/configurations/kilo-install/Dockerfile
new file mode 100644
index 0000000..f0ced90
--- /dev/null
+++ b/xos/configurations/kilo-install/Dockerfile
@@ -0,0 +1,154 @@
+# Autogenerated -- do not edit
+FROM       ubuntu:14.04.2
+MAINTAINER Andy Bavier <acb@cs.princeton.edu>
+
+# 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 apt-get update && apt-get install -y \
+    git \
+    postgresql \
+    python-psycopg2 \
+    graphviz \
+    graphviz-dev \
+    libxslt1.1 \
+    libxslt1-dev \
+    python-pip \
+    tar \
+    gcc \
+    python-httplib2 \
+    geoip-database \
+    libgeoip1 \
+    wget \ 
+    curl \ 
+    python-dev \
+    libyaml-dev \ 
+    pkg-config \ 
+    python-pycurl 
+
+RUN pip install django==1.7
+RUN pip install djangorestframework==2.4.4
+RUN pip install markdown  # Markdown support for the browseable API.
+RUN pip install pyyaml    # YAML content-type support.
+RUN pip install django-filter  # Filtering support
+RUN pip install lxml  # XML manipulation library
+RUN pip install netaddr # IP Addr library
+RUN pip install pytz
+RUN pip install django-timezones
+RUN pip install requests
+RUN pip install django-crispy-forms
+RUN pip install django-geoposition
+RUN pip install django-extensions
+RUN pip install django-suit
+RUN pip install django-bitfield
+RUN pip install django-ipware
+RUN pip install django-encrypted-fields
+RUN pip install python-keyczar
+RUN pip install pygraphviz
+RUN pip install dnslib
+
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-keystoneclient
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-novaclient
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-neutronclient
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-glanceclient
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-ceilometerclient
+
+RUN pip install django_rest_swagger
+
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-setuptools
+RUN easy_install django_evolution
+RUN easy_install python_gflags
+RUN easy_install --upgrade httplib2
+RUN easy_install google_api_python_client
+RUN easy_install httplib2.ca_certs_locater
+
+# Install custom Ansible
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-crypto
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-yaml
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-client
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-paramiko
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-jinja2
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-httplib2
+RUN git clone -b release1.8.2 git://github.com/ansible/ansible.git /opt/ansible
+RUN git clone -b release1.8.2 git://github.com/ansible/ansible-modules-extras.git /opt/ansible/lib/ansible/modules/extras
+RUN git clone -b release1.8.2 git://github.com/ansible/ansible-modules-extras.git /opt/ansible/v2/ansible/modules/extras
+RUN git clone git://github.com/sb98052/ansible-modules-core.git /opt/ansible/lib/ansible/modules/core
+RUN git clone git://github.com/sb98052/ansible-modules-core.git /opt/ansible/v2/ansible/modules/core
+ADD ansible-hosts /etc/ansible/hosts
+
+ADD http://code.jquery.com/jquery-1.9.1.min.js /usr/local/lib/python2.7/dist-packages/suit/static/suit/js/
+
+# For Observer
+RUN git clone git://git.planet-lab.org/fofum.git /tmp/fofum
+RUN cd /tmp/fofum; python setup.py install
+RUN rm -rf /tmp/fofum
+
+RUN mkdir -p /usr/local/share /bin
+ADD http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-x86_64.tar.bz2 /usr/local/share/
+RUN tar jxvf /usr/local/share/phantomjs-1.7.0-linux-x86_64.tar.bz2 -C /usr/local/share/
+RUN rm -f /usr/local/share/phantomjs-1.7.0-linux-x86_64.tar.bz2
+RUN ln -s /usr/local/share/phantomjs-1.7.0-linux-x86_64 /usr/local/share/phantomjs
+RUN ln -s /usr/local/share/phantomjs/bin/phantomjs /bin/phantomjs
+
+# Supervisor
+RUN DEBIAN_FRONTEND=noninteractive apt-get install -y supervisor
+ADD observer.conf /etc/supervisor/conf.d/
+
+# Get XOS 
+ADD xos /opt/xos
+
+# Initscript is broken in Ubuntu
+#ADD observer-initscript /etc/init.d/xosobserver
+
+RUN chmod +x /opt/xos/scripts/opencloud
+RUN /opt/xos/scripts/opencloud genkeys
+
+# Workaround for AUFS issue
+# https://github.com/docker/docker/issues/783#issuecomment-56013588
+RUN mkdir /etc/ssl/private-copy; mv /etc/ssl/private/* /etc/ssl/private-copy/; rm -r /etc/ssl/private; mv /etc/ssl/private-copy /etc/ssl/private; chmod -R 0700 /etc/ssl/private; chown -R postgres /etc/ssl/private
+
+# Set postgres password to match default value in settings.py
+RUN service postgresql start; sudo -u postgres psql -c "alter user postgres with password 'password';"
+
+# Turn DEBUG on so that devel server will serve static files
+#    (not necessary if --insecure is passed to 'manage.py runserver')
+# RUN sed -i 's/DEBUG = False/DEBUG = True/' /opt/xos/xos/settings.py
+
+# Cruft to workaround problems with migrations, should go away...
+RUN /opt/xos/scripts/opencloud remigrate
+
+# git clone uses cached copy, doesn't pick up latest
+RUN git -C /opt/ansible pull
+RUN git -C /opt/ansible/lib/ansible/modules/core pull
+RUN git -C /opt/ansible/v2/ansible/modules/core pull
+
+# install Tosca engine
+RUN apt-get install -y m4
+RUN pip install python-dateutil
+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 ["/bin/bash"]
+#CMD /opt/xos/scripts/docker_start_xos
+RUN mkdir -p /root/setup
+ADD xos/configurations/common/admin-openrc.sh /root/setup/
+RUN bash -c 'echo "nat-net" > /root/setup/flat_net_name'
+ADD xos/configurations/common/cloudlab-nodes.yaml /opt/xos/configurations/commmon/
+ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
+
+CMD /usr/bin/make -C /opt/xos/configurations/kilo-install -f Makefile.inside; /bin/bash
+
+#CMD ["/bin/bash"]
diff --git a/xos/configurations/kilo-install/Dockerfile.kilo-install b/xos/configurations/kilo-install/Dockerfile.kilo-install
new file mode 100644
index 0000000..e4d301b
--- /dev/null
+++ b/xos/configurations/kilo-install/Dockerfile.kilo-install
@@ -0,0 +1,9 @@
+RUN mkdir -p /root/setup
+ADD xos/configurations/common/admin-openrc.sh /root/setup/
+RUN bash -c 'echo "nat-net" > /root/setup/flat_net_name'
+ADD xos/configurations/common/cloudlab-nodes.yaml /opt/xos/configurations/commmon/
+ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
+
+CMD /usr/bin/make -C /opt/xos/configurations/kilo-install -f Makefile.inside; /bin/bash
+
+#CMD ["/bin/bash"]
diff --git a/xos/configurations/kilo-install/Makefile b/xos/configurations/kilo-install/Makefile
new file mode 100644
index 0000000..7d11490
--- /dev/null
+++ b/xos/configurations/kilo-install/Makefile
@@ -0,0 +1,30 @@
+MYIP:=$(shell hostname -i)
+RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
+LAST_CONTAINER=$(shell sudo docker ps -l -q)
+
+test: common_cloudlab images
+	echo "# Autogenerated -- do not edit" > Dockerfile
+	cat ../common/Dockerfile.common Dockerfile.kilo-install >> Dockerfile
+	cd ../../..; sudo docker build -t xos -f xos/configurations/kilo-install/Dockerfile .
+	sudo docker run -d --add-host="0.0.0.0:$(MYIP)" -p 9999:8000 -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro  xos
+	bash ../common/wait_for_xos.sh
+
+common_cloudlab:
+	make -C ../common -f Makefile.cloudlab
+
+stop:
+	sudo docker stop $(RUNNING_CONTAINER)
+
+showlogs:
+	sudo docker logs $(LAST_CONTAINER)
+
+enter:
+	sudo docker exec -t -i $(RUNNING_CONTAINER) bash
+
+images:
+	sudo bash -c "source /root/setup/admin-openrc.sh; \
+		glance image-show trusty-server-multi-nic || \
+		glance image-create --name trusty-server-multi-nic \
+			--disk-format qcow2 \
+			--file /proj/xos-PG0/acb/images/trusty-server-multi-nic.img \
+			--container-format bare"
diff --git a/xos/configurations/kilo-install/Makefile.inside b/xos/configurations/kilo-install/Makefile.inside
new file mode 100644
index 0000000..40b2672
--- /dev/null
+++ b/xos/configurations/kilo-install/Makefile.inside
@@ -0,0 +1,12 @@
+all: setup_xos update_certificates run_develserver
+
+setup_xos:
+	bash /opt/xos/scripts/docker_setup_xos
+	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab.yaml
+	python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab-nodes.yaml
+
+update_certificates:
+	update-ca-certificates
+
+run_develserver:
+	cd /opt/xos; python manage.py runserver 0.0.0.0:8000 --insecure
diff --git a/xos/configurations/kilo-install/README b/xos/configurations/kilo-install/README
new file mode 100644
index 0000000..2ff7549
--- /dev/null
+++ b/xos/configurations/kilo-install/README
@@ -0,0 +1,8 @@
+This configuration is based on the "devel" configuration for CloudLab.  It is
+meant to work with an OpenStack Kilo cluster installed on CloudLab using the
+procedure documented here:
+
+https://github.com/andybavier/opencloud-cluster-setup
+
+It launches an XOS container on Cloudlab that runs the XOS develserver. The
+container is left running in the background.
diff --git a/xos/core/xoslib/methods/cordsubscriber.py b/xos/core/xoslib/methods/cordsubscriber.py
index ea8da80..c26ac54 100644
--- a/xos/core/xoslib/methods/cordsubscriber.py
+++ b/xos/core/xoslib/methods/cordsubscriber.py
@@ -392,10 +392,18 @@
 
         return Response( {"vbng_mapping": mappings} )
 
+class CordDebugIdSerializer(serializers.ModelSerializer, PlusSerializerMixin):
+    # Swagger is failing because CordDebugViewSet has neither a model nor
+    # a serializer_class. Stuck this in here as a placeholder for now.
+    id = ReadOnlyField()
+    class Meta:
+        model = CordSubscriber
+
 class CordDebugViewSet(XOSViewSet):
     base_name = "cord_debug"
     method_name = "rs/cord_debug"
     method_kind = "viewset"
+    serializer_class = CordDebugIdSerializer
 
     @classmethod
     def get_urlpatterns(self):
diff --git a/xos/openstack/client.py b/xos/openstack/client.py
index 4962e06..c500204 100644
--- a/xos/openstack/client.py
+++ b/xos/openstack/client.py
@@ -36,7 +36,7 @@
     return opts
 
 class Client:
-    def __init__(self, username=None, password=None, tenant=None, url=None, token=None, endpoint=None, controller=None, admin=True, *args, **kwds):
+    def __init__(self, username=None, password=None, tenant=None, url=None, token=None, endpoint=None, controller=None, cacert=None, admin=True, *args, **kwds):
        
         self.has_openstack = has_openstack
         self.url = controller.auth_url
@@ -62,6 +62,8 @@
         if endpoint:
             self.endpoint = endpoint
 
+        self.cacert = cacert
+
         #if '@' in self.username:
         #    self.username = self.username[:self.username.index('@')]
 
@@ -157,7 +159,8 @@
             self.client = quantum_client.Client(username=self.username,
                                                 password=self.password,
                                                 tenant_name=self.tenant,
-                                                auth_url=self.url)
+                                                auth_url=self.url,
+                                                ca_cert=self.cacert)
     @require_enabled
     def connect(self, *args, **kwds):
         self.__init__(*args, **kwds)
diff --git a/xos/openstack_observer/steps/sync_instances.yaml b/xos/openstack_observer/steps/sync_instances.yaml
index 803a294..a61e5cf 100644
--- a/xos/openstack_observer/steps/sync_instances.yaml
+++ b/xos/openstack_observer/steps/sync_instances.yaml
@@ -17,6 +17,7 @@
       wait_for: 200
       flavor_name: {{ flavor_name }}
       user_data: "{{ user_data }}"
+      config_drive: yes
       nics:
       {% for net in nics %}
           - net-id: {{ net }}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 1a5bee1..b929eda 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -8,11 +8,11 @@
             no-delete:
                 type: boolean
                 default: false
-                description: do not allow Tosca to delete this object
+                description: Do not allow Tosca to delete this object
             no-create:
                 type: boolean
                 default: false
-                description: do not allow Tosca to create this object)
+                description: Do not allow Tosca to create this object)
 # Service
 define(xos_base_service_caps,
             scalable:
@@ -23,24 +23,30 @@
             kind:
                 type: string
                 default: generic
+                description: Type of service.
             view_url:
                 type: string
                 required: false
+                description: URL to follow when icon is clicked in the Service Directory.
             icon_url:
                 type: string
                 required: false
+                description: ICON to display in the Service Directory.
             enabled:
                 type: boolean
                 default: true
             published:
                 type: boolean
                 default: true
+                description: If True then display this Service in the Service Directory.
             public_key:
                 type: string
                 required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
             versionNumber:
                 type: string
-                required: false)
+                required: false
+                description: Version number of Service.)
 # Subscriber
 define(xos_base_subscriber_caps,
             subscriber:
@@ -67,12 +73,17 @@
 node_types:
     tosca.nodes.Service:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Service object. Services may be listed in the Service
+            directory and may be linked together via Tenancy Relationships.
         capabilities:
             xos_base_service_caps
         properties:
             xos_base_service_props
 
     tosca.nodes.VCPEService:
+        description: >
+            CORD: The vCPE Service.
         derived_from: tosca.nodes.Root
         capabilities:
             xos_base_service_caps
@@ -81,9 +92,12 @@
             backend_network_label:
                 type: string
                 required: false
+                description: Label that matches network used to connect HPC and BBS services.
 
     tosca.nodes.VBNGService:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: The vBNG Service.
         capabilities:
             xos_base_service_caps
         properties:
@@ -91,9 +105,12 @@
             vbng_url:
                 type: string
                 required: false
+                description: URL of REST API endpoint for vBNG Service.
 
     tosca.nodes.CDNService:
         derived_from: tosca.nodes.Root
+        description: >
+            Content Delivery Network Service. Includes Request Routing and Hypercache.
         capabilities:
             xos_base_service_caps
         properties:
@@ -101,6 +118,7 @@
 
     tosca.nodes.Subscriber:
         derived_from: tosca.nodes.Root
+        description: XOS subscriber base class.
         capabilities:
             xos_base_subscriber_caps
         properties:
@@ -108,6 +126,10 @@
 
     tosca.nodes.CORDSubscriber:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: Subscriber. The Subscriber object contains all of the settings
+            for a CORD household. For example, it contains parental control
+            filter settings.
         capabilities:
             xos_base_subscriber_caps
         properties:
@@ -115,37 +137,54 @@
             firewall_enable:
                 type: boolean
                 default: false
+                description: If True, then firewalling is enabled.
             url_filter_enable:
                 type: boolean
                 default: false
+                description: If True, then parental controls are enabled.
             url_filter_level:
                 type: string
                 default: PG
+                description: The default URL filter level for the household.
             cdn_enable:
                 type: boolean
                 default: true
+                description: If True, then the CDN is enabled.
 
     tosca.nodes.CORDUser:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: User. The CORD user represents an individual device beloning
+            to the CORD Subscriber. Each device may have its own parental
+            controls.
         properties:
             level:
                 type: string
                 default: PG_13
+                description: Parental control level for this device.
             mac:
                 type: string
                 required: true
+                description: MAC address for this device.
 
     tosca.nodes.VOLTTenant:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: A Tenant of the vOLT Service. Each Tenant is tied to a
+            specific vlan_id.
         properties:
             xos_base_tenant_props
             vlan_id:
                 type: string
                 required: false
+                description: vlan_id for connection to subscriber household.
 
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
 
+        description: >
+            An XOS User record. Users are able to login and use the XOS GUI.
+
         capabilities:
             user:
                 type: tosca.capabilities.xos.User
@@ -157,31 +196,43 @@
             firstname:
                 type: string
                 required: true
+                description: First name of User.
             lastname:
                 type: string
                 required: true
+                description: Last name of User.
             phone:
                 type: string
                 required: false
+                description: Phone number of User.
             user_url:
                 type: string
                 required: false
+                description: URL to User web page.
             public_key:
                 type: string
                 required: false
+                description: Public key that will be installed in Instances.
             is_active:
                 type: boolean
                 default: true
+                description: If True, the user may log in.
             is_admin:
                 type: boolean
                 default: false
+                description: If True, the user has root admin privileges.
             login_page:
                 type: string
                 required: false
+                description: Indicates what page the user should go to on login.
 
     tosca.nodes.NetworkTemplate:
         derived_from: tosca.nodes.Root
 
+        description: >
+            An XOS network template. Network templates contain settings associated
+            with a particular class of network.
+
         capabilities:
             network_template:
                 type: tosca.capabilities.xos.NetworkTemplate
@@ -190,21 +241,27 @@
             visibility:
                 type: string
                 default: private
+                description: Indicates whether network is publicly routable.
             translation:
                 type: string
                 default: none
+                description: Indicates whether network uses address translation.
             shared_network_name:
                 type: string
                 required: false
+                description: Attaches this template to a specific OpenStack network.
             shared_network_id:
                 type: string
                 required: false
+                description: Attaches this template to a specific OpenStack network.
             topology_kind:
                 type: string
                 default: BigSwitch
+                descrption: Describes the topology of the network.
             controller_kind:
                 type: string
                 required: false
+                description: Indicates the type of controller that the network is connected to.
 
     tosca.nodes.network.Network.XOS:
           # Due to bug? in implementation, we have to copy everything from
@@ -280,41 +337,27 @@
             ports:

                 type: string
                 required: false
+                description: >
+                    A comma-separated list of protocols and ports. For example,
+                    "tcp/123, tcp/456-459, udp/111"
             labels:
                 type: string
                 required: false
+                description: A comma-separated list of labels for this network.
             permit_all_slices:
                 type: boolean
                 # In the data model, this is defaulted to false. However, to
                 # preserve Tosca semantics, we default it to true instead.
                 default: true
+                description: If True, then any slice may be attached to this network.
           capabilities:
             link:

               type: tosca.capabilities.network.Linkable
 
-#    tosca.nodes.XOSNetwork:
-#        derived_from: tosca.nodes.Root
-#
-#        capabilities:
-#            network:
-#                type: tosca.capabilities.xos.Network
-#
-#        properties:
-#            ports:
-#                type: string
-#                required: false
-#            labels:
-#                type: string
-#                required: false
-#            permit_all_slices:
-#                type: boolean
-#                default: false
-#            permitted_slices:
-#                type: string
-#                required: false
-
     tosca.nodes.Deployment:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Deployment.
         capabilities:
             deployment:
                 type: tosca.capabilities.xos.Deployment
@@ -323,12 +366,16 @@
             accessControl:
                 type: string
                 default: allow all
+                description: ACL that describes who may use this deployment.
             flavors:
                 type: string
                 required: false
+                description: Comma-separated list of flavors that this deployment supports.
 
     tosca.nodes.Image:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Operating System Image.
         capabilities:
             image:
                 type: tosca.capabilities.xos.Image
@@ -336,15 +383,21 @@
             disk_format:
                 type: string
                 required: false
+                description: Glance disk format.
             container_format:
                 type: string
                 required: false
+                description: Glance container format.
             path:
                 type: string
                 required: false
+                description: Path to Image file inside XOS docker container.
 
     tosca.nodes.Controller:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS controller. Controllers serve as the interface between
+            XOS and services such as OpenStack.
         capabilities:
             controller:
                 type: tosca.capabilities.xos.Controller
@@ -352,27 +405,36 @@
             backend_type:
                 type: string
                 required: false
+                description: Type of backend.
             version:
                 type: string
                 required: false
+                description: Version of backend.
             auth_url:
                 type: string
                 required: false
+                description: Keystone auth_url.
             admin_user:
                 type: string
                 required: false
+                description: Keystone username.
             admin_password:
                 type: string
                 required: false
+                description: Keystone password.
             admin_tenant:
                 type: string
                 required: false
+                description: Tenant associated with admin account.
             domain:
                 type: string
                 required: false
+                description: OpenStack domain (or "Default")
 
     tosca.nodes.Site:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Site. Sites are containers for Users and/or Nodes.
         capabilities:
             site:
                 type: tosca.capabilities.xos.Site
@@ -380,19 +442,22 @@
              display_name:
                  type: string
                  required: false
-                 description: name of the site
+                 description: Name of the site.
              site_url:
                  type: string
                  required: false
+                 description: URL of site web page.
              enabled:
                  type: boolean
                  default: true
              hosts_nodes:
                  type: boolean
                  default: true
+                 description: If True, then this site hosts nodes where Instances may be instantiated.
              hosts_users:
                  type: boolean
                  default: true
+                 description: If True, then this site hosts users who may use XOS.
              is_public:
                  type: boolean
                  default: true
@@ -400,6 +465,9 @@
 
     tosca.nodes.Slice:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Slice. A slice is a collection of instances that share
+            common attributes.
         capability:
             slice:
                 type: tosca.capabilities.xos.Slice
@@ -410,15 +478,21 @@
             description:
                 type: string
                 required: false
+                description: Description of this slice.
             slice_url:
                 type: string
                 required: false
+                description: URL to web page that describes slice.
             max_instances:
                 type: integer
                 default: 10
+                description: Quota of instances that this slice may create.
 
     tosca.nodes.Node:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Node. Nodes are physical machines that host virtual machines
+            and/or containers.
         capability:
             node:
                 type: tosca.capabilities.xos.Node
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 96c03fa..2c674eb 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -20,6 +20,9 @@
 node_types:
     tosca.nodes.Service:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Service object. Services may be listed in the Service
+            directory and may be linked together via Tenancy Relationships.
         capabilities:
             scalable:
                 type: tosca.capabilities.Scalable
@@ -29,26 +32,34 @@
             kind:
                 type: string
                 default: generic
+                description: Type of service.
             view_url:
                 type: string
                 required: false
+                description: URL to follow when icon is clicked in the Service Directory.
             icon_url:
                 type: string
                 required: false
+                description: ICON to display in the Service Directory.
             enabled:
                 type: boolean
                 default: true
             published:
                 type: boolean
                 default: true
+                description: If True then display this Service in the Service Directory.
             public_key:
                 type: string
                 required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
             versionNumber:
                 type: string
                 required: false
+                description: Version number of Service.
 
     tosca.nodes.VCPEService:
+        description: >
+            CORD: The vCPE Service.
         derived_from: tosca.nodes.Root
         capabilities:
             scalable:
@@ -59,30 +70,39 @@
             kind:
                 type: string
                 default: generic
+                description: Type of service.
             view_url:
                 type: string
                 required: false
+                description: URL to follow when icon is clicked in the Service Directory.
             icon_url:
                 type: string
                 required: false
+                description: ICON to display in the Service Directory.
             enabled:
                 type: boolean
                 default: true
             published:
                 type: boolean
                 default: true
+                description: If True then display this Service in the Service Directory.
             public_key:
                 type: string
                 required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
             versionNumber:
                 type: string
                 required: false
+                description: Version number of Service.
             backend_network_label:
                 type: string
                 required: false
+                description: Label that matches network used to connect HPC and BBS services.
 
     tosca.nodes.VBNGService:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: The vBNG Service.
         capabilities:
             scalable:
                 type: tosca.capabilities.Scalable
@@ -92,30 +112,39 @@
             kind:
                 type: string
                 default: generic
+                description: Type of service.
             view_url:
                 type: string
                 required: false
+                description: URL to follow when icon is clicked in the Service Directory.
             icon_url:
                 type: string
                 required: false
+                description: ICON to display in the Service Directory.
             enabled:
                 type: boolean
                 default: true
             published:
                 type: boolean
                 default: true
+                description: If True then display this Service in the Service Directory.
             public_key:
                 type: string
                 required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
             versionNumber:
                 type: string
                 required: false
+                description: Version number of Service.
             vbng_url:
                 type: string
                 required: false
+                description: URL of REST API endpoint for vBNG Service.
 
     tosca.nodes.CDNService:
         derived_from: tosca.nodes.Root
+        description: >
+            Content Delivery Network Service. Includes Request Routing and Hypercache.
         capabilities:
             scalable:
                 type: tosca.capabilities.Scalable
@@ -125,27 +154,34 @@
             kind:
                 type: string
                 default: generic
+                description: Type of service.
             view_url:
                 type: string
                 required: false
+                description: URL to follow when icon is clicked in the Service Directory.
             icon_url:
                 type: string
                 required: false
+                description: ICON to display in the Service Directory.
             enabled:
                 type: boolean
                 default: true
             published:
                 type: boolean
                 default: true
+                description: If True then display this Service in the Service Directory.
             public_key:
                 type: string
                 required: false
+                description: Public key to install into Instances to allows Services to SSH into them.
             versionNumber:
                 type: string
                 required: false
+                description: Version number of Service.
 
     tosca.nodes.Subscriber:
         derived_from: tosca.nodes.Root
+        description: XOS subscriber base class.
         capabilities:
             subscriber:
                 type: tosca.capabilities.xos.Subscriber
@@ -159,6 +195,10 @@
 
     tosca.nodes.CORDSubscriber:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: Subscriber. The Subscriber object contains all of the settings
+            for a CORD household. For example, it contains parental control
+            filter settings.
         capabilities:
             subscriber:
                 type: tosca.capabilities.xos.Subscriber
@@ -172,28 +212,41 @@
             firewall_enable:
                 type: boolean
                 default: false
+                description: If True, then firewalling is enabled.
             url_filter_enable:
                 type: boolean
                 default: false
+                description: If True, then parental controls are enabled.
             url_filter_level:
                 type: string
                 default: PG
+                description: The default URL filter level for the household.
             cdn_enable:
                 type: boolean
                 default: true
+                description: If True, then the CDN is enabled.
 
     tosca.nodes.CORDUser:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: User. The CORD user represents an individual device beloning
+            to the CORD Subscriber. Each device may have its own parental
+            controls.
         properties:
             level:
                 type: string
                 default: PG_13
+                description: Parental control level for this device.
             mac:
                 type: string
                 required: true
+                description: MAC address for this device.
 
     tosca.nodes.VOLTTenant:
         derived_from: tosca.nodes.Root
+        description: >
+            CORD: A Tenant of the vOLT Service. Each Tenant is tied to a
+            specific vlan_id.
         properties:
             kind:
                 type: string
@@ -204,10 +257,14 @@
             vlan_id:
                 type: string
                 required: false
+                description: vlan_id for connection to subscriber household.
 
     tosca.nodes.User:
         derived_from: tosca.nodes.Root
 
+        description: >
+            An XOS User record. Users are able to login and use the XOS GUI.
+
         capabilities:
             user:
                 type: tosca.capabilities.xos.User
@@ -219,31 +276,43 @@
             firstname:
                 type: string
                 required: true
+                description: First name of User.
             lastname:
                 type: string
                 required: true
+                description: Last name of User.
             phone:
                 type: string
                 required: false
+                description: Phone number of User.
             user_url:
                 type: string
                 required: false
+                description: URL to User web page.
             public_key:
                 type: string
                 required: false
+                description: Public key that will be installed in Instances.
             is_active:
                 type: boolean
                 default: true
+                description: If True, the user may log in.
             is_admin:
                 type: boolean
                 default: false
+                description: If True, the user has root admin privileges.
             login_page:
                 type: string
                 required: false
+                description: Indicates what page the user should go to on login.
 
     tosca.nodes.NetworkTemplate:
         derived_from: tosca.nodes.Root
 
+        description: >
+            An XOS network template. Network templates contain settings associated
+            with a particular class of network.
+
         capabilities:
             network_template:
                 type: tosca.capabilities.xos.NetworkTemplate
@@ -252,21 +321,27 @@
             visibility:
                 type: string
                 default: private
+                description: Indicates whether network is publicly routable.
             translation:
                 type: string
                 default: none
+                description: Indicates whether network uses address translation.
             shared_network_name:
                 type: string
                 required: false
+                description: Attaches this template to a specific OpenStack network.
             shared_network_id:
                 type: string
                 required: false
+                description: Attaches this template to a specific OpenStack network.
             topology_kind:
                 type: string
                 default: BigSwitch
+                descrption: Describes the topology of the network.
             controller_kind:
                 type: string
                 required: false
+                description: Indicates the type of controller that the network is connected to.
 
     tosca.nodes.network.Network.XOS:
           # Due to bug? in implementation, we have to copy everything from
@@ -342,41 +417,27 @@
             ports:

                 type: string
                 required: false
+                description: >
+                    A comma-separated list of protocols and ports. For example,
+                    "tcp/123, tcp/456-459, udp/111"
             labels:
                 type: string
                 required: false
+                description: A comma-separated list of labels for this network.
             permit_all_slices:
                 type: boolean
                 # In the data model, this is defaulted to false. However, to
                 # preserve Tosca semantics, we default it to true instead.
                 default: true
+                description: If True, then any slice may be attached to this network.
           capabilities:
             link:

               type: tosca.capabilities.network.Linkable
 
-#    tosca.nodes.XOSNetwork:
-#        derived_from: tosca.nodes.Root
-#
-#        capabilities:
-#            network:
-#                type: tosca.capabilities.xos.Network
-#
-#        properties:
-#            ports:
-#                type: string
-#                required: false
-#            labels:
-#                type: string
-#                required: false
-#            permit_all_slices:
-#                type: boolean
-#                default: false
-#            permitted_slices:
-#                type: string
-#                required: false
-
     tosca.nodes.Deployment:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Deployment.
         capabilities:
             deployment:
                 type: tosca.capabilities.xos.Deployment
@@ -384,20 +445,24 @@
             no-delete:
                 type: boolean
                 default: false
-                description: do not allow Tosca to delete this object
+                description: Do not allow Tosca to delete this object
             no-create:
                 type: boolean
                 default: false
-                description: do not allow Tosca to create this object
+                description: Do not allow Tosca to create this object
             accessControl:
                 type: string
                 default: allow all
+                description: ACL that describes who may use this deployment.
             flavors:
                 type: string
                 required: false
+                description: Comma-separated list of flavors that this deployment supports.
 
     tosca.nodes.Image:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Operating System Image.
         capabilities:
             image:
                 type: tosca.capabilities.xos.Image
@@ -405,15 +470,21 @@
             disk_format:
                 type: string
                 required: false
+                description: Glance disk format.
             container_format:
                 type: string
                 required: false
+                description: Glance container format.
             path:
                 type: string
                 required: false
+                description: Path to Image file inside XOS docker container.
 
     tosca.nodes.Controller:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS controller. Controllers serve as the interface between
+            XOS and services such as OpenStack.
         capabilities:
             controller:
                 type: tosca.capabilities.xos.Controller
@@ -421,27 +492,36 @@
             backend_type:
                 type: string
                 required: false
+                description: Type of backend.
             version:
                 type: string
                 required: false
+                description: Version of backend.
             auth_url:
                 type: string
                 required: false
+                description: Keystone auth_url.
             admin_user:
                 type: string
                 required: false
+                description: Keystone username.
             admin_password:
                 type: string
                 required: false
+                description: Keystone password.
             admin_tenant:
                 type: string
                 required: false
+                description: Tenant associated with admin account.
             domain:
                 type: string
                 required: false
+                description: OpenStack domain (or "Default")
 
     tosca.nodes.Site:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Site. Sites are containers for Users and/or Nodes.
         capabilities:
             site:
                 type: tosca.capabilities.xos.Site
@@ -449,19 +529,22 @@
              display_name:
                  type: string
                  required: false
-                 description: name of the site
+                 description: Name of the site.
              site_url:
                  type: string
                  required: false
+                 description: URL of site web page.
              enabled:
                  type: boolean
                  default: true
              hosts_nodes:
                  type: boolean
                  default: true
+                 description: If True, then this site hosts nodes where Instances may be instantiated.
              hosts_users:
                  type: boolean
                  default: true
+                 description: If True, then this site hosts users who may use XOS.
              is_public:
                  type: boolean
                  default: true
@@ -469,6 +552,9 @@
 
     tosca.nodes.Slice:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Slice. A slice is a collection of instances that share
+            common attributes.
         capability:
             slice:
                 type: tosca.capabilities.xos.Slice
@@ -479,15 +565,21 @@
             description:
                 type: string
                 required: false
+                description: Description of this slice.
             slice_url:
                 type: string
                 required: false
+                description: URL to web page that describes slice.
             max_instances:
                 type: integer
                 default: 10
+                description: Quota of instances that this slice may create.
 
     tosca.nodes.Node:
         derived_from: tosca.nodes.Root
+        description: >
+            An XOS Node. Nodes are physical machines that host virtual machines
+            and/or containers.
         capability:
             node:
                 type: tosca.capabilities.xos.Node
diff --git a/xos/tosca/doctemplates/html/node_type.html b/xos/tosca/doctemplates/html/node_type.html
new file mode 100644
index 0000000..95e8b7b
--- /dev/null
+++ b/xos/tosca/doctemplates/html/node_type.html
@@ -0,0 +1,46 @@
+<hr>
+<h2 id="{{ node_type.node_type_name }}">{{ node_type.node_type_name }}</h2>
+<blockquote>
+
+<h3>Description:</h3>
+{% if node_type.description %}
+  <blockquote>
+  {{ node_type.description }}
+  </blockquote>
+{% endif %}
+
+<h3>Capabilities:</h3>
+{% if node_type.capabilities %}
+  <blockquote>
+  <table class="properties">
+  <tr><th>name</th><th>type</th></tr>
+  {% for capname,cap in node_type.capabilities.iteritems() %}
+     <tr><td>{{ capname }}</td><td>{{ cap.type }}</td></tr>
+  {% endfor %}
+  </table>
+  </blockquote>
+{% endif %}
+
+<h3>Properties:</h3>
+{% if node_type.properties %}
+  <blockquote>
+  <table class="properties">
+  <tr><th>name</th><th>required</th><th>type</th><th>default</th></tr>
+  {% for propname,prop in node_type.properties.iteritems() %}
+     <tr><td>{{ propname }}</td>
+         <td>{{ prop.required }}</td>
+         <td>{{ prop.type }}</td>
+         <td>{{ prop.default }}</td>
+     </tr>
+     {% if prop.description %}
+     <tr><td colspan=4 class="helptext" style="apadding-left: 30px;">{{ prop.description }}</td></tr>
+     {% endif %}
+  {% endfor %}
+  </table>
+  </blockquote>
+{% endif %}
+
+</blockquote>
+
+
+
diff --git a/xos/tosca/doctemplates/html/toscadoctemplate.html b/xos/tosca/doctemplates/html/toscadoctemplate.html
new file mode 100644
index 0000000..8568d33
--- /dev/null
+++ b/xos/tosca/doctemplates/html/toscadoctemplate.html
@@ -0,0 +1,80 @@
+<html><head>
+<title>XOS TOSCA Reference</title>
+
+<style>
+.properties {
+  border-collapse: collapse;
+}
+.properties td, .properties th {
+  border: 1px solid black;
+  padding: 3px 7px 2px 7px;
+}
+.properties th {
+  font-size: 1.1em;
+  padding-top: 5px;
+  padding-bottom: 4px;
+  oldbackground-color: #A7C942;
+  background-color: #2d6ca2;
+  color: #ffffff;
+}
+.helptext {
+  padding-left: 30px !important;
+  color: rgb(153, 153, 153);
+}
+</style>
+
+</head>
+<body>
+
+<h1>XOS TOSCA Reference</h1>
+
+<p>This documentation is autogenerated from the XOS Tosca custom_types
+specification (xos/tosca/custom_types/xos.m4).
+
+Table of Contents:
+<ul>
+<li>Node Types
+<ul>
+{% for node_type in node_types %}
+  {% if node_type.node_type_kind == "node" %}
+    <li><a href="#{{ node_type.node_type_name }}">{{ node_type.node_type_name }}</a></li>
+  {% endif %}
+{% endfor %}
+</li>
+</ul>
+<li><a href="#xos_relationships">Relationships</a></li>
+<li><a href="#xos_capabilities">Capabilities</a></li>
+</ul>
+
+{% for node_type in node_types %}
+  {% if node_type.node_type_kind == "node" %}
+    {% include 'node_type.html' %}
+  {% endif %}
+{% endfor %}
+
+<h3 id="xos_relationships">XOS Relationships</h3>
+<blockquote>
+<table class="properties">
+<tr><th>name</th><th>target_types</th></tr>
+{% for node_type in node_types %}
+  {% if node_type.node_type_kind == "relationship" %}
+      <tr><td>{{ node_type.node_type_name }}</td><td>{{ node_type.valid_target_types|join(', ') }}</td></tr>
+  {% endif %}
+{% endfor %}
+</table>
+</blockquote>
+
+<h3 id="xos_capabilities">XOS Capabilities</h3>
+<blockquote>
+<table class="properties">
+<tr><th>name</th></tr>
+{% for node_type in node_types %}
+  {% if node_type.node_type_kind == "capability" %}
+      <tr><td>{{ node_type.node_type_name }}</td></tr>
+  {% endif %}
+{% endfor %}
+</table>
+</blockquote>
+
+</body>
+</html>
diff --git a/xos/tosca/makedocs.py b/xos/tosca/makedocs.py
new file mode 100644
index 0000000..f7dc11b
--- /dev/null
+++ b/xos/tosca/makedocs.py
@@ -0,0 +1,59 @@
+import jinja2
+import os
+import sys
+import yaml
+import pdb
+
+# add the parent directory to sys.path
+import os,sys,inspect
+currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+parentdir = os.path.dirname(currentdir)
+sys.path.append(parentdir)
+
+# a bit of a hack for developing -- run m4 to generate xos.yaml from xos.m4
+os.system("m4 %s/custom_types/xos.m4 > %s/custom_types/xos.yaml" % (currentdir, currentdir))
+
+"""
+{'derived_from': 'tosca.nodes.Root', 'capabilities': {'scalable': {'type': 'tosca.capabilities.Scalable'},
+'service': {'type': 'tosca.capabilities.xos.Service'}}, 'properties': {'icon_url': {'required': False,
+'type': 'string'}, 'public_key': {'required': False, 'type': 'string'}, 'kind': {'default': 'generic',
+'type': 'string'}, 'published': {'default': True, 'type': 'boolean'}, 'view_url': {'required': False, 'type': 'string'}, 'enabled': {'default': True, 'type': 'boolean'}, 'versionNumber': {'required': False, 'type': 'string'}}}
+"""
+
+class ToscaDocumenter(object):
+    def __init__(self, fn="./custom_types/xos.yaml", templatedir="./doctemplates/html", templatename="toscadoctemplate.html", destfn="tosca_reference.html"):
+        self.env = jinja2.Environment(loader=jinja2.FileSystemLoader(templatedir))
+
+        self.custom_types = yaml.load(file(fn).read())
+        self.node_types = self.custom_types.get("node_types")
+
+        self.destfn = destfn
+        self.templatename = templatename
+
+    def run(self):
+        node_types=[]
+        for k in sorted(self.node_types.keys()):
+            nt = self.node_types[k]
+            nt["node_type_name"] = k
+
+            derived_from = nt.get("derived_from","")
+
+            if derived_from.startswith("tosca.nodes"):
+                nt["node_type_kind"] = "node"
+            elif derived_from.startswith("tosca.capabilities"):
+                nt["node_type_kind"] = "capability"
+            elif derived_from.startswith("tosca.relationships"):
+                nt["node_type_kind"] = "relationship"
+
+            node_types.append(nt)
+
+        template = self.env.get_template(self.templatename)
+
+        self.destf = open(self.destfn,"w")
+        self.destf.write(template.render(node_types=node_types))
+
+def main():
+    ToscaDocumenter().run()
+
+if __name__=="__main__":
+    main()
diff --git a/xos/tosca/tests/allObserverTests.py b/xos/tosca/tests/allObserverTests.py
index 82bb10b..d06daba 100644
--- a/xos/tosca/tests/allObserverTests.py
+++ b/xos/tosca/tests/allObserverTests.py
@@ -1,6 +1,12 @@
 from observerComputeTest import ObserverComputeTest
 from observerImageTest import ObserverImageTest
+from observerUserTest import ObserverUserTest
+from observerSiteTest import ObserverSiteTest
+from observerSliceTest import ObserverSliceTest
 
 if __name__ == "__main__":
     ObserverComputeTest()
     ObserverImageTest()
+    ObserverSiteTest()
+    ObserverUserTest()
+    ObserverSliceTest()
diff --git a/xos/tosca/tests/observerSiteTest.py b/xos/tosca/tests/observerSiteTest.py
new file mode 100644
index 0000000..0ebc8be
--- /dev/null
+++ b/xos/tosca/tests/observerSiteTest.py
@@ -0,0 +1,56 @@
+from observertest import BaseObserverToscaTest
+
+from core.models import Site, Deployment, ControllerSite
+
+# Note that as a side effect, these tests will also create a Site
+
+class ObserverSiteTest(BaseObserverToscaTest):
+    tests = ["create_site"]
+    # hide_observer_output = False # uncomment to display lots of stuff to screen
+
+    def cleanup(self):
+        # We don't want to leak rezsources, so we make sure to let the observer
+        # attempt to delete these objects.
+        self.try_to_delete(Site, purge=False, login_base="testsite")
+        self.run_observer()
+        self.try_to_delete(Site, purge=True, login_base="testsite")
+
+    def create_site(self):
+        self.assert_noobj(Site, "testsite")
+        self.execute(self.make_nodetemplate(self.get_usable_deployment(), "tosca.nodes.Deployment",
+                                            props={"no-delete": True}) +  \
+"""
+    testsite:
+      type: tosca.nodes.Site
+      properties:
+          site_url: http://opencloud.us/
+      requirements:
+          - deployment:
+               node: %s
+               relationship: tosca.relationships.SiteDeployment
+               requirements:
+                   - controller:
+                       node: %s
+                       relationship: tosca.relationships.UsesController
+""" % (self.get_usable_deployment(), self.get_usable_controller()))
+
+        testsite = self.assert_obj(Site, "testsite")
+
+        self.run_model_policy(save_output="/tmp/sitetest:create_site:model_policy")
+
+        # make sure a ControllerSite object was created
+        cs = ControllerSite.objects.filter(site=testsite)
+        assert(len(cs) == 1)
+
+        self.run_observer(save_output="/tmp/sitetest:create_site:observer")
+
+        testsite = self.assert_obj(Site, "testsite")
+
+        cs = ControllerSite.objects.filter(site=testsite)
+        assert(len(cs) == 1)
+        assert(cs[0].tenant_id is not None)
+        assert(cs[0].tenant_id != "")
+
+if __name__ == "__main__":
+    ObserverSiteTest()
+
diff --git a/xos/tosca/tests/observerSliceTest.py b/xos/tosca/tests/observerSliceTest.py
new file mode 100644
index 0000000..749f4ed
--- /dev/null
+++ b/xos/tosca/tests/observerSliceTest.py
@@ -0,0 +1,48 @@
+from observertest import BaseObserverToscaTest
+
+from core.models import Site, Deployment, Slice, ControllerSlice
+
+# Note that as a side effect, these tests will also create a Site
+
+class ObserverSliceTest(BaseObserverToscaTest):
+    tests = ["create_slice"]
+    # hide_observer_output = False # uncomment to display lots of stuff to screen
+
+    def cleanup(self):
+        # We don't want to leak resources, so we make sure to let the observer
+        # attempt to delete these objects.
+        self.try_to_delete(Slice, purge=False, name="testsite_slice1")
+        self.try_to_delete(Site, purge=False, login_base="testsite")
+        self.run_observer()
+        self.try_to_delete(Slice, purge=True, name="testsite_slice1")
+        self.try_to_delete(Site, purge=True, login_base="testsite")
+
+    def create_slice(self):
+        self.assert_noobj(Site, "testsite")
+        self.assert_noobj(Slice, "testsite_slice1")
+        self.execute(self.make_nodetemplate(self.get_usable_deployment(), "tosca.nodes.Deployment",
+                                            props={"no-delete": True}) +
+                     self.make_nodetemplate("testsite", "tosca.nodes.Site") + \
+                     self.make_nodetemplate("testsite_slice1", "tosca.nodes.Slice", reqs=[("testsite", "tosca.relationships.MemberOfSite")]))
+
+        testsite = self.assert_obj(Site, "testsite")
+        testslice = self.assert_obj(Slice, "testsite_slice1")
+
+        self.run_model_policy(save_output="/tmp/slicetest:create_slice:model_policy")
+
+        # make sure a ControllerSlice object was created
+        cs = ControllerSlice.objects.filter(slice=testslice)
+        assert(len(cs) == 1)
+
+        self.run_observer(save_output="/tmp/slicetest:create_slice:observer")
+
+        testslice = self.assert_obj(Slice, "testsite_slice1")
+
+        cs = ControllerSlice.objects.filter(slice=testslice)
+        assert(len(cs) == 1)
+        assert(cs[0].tenant_id is not None)
+        assert(cs[0].tenant_id != "")
+
+if __name__ == "__main__":
+    ObserverSliceTest()
+
diff --git a/xos/tosca/tests/observerUserTest.py b/xos/tosca/tests/observerUserTest.py
new file mode 100644
index 0000000..44b47d3
--- /dev/null
+++ b/xos/tosca/tests/observerUserTest.py
@@ -0,0 +1,82 @@
+from observertest import BaseObserverToscaTest
+
+from core.models import Site, Deployment, User, ControllerUser
+
+# Note that as a side effect, these tests will also create a Site
+
+class ObserverUserTest(BaseObserverToscaTest):
+    tests = ["create_user"]
+    # hide_observer_output = False # uncomment to display lots of stuff to screen
+
+    def cleanup(self):
+        # We don't want to leak resources, so we make sure to let the observer
+        # attempt to delete these objects.
+        self.try_to_delete(User, purge=False, email="johndoe@foo.bar")
+        self.try_to_delete(Site, purge=False, login_base="testsite")
+        self.run_observer()
+        self.try_to_delete(User, purge=True, email="johndoe@foo.bar")
+        self.try_to_delete(Site, purge=True, login_base="testsite")
+
+    def assert_nouser(self, email):
+        assert(not User.objects.filter(email=email))
+
+    def assert_user(self, email, **kwargs):
+        obj = User.objects.get(email=email)
+        assert(obj)
+        for (k,v) in kwargs.items():
+            if (getattr(obj,k,None) != v):
+                print "Object %s property '%s' is '%s' and should be '%s'" % (obj, k, getattr(obj,k,None), v)
+                assert(False)
+        return obj
+
+    def create_user(self):
+        self.assert_noobj(Site, "testsite")
+        self.assert_nouser("johndoe@foo.bar")
+        self.execute(self.make_nodetemplate(self.get_usable_deployment(), "tosca.nodes.Deployment",
+                                            props={"no-delete": True}) +  \
+"""
+    testsite:
+      type: tosca.nodes.Site
+      properties:
+          site_url: http://opencloud.us/
+      requirements:
+          - deployment:
+               node: %s
+               relationship: tosca.relationships.SiteDeployment
+               requirements:
+                   - controller:
+                       node: %s
+                       relationship: tosca.relationships.UsesController
+    johndoe@foo.bar:
+      type: tosca.nodes.User

+      properties:

+          password: letmein

+          firstname: john

+          lastname: doe

+      requirements:

+          - site:

+              node: testsite

+              relationship: tosca.relationships.MemberOfSite
+""" % (self.get_usable_deployment(), self.get_usable_controller()))
+
+        testsite = self.assert_obj(Site, "testsite")
+        testuser = self.assert_user("johndoe@foo.bar")
+
+        self.run_model_policy(save_output="/tmp/usertest:create_user:model_policy")
+
+        # make sure a ControllerSite object was created
+        cu = ControllerUser.objects.filter(user=testuser)
+        assert(len(cu) == 1)
+
+        self.run_observer(save_output="/tmp/usertest:create_user:observer")
+
+        testuser = self.assert_user("johndoe@foo.bar")
+
+        cu = ControllerUser.objects.filter(user=testuser)
+        assert(len(cu) == 1)
+        assert(cu[0].kuser_id is not None)
+        assert(cu[0].kuser_id != "")
+
+if __name__ == "__main__":
+    ObserverUserTest()
+
diff --git a/xos/tosca/tests/observertest.py b/xos/tosca/tests/observertest.py
index f714045..1ec8797 100644
--- a/xos/tosca/tests/observertest.py
+++ b/xos/tosca/tests/observertest.py
@@ -19,6 +19,9 @@
     def get_usable_deployment(self):
         return "MyDeployment"
 
+    def get_usable_controller(self):
+        return "CloudLab"
+
     def ensure_observer_not_running(self):
         ps_output = subprocess.Popen("ps -elfy", shell=True, stdout=subprocess.PIPE).stdout.read()
         if "/opt/xos/xos-observer.py" in ps_output: